Dodo IS 架構的歷史:一個早期的單體

或者,每個擁有巨石的不快樂公司都有自己的不快樂。

與 Dodo 披薩業務一樣,Dodo IS 系統的開發於 2011 年立即開始。 它基於業務流程完全數字化的想法,並且 靠我自己,甚至在 2011 年引起了很多問題和懷疑。 但是 9 年來,我們一直在走這條路——我們自己的開發,從一個整體開始。

這篇文章是對“為什麼要重寫架構並進行如此大規模和長期的更改?”問題的“答案”。 返回上一篇文章 《Dodo IS架構的歷史:後台之道》. 我將從 Dodo IS 的開發是如何開始的,最初的架構是怎樣的,新的模塊是如何出現的,以及由於什麼問題必須進行大規模的改變開始的。

Dodo IS 架構的歷史:一個早期的單體

系列文章“什麼是 Dodo IS?” 講述:

  1. Dodo IS 中的早期單體應用(2011-2015)。 (你在這裡)

  2. 後台路徑:分離基地和總線.

  3. 客戶端路徑:基礎立面(2016-2017)。 (進行中...)

  4. 真正的微服務的歷史。 (2018-2019)。 (進行中...)

  5. 完成整體結構的鋸切和結構的穩定。 (進行中...)

初始架構

2011 年,Dodo IS 的架構是這樣的:

Dodo IS 架構的歷史:一個早期的單體

架構中的第一個模塊是訂單接受。 業務流程是:

  • 客戶打電話給比薩店;

  • 經理拿起電話;

  • 通過電話接受訂單;

  • 在訂單接受界面中並行填寫:它考慮了有關客戶的信息、訂單詳細信息、送貨地址的數據。 

信息系統的界面是這個樣子的……

2011 年 XNUMX 月的第一個版本:

2012年XNUMX月略有改善

Dodo Pizza Information System Delivery 比薩餐廳

用於開發第一個接單模塊的資源是有限的。 我們必須用一個小團隊快速完成很多工作。 一個小團隊就是2個開發者,他們為整個未來的系統奠定了基礎。

他們的第一個決定決定了技術棧的命運:

  • ASP.NET MVC 後端,C# 語言。 開發人員是 dotnetchiki,這個堆棧對他們來說既熟悉又愉快。

  • Bootstrap 和 JQuery 前端:自寫樣式和腳本的用戶界面。 

  • MySQL 數據庫:無許可證費用,易於使用。

  • 服務器在 Windows Server 上,因為那時 .NET 只能在 Windows 下(我們不討論 Mono)。

在物理上,所有這一切都在“主辦方的奉獻”中表達。 

接單應用架構

那時候大家都在說微服務,SOA在大項目上用了5年,比如2006年發布的WCF。 但後來他們選擇了可靠且經過驗證的解決方案。

這裡是。

Dodo IS 架構的歷史:一個早期的單體

Asp.Net MVC 是 Razor,它根據來自表單或客戶端的請求,使用服務器呈現呈現 HTML 頁面。 在客戶端,CSS 和 JS 腳本已經顯示信息,如果需要,通過 JQuery 執行 AJAX 請求。

服務器上的請求最終在 *Controller 類中結束,最終 HTML 頁面的處理和生成在方法中進行。 控制器向稱為 *Services 的邏輯層發出請求。 每項服務都對應於業務的某個方面:

  • 例如,DepartmentStructureService 提供有關比薩餅店和部門的信息。 一個部門是由一個加盟商經營的一組比薩餅店。

  • ReceivingOrdersService 接受併計算訂單的組成。

  • 而SmsService發送短信是通過調用API服務來發送短信的。

服務處理來自數據庫的數據,存儲業務邏輯。 每項服務都有一個或多個具有適當名稱的 *Repositories。 它們已經包含對數據庫中存儲過程的查詢和映射器層。 存儲中有業務邏輯,尤其是在發布報告數據的那些中。 沒有用ORM,大家都靠手寫sql。 

還有一層領域模型和公共幫助類,例如存儲訂單的 Order 類。 在同一個地方,在圖層中,有一個幫助程序根據所選貨幣轉換顯示文本。

這一切都可以用這樣一個模型來表示:

Dodo IS 架構的歷史:一個早期的單體

訂貨方式

考慮一種創建此類訂單的簡化初始方法。

Dodo IS 架構的歷史:一個早期的單體

最初,網站是靜態的。 它上面有價格,最上面是一個電話號碼和題詞“如果你想要披薩,請撥打電話訂購。” 為了訂購,我們需要實現一個簡單的流程: 

  • 客戶訪問帶有價格的靜態站點,選擇產品並撥打站點上列出的號碼。

  • 客戶命名他們想要添加到訂單中的產品。

  • 給出了他的地址和姓名。

  • 操作員接受訂單。

  • 訂單顯示在接受訂單界面。

這一切都從顯示菜單開始。 登錄的用戶操作員一次只接受一個訂單。 因此,推車可以存儲在他的session中(用戶的session存儲在內存中)。 有一個包含產品和客戶信息的 Cart 對象。

客戶為產品命名,操作員點擊 + 在產品旁邊,並向服務器發送請求。 從數據庫中提取有關產品的信息,並將有關產品的信息添加到購物車。

Dodo IS 架構的歷史:一個早期的單體

注意. 是的,這裡你不能從數據庫中提取產品,而是從前端傳輸它。 但為了清楚起見,我準確顯示了數據庫的路徑。 

接下來,輸入客戶的地址和姓名。 

Dodo IS 架構的歷史:一個早期的單體

當您點擊“創建訂單”時:

  • 請求被發送到 OrderController.SaveOrder()。

  • 我們從會話中得到了購物車,有我們需要的數量的產品。

  • 我們用有關客戶端的信息來補充購物車,並將其傳遞給 ReceivingOrderService 類的 AddOrder 方法,然後將其保存到數據庫中。 

  • 數據庫中有包含訂單、訂單的組成、客戶端的表,它們都是相連的。

  • 訂單展示界面去拉出最新的訂單展示出來。

新模塊

接受命令是重要且必要的。 如果您沒有銷售訂單,就無法開展比薩餅業務。 因此,系統開始獲得功能 - 大約從 2012 年到 2015 年。 在此期間,出現了許多不同的系統塊,我稱之為 模塊,相對於服務或產品的概念。 

模塊是由一些共同的業務目標聯合起來的一組功能。 同時,它們在物理上處於同一個應用程序中。

模塊可以稱為系統塊。 例如,這是一個報告模塊、管理界面、 廚房裡的食物追踪器, 授權。 這些都是不同的用戶界面,有些甚至具有不同的視覺風格。 同時,一切都在一個應用程序、一個運行進程的框架內。 

從技術上講,模塊被設計為 Area(這樣的想法甚至保留在 ASP.NET核心). 前端、模型以及它們自己的控制器類都有單獨的文件。 結果,系統從這個...

Dodo IS 架構的歷史:一個早期的單體

...進入這個:

Dodo IS 架構的歷史:一個早期的單體

一些模塊由單獨的站點(可執行項目)實現,這是由於完全獨立的功能,部分是由於稍微獨立、更集中的開發。 這:

  • 現場 - 第一個版本 網站 dodopizza.ru。

  • 出口:從 Dodo IS 為 1C 上傳報告。 

  • 個人 - 員工的個人賬戶。 它是單獨開發的,有自己的入口點和單獨的設計。

  • fs — 託管靜態的項目。 後來我們放棄了它,將所有靜態數據轉移到 Akamai CDN。 

其餘塊位於 BackOffice 應用程序中。 

Dodo IS 架構的歷史:一個早期的單體

名稱解釋:

  • 收銀員 - 餐廳收銀員。

  • ShiftManager - “Shift Manager”角色的接口:比薩店銷售的運營統計,將產品放在停止列表上的能力,改變順序。

  • OfficeManager - “Pizzeria Manager”和“Franchisee”角色的接口。 這裡收集了用於設置比薩店、獎金促銷、接收員工和與員工合作、報告的功能。

  • PublicScreens - 掛在比薩店的電視和平板電腦的界面。 電視顯示菜單、廣告信息、交貨時的訂單狀態。 

他們使用了一個公共服務層、一個公共 Dodo.Core 域類塊和一個公共基礎。 有時他們仍然可以相互引導過渡。 包括個別網站,如 dodopizza.ru 或 personal.dodopizza.ru,都去了一般服務。

當新的模塊出現時,我們盡量重用數據庫中已經創建的服務、存儲過程和表的代碼。 

為了更好地了解系統中模塊的規模,這裡有一張 2012 年的圖表以及開發計劃:

Dodo IS 架構的歷史:一個早期的單體

到 2015 年,一切都在地圖上,甚至更多正在生產中。

  • 訂單接受已經發展成為聯絡中心的一個單獨模塊,接線員在這裡接受訂單。

  • 比薩店裡掛著帶有菜單和信息的公共屏幕。

  • 廚房有一個模塊,當有新訂單到達時會自動播放語音消息“New Pizza”,還會為快遞員打印發票。 這極大地簡化了廚房中的流程,讓員工不會因為大量簡單的操作而分心。

  • 送貨單位變成了一個單獨的送貨結賬台,訂單被發給之前輪班的快遞員。 他的工作時間被計入工資計算。 

與此同時,從2012年到2015年,出現了10多家開發商,開設了35家比薩店,將系統部署到羅馬尼亞,並準備在美國開設分店。 開發人員不再處理所有任務,而是分成小組。 每一個都專注於自己的系統部分。 

問題

包括因為架構(但不僅如此)。

基地混亂

一個基地很方便。 可以在其中實現一致性,並且以關係數據庫中內置的工具為代價。 使用它既熟悉又方便,尤其是在表格和數據很少的情況下。

但經過 4 年多的開發,數據庫原來有大約 600 個表,1500 個存儲過程,其中許多也有邏輯。 遺憾的是,存儲過程在使用 MySQL 時並沒有帶來太多優勢。 它們不被基礎緩存,在其中存儲邏輯會使開發和調試複雜化。 代碼重用也很困難。

許多表沒有合適的索引,某處反而索引很多,插入困難。 有必要修改大約 20 個表 - 創建訂單的事務可能需要大約 3-5 秒。 

表格中的數據並不總是最合適的形式. 在某個地方有必要進行非規範化。 部分定期接收的數據以 XML 結構的形式在列中,這增加了執行時間,延長了查詢時間並使開發複雜化。

同款表的製作非常 異構請求. 流行的表尤其受到影響,就像上面提到的表一樣。 訂單 或表 比薩店. 它們用於顯示廚房、分析中的操作界面。 另一個網站聯繫了他們(dodopizza.ru),在任何給定時間,大量請求可能會突然到來。 

數據未聚合 並且許多計算都是使用基數即時進行的。 這產生了不必要的計算和額外的負載。 

通常,代碼在無法這樣做時就進入了數據庫。 某處沒有足夠的批量操作,某處有必要通過代碼將一個請求分散到多個請求中,以加快速度並提高可靠性。 

代碼中的內聚和混淆

本來應該負責自己那部分業務的模塊卻沒有老老實實做. 其中一些角色的職能重複。 例如,負責他所在城市的網絡營銷活動的本地營銷人員必須同時使用“管理”界面(創建促銷)和“辦公室經理”界面(查看促銷對業務的影響)。 當然,在這兩個模塊中使用了與獎金促銷相同的服務。

服務(一個整體大型項目中的類)可以相互調用以豐富它們的數據。

使用存儲數據的模型類本身, 代碼中的工作以不同方式進行. 某處有構造函數,通過它們可以指定所需的字段。 這是通過公共財產在某處完成的。 當然,從數據庫中獲取和轉換數據是多種多樣的。 

邏輯要么在控制器中,要么在服務類中。 

這些似乎是小問題,但它們大大減慢了開發速度並降低了質量,從而導致不穩定和錯誤。 

大型開發的複雜性

困難出現在發展本身. 有必要並行地製作系統的不同模塊。 將每個組件的需求融入單個代碼變得越來越困難。 同時同意並取悅所有組件並不容易。 除此之外還有技術上的限制,尤其是在基礎和前端方面。 有必要放棄 jQuery 而轉向高級框架,尤其是在客戶端服務(網站)方面。

在系統的某些部分,可以使用更適合於此的數據庫。. 例如,後來我們有從 Redis 轉移到 CosmosDB 來存儲訂單籃子的用例。 

涉及他們領域的團隊和開發人員顯然希望他們的服務在開發和推出方面有更多的自主權。 合併衝突,發布問題。 如果對於 5 名開發人員來說這個問題微不足道,那麼對於 10 名開發人員,甚至隨著計劃的增長,一切都會變得更加嚴重。 接下來是移動應用程序的開發(從 2017 年開始,到 2018 年才開始) 大跌). 

系統的不同部分需要不同級別的穩定性, 但由於系統的強大連接性,我們無法提供此功能。 在管理面板中開發新功能的錯誤很可能發生在接受網站訂單時,因為代碼是通用的和可重用的,數據庫和數據也是一樣的。

在這樣一個單體模塊化架構的框架下,或許可以避免這些錯誤和問題:進行職責分工,重構代碼和數據庫,明確分層,每天監控質量。 但是所選擇的架構解決方案和對系統功能快速擴展的關注導致了穩定性方面的問題。

The Power of the Mind 博客如何將收銀機放入餐廳

如果比薩店網絡(和負載)的增長以相同的速度繼續,那麼一段時間後下降將導致系統不會上升。 很好地說明了我們到 2015 年開始面臨的問題,這裡有這樣一個故事。 

在博客“心靈力量”是一個顯示整個網絡年度收入數據的小部件。 該小部件訪問了提供此數據的 Dodo 公共 API。 該統計數據目前可在 http://dodopizzastory.com/. 該小部件顯示在每個頁面上,並每 20 秒向計時器發出一次請求。 該請求轉到 api.dodopizza.ru 並要求:

  • 網絡中比薩店的數量;

  • 自年初以來的網絡總收入;

  • 今天的收入。

收入統計請求直接進入數據庫並開始請求訂單數據,即時匯總數據並給出金額。 

餐館的收銀台轉到同一張訂單表,卸載今天收到的訂單清單,並向其中添加新訂單。 收銀機每 5 秒或在頁面刷新時發出請求。

該圖如下所示:

Dodo IS 架構的歷史:一個早期的單體

一年秋天,Fyodor Ovchinnikov 在他的博客上寫了一篇很長很受歡迎的文章。 很多人來到博客,開始仔細閱讀所有內容。 當每個來的人都在閱讀這篇文章時,收入小部件正常工作並每 20 秒請求一次 API。

API 調用一個存儲過程來計算連鎖店中所有比薩餅店自年初以來的所有訂單總和。 聚合基於非常流行的訂單表。 當時所有開餐廳的所有收銀台都去它。 收銀台停止響應,不接受訂單。 他們也沒有被網站接受,沒有出現在跟踪器上,值班經理在他的界面中看不到他們。 

這不是唯一的故事。 到 2015 年秋天,每個星期五系統上的負載都非常嚴重。 有幾次我們關閉了公共 API,有一次,我們甚至不得不關閉網站,因為沒有任何幫助。 甚至還有一份服務清單,其中包含重負載下的關閉命令。

從現在開始,我們將開始與負載和系統穩定性作鬥爭(從 2015 年秋季到 2018 年秋季)。 就在這時發生了”大跌”。 此外,有時也會出現故障,有些是非常敏感的,但一般不穩定的時期現在可以認為已經過去了。

業務快速增長

為什麼不能立即完成? 看看下面的圖表。

Dodo IS 架構的歷史:一個早期的單體

同樣在 2014-2015 年,在羅馬尼亞開設了一家店,並正在準備在美國開設一家店。

網絡發展非常迅速,新的國家開張了,新形式的比薩店出現了,例如,在美食廣場開了一家比薩店。 所有這些都需要特別注意 Dodo IS 功能的擴展。 沒有所有這些功能,沒有廚房的跟踪,沒有系統的產品和損失核算,沒有在美食廣場大廳顯示訂單的發布,我們很難談論“正確”的架構和“正確”的方法現在發展。

及時修訂架構和普遍關注技術問題的另一個障礙是 2014 年的危機。 像這樣的事情嚴重打擊了團隊成長的機會,尤其是對於像 Dodo Pizza 這樣的年輕企業來說。

有幫助的快速解決方案

問題需要解決方案。 通常,解決方案可以分為兩組:

  • 快速的方法可以撲滅大火併提供少量安全邊際,並為我們贏得改變的時間。

  • 系統性的,因此是長期的。 重新設計了一些模塊,將單體架構拆分為單獨的服務(其中大部分根本不是微觀的,而是宏觀的服務,並且有一些關於它的東西 Andrey Morevskiy 的報告). 

快速更改的干列表如下:

擴大基地主

當然,應對負載首先要做的就是增加服務器的容量。 這是為 master 數據庫和 Web 服務器完成的。 las,這只有在一定限度內才有可能,然後它變得太昂貴了。

從 2014 年開始,我們已經遷移到 Azure,當時我們也寫了關於這個話題的文章“Dodo Pizza 如何使用 Microsoft Azure 雲配送披薩”。 但是在基地服務器的一系列增加之後,他們遇到了成本問題。 

用於讀取的基礎副本

為基地製作了兩個副本:

讀副本 參考請求. 它用於讀取目錄、類型、城市、街道、比薩店、產品(緩慢更改的域),以及在可以接受小延遲的那些接口中。 有 2 個這樣的副本,我們以與主副本相同的方式確保它們的可用性。

用於報告請求的 ReadReplica. 該數據庫的可用性較低,但所有報告都發送給了它。 讓他們有海量數據重新計算的繁重請求,但不影響主數據庫和操作界面。 

在代碼中緩存

代碼中的任何地方都沒有緩存(根本)。 這導致對加載的數據庫發出額外的(並非總是必要的)請求。 緩存首先既存在於內存中,也存在於外部緩存服務上,即 Redis。 一切都隨著時間的推移而失效,設置在代碼中指定。

多個後端服務器

應用程序的後端也需要擴展以處理增加的工作負載。 有必要從一個 iis 服務器創建一個集群。 我們重新安排了 申請環節 從內存到 RedisCache,這使得將多個服務器放在一個簡單的負載均衡器後面成為可能。 起初,使用與緩存相同的 Redis,然後將其拆分為多個。 

結果,架構變得更加複雜......

Dodo IS 架構的歷史:一個早期的單體

……但消除了一些緊張感。

然後有必要重做我們承擔的加載組件。 我們將在下一部分討論這個問題。

來源: www.habr.com

添加評論