日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當(dāng)前位置:首頁 > > 架構(gòu)師社區(qū)
[導(dǎo)讀]來自:阿里巴巴中間件 文? |??挽晴 個(gè)人簡介: 2014年12月加入餓了么,當(dāng)時(shí)參與后臺系統(tǒng)的研發(fā)(Walis+Javis=>Walle),主要面向客服和BD。 2015年5月開始接觸訂單系統(tǒng)的研發(fā),7月負(fù)責(zé)訂單研發(fā)組;度過單體應(yīng)用到服務(wù)化這個(gè)階段。 2016年初搭建訂單的測試團(tuán)隊(duì),


千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!

來自:阿里巴巴中間件

文  |  挽晴


個(gè)人簡介:
2014年12月加入餓了么,當(dāng)時(shí)參與后臺系統(tǒng)的研發(fā)(Walis+Javis=>Walle),主要面向客服和BD。
2015年5月開始接觸訂單系統(tǒng)的研發(fā),7月負(fù)責(zé)訂單研發(fā)組;度過單體應(yīng)用到服務(wù)化這個(gè)階段。
2016年初搭建訂單的測試團(tuán)隊(duì),訂單拆分為正逆向后,主要負(fù)責(zé)正向和交付部分。
2017年做了一些平臺搭建的探索。
2018年初負(fù)責(zé)整個(gè)訂單正逆向和交付,年中將下單、購物車部分一起歸并,年底和商戶訂單部分整合,形成交易中臺。
2019年10月從交易中臺轉(zhuǎn)出,近期做了一小段時(shí)間的組織效能和架構(gòu)。


我為什么會(huì)寫這篇文章,究其緣由:
 
一是自己在交易域做了 4 年,有很多只有我才知道,才能串起來的故事,想把這些記錄并保留下來。

二是發(fā)現(xiàn)后邊的很多同學(xué)看交易體系時(shí),一接觸就是分布式、SOA、每日百萬、千萬數(shù)據(jù)量,只知道它是這個(gè)樣子,很難理解背后的思考和緣由。伴隨自己這幾年的經(jīng)驗(yàn),想讓大家能夠更容易的理解這個(gè)演化過程的原因和歷程,有甘有苦。

三是很多總結(jié)也好,方法論也好,更多是去除了“糟粕”呈現(xiàn)在大家面前,這里可能會(huì)稍微加一點(diǎn)“毒雞湯”,現(xiàn)實(shí)不一定那么美好,我們有很多抉擇,現(xiàn)在回過頭來看,也許是慶幸,也許是錯(cuò)誤。
 
這篇文章希望通過一些發(fā)展的故事和思考來給讀者呈現(xiàn)整個(gè)歷程,大家可以看到非常多野蠻生長的痕跡,并會(huì)附帶一些思考和總結(jié),但不會(huì)像快餐式的總結(jié)很多大道理。

那我們就從2012年的太古時(shí)期講起。
 

太古



在談?dòng)唵沃?,我們往前再考古考古,在太古時(shí)代,有一套使用 Python 寫的系統(tǒng),叫做 Zeus 的系統(tǒng),這個(gè) Zeus 包含了當(dāng)時(shí)餓了么最核心的幾大模塊,比如訂單、用戶、餐廳,這些統(tǒng)統(tǒng)在一個(gè)代碼庫中,并且部署在同一臺機(jī)器, Zeus 之外還有兩大核心,即餓了么 PC ,也就是很多老人常提的「主站」,以及面向商戶的 NaposPC 。這些系統(tǒng)通過 Thrif 協(xié)議通信。除開這條鏈路之外,所有雜亂的內(nèi)部功能,全在一個(gè)叫 walle 的系統(tǒng)中,這個(gè) Walle 系統(tǒng)是采用 PHP 寫的。

 
那么當(dāng)時(shí)的 Zeus ,大概長這個(gè)樣子:
                                 千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 據(jù)不嚴(yán)格考究,從 Git 的提交歷史看,訂單部分的第一個(gè) commit 是余立鑫同學(xué)于 2012 年 9 月 1 日提交的,內(nèi)容是" add eos service for zeus. currently only defind a simple get api. ",這個(gè) EOS 指的就是訂單系統(tǒng),即 ElemeOrderService 的簡稱,這個(gè)名詞沿用到了今天,成為交易正向的訂單部分,甚至一段時(shí)間是訂單組的代名詞。
 
 Zeus 在后來其實(shí)經(jīng)過了一定的重構(gòu),叫做 Zeus2 ,但具體時(shí)間已不可考。
 

萌芽



2014 年 10 月我到餓了么來面試,面試官是商戶端負(fù)責(zé)人磊哥。 12 月 1 日,我入職餓了么, HR 領(lǐng)著帶著一臉萌新的我,到磊哥面前時(shí),磊哥把我?guī)У?JN 面前說,“這就是那個(gè)實(shí)習(xí)生”,然后扭頭就跑了。后來得知,當(dāng)時(shí)面試結(jié)束后,磊哥和 JN 同學(xué)說,剛剛面了一個(gè)實(shí)習(xí)生,湊合能用,正巧商戶組有計(jì)劃轉(zhuǎn)型 Java ,而佳寧還很缺 python 的人,然后就騙了 JN 一頓飯把我賣了。

 
回到正題,在 2014 年 12 月~ 2014 年 4 月這幾個(gè)月的時(shí)間里,我配合完成了一個(gè)更老的 BD 系統(tǒng)后端遷移到 Walis ,并且在我的導(dǎo)師轉(zhuǎn)崗到 CI 團(tuán)隊(duì)后,自己完成了 Walis 從單應(yīng)用遷移到分布式應(yīng)用。
 

訂單組的成立

 
對我來說,完全是運(yùn)氣和緣分...
  
接近 2015 年 5 月的時(shí)候,我的主管,JN同學(xué),有一天突然找到我,看起來很興奮,告訴我,公司打算成立一個(gè)訂單組,這個(gè)訂單組由他來負(fù)責(zé),除了他之外,他唯獨(dú)選中了我(大概是因?yàn)樯隙挝姨岬降囊恍┙?jīng)歷,在可選的人里,還湊合~),說是我怎么怎么讓他相中,這個(gè)男人忽悠起人來,一套一套的。
 
作為一個(gè)技術(shù)人員,內(nèi)心非常沸騰。一是高并發(fā)、高流量、分布式這些耳熟能詳?shù)母叽笊厦~之前只是聽說過,不曾想這么快就能夠接觸到這樣的系統(tǒng);二是我們此前做的系統(tǒng)很“邊緣”,有多邊緣呢,白天幾乎沒什么請求, BD 走訪商戶回來,恰巧晚上才是高峰期,即使是晚上,關(guān)鍵的單接口也就偶爾幾個(gè)、十幾個(gè)請求,是當(dāng)時(shí)那種掛 2 個(gè)小時(shí)才可能有人發(fā)現(xiàn),掛半天不一定有人叫的系統(tǒng),那時(shí)候我們幸福的晚上 7 點(diǎn)前就下班了,第一次發(fā)布的時(shí)候非常鄭重的和我說,可能要加班到晚上 8 點(diǎn)半。
 
之所以選擇 JN 做訂單組負(fù)責(zé)人,因?yàn)樗m然是個(gè)前端工程師起家,做的是“邊緣”后臺系統(tǒng),但卻是對整個(gè)公司所有系統(tǒng)和業(yè)務(wù)都比較熟悉的人,很適合發(fā)展訂單系統(tǒng)。
 
嗯,沒錯(cuò),這個(gè)組在成立前一天,一直只有我們兩個(gè)人。當(dāng)時(shí)的我還沒畢業(yè),除了興奮,更多的是忐忑。
 
2015 年 5 月 12 日,訂單組正式成立,成立當(dāng)天,拉來了隔壁組的 ZH (是個(gè)PHPer,招進(jìn)來的時(shí)候是計(jì)劃去接Walle),然后聊到一半的時(shí)候,當(dāng)時(shí)的部門總監(jiān)跑過來,說正巧有個(gè)小哥哥當(dāng)天入職,還不錯(cuò),正好給訂單組吧,是個(gè) Java 工程師。于是乎,成立當(dāng)天,我們?nèi)藬?shù)翻了一倍,變成了 4 個(gè)人。
 
我們給自己的第一個(gè)任務(wù): 讀代碼,理業(yè)務(wù),畫圖。和 CTO 申請到了 1 個(gè)月的時(shí)間來緩沖,這段時(shí)間不接任何業(yè)務(wù)需求!

分別請來了訂單的前主程、Python 框架負(fù)責(zé)人、Zeus 系應(yīng)用運(yùn)維負(fù)責(zé)人給我們講解。實(shí)際上,每個(gè)人的分享也就 1 個(gè)多小時(shí)。那一個(gè)月真是從幾萬行 Python 代碼,沒有任何產(chǎn)品文檔,極其稀少的注釋,一行行的啃,每個(gè)人解讀一部分。我最后匯總把整個(gè)訂單的生命周期、關(guān)鍵操作、關(guān)鍵業(yè)務(wù)邏輯,畫在了一張大圖里,這張圖,我們后來用了一年多。
 
其實(shí),當(dāng)時(shí)年中旬的餓了么,產(chǎn)研規(guī)模已經(jīng)達(dá)到幾百人左右,新 CTO ,雪峰老師是年初加入餓了么,整個(gè)基礎(chǔ)設(shè)施的起步是 2015 年下半年,整個(gè)體系的飛速搭建是在 2016 年。
       
可以說是正處于相當(dāng)混亂,又高速發(fā)展的時(shí)期。我們稱那個(gè)時(shí)間是一邊開著跑車一邊換輪胎。
 

Zeus 解耦

 
和訂單真正密切相關(guān)的第一個(gè) Super 任務(wù),大概是從 6 月左右開始 --- Zeus 解耦,HC老師是 Python 框架的負(fù)責(zé)人,也是個(gè)人最佩服和敬仰的技術(shù)專家之一,在美國舉行 Qcon 上,作為首席架構(gòu)師介紹過當(dāng)時(shí)餓了么整體技術(shù)架構(gòu)。剛才在太古時(shí)期已經(jīng)說到, Zeus 是一個(gè)巨型單體應(yīng)用,為了今后各個(gè)部分能夠快速發(fā)展,降低耦合和牽連影響等,公司啟動(dòng)了 zeus 解耦項(xiàng)目,總之就兩個(gè)字,拆分。
      
經(jīng)過 1 個(gè)多月的密集會(huì)議,完成了拆分的方案。說的似乎沒那么難,但是這場口水戰(zhàn)當(dāng)時(shí)打的不可開交,拆分后不同的服務(wù)歸屬于誰?模塊和模塊之間并沒有切分的那么干凈,A和B服務(wù)中的邊界怎么定等等一系列問題。當(dāng)時(shí)的我還不夠格參與討論。
 
結(jié)論是, Zeus 將要拆分成下邊的幾個(gè)主服務(wù):
 
  • zeus.eos => 訂單服務(wù)
  • zeus.eus => 用戶服務(wù)
  • zeus.ers => 商家服務(wù)
  • zeus.eps => 營銷服務(wù)(新產(chǎn)物)
  • zeus.sms => 短信服務(wù)
  • ...
 
第一階段
每個(gè)被拆分后的服務(wù),隨之進(jìn)行的是新的一波重構(gòu)和拆分。例如從 zeus.eos 分離出來 biz.booking ,拿走了下單和購物車部分能力;分離出來 biz.ugc 拿走了訂單評價(jià)相關(guān)能力。
        千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!

拆分主要經(jīng)歷的幾個(gè)階段:
1、(7月份)共享代碼倉庫,按模塊獨(dú)立運(yùn)行。即,把 Zeus 所有代碼都打包到服務(wù)器后,按照劃分,在特定機(jī)器上只將特定模塊單獨(dú)啟動(dòng),開放特定端口。
2、(8月份) Proxy 階段。即在原服務(wù)中,要遷出去的接口上增加一個(gè)代理,可以代理到新服務(wù)的接口,由服務(wù)注冊中心開關(guān)能力來控制切換流量大小。
3、(8月份至9月初)腳本、模塊的完全切分改造。
4、(9月份)代碼倉庫獨(dú)立。使用了 Git 的核彈武器 filter-branch ,將模塊中的代碼和變更歷史,完全完整的從原代碼庫中分離。而此時(shí)部署卻仍然為混布,在發(fā)布工具中,某個(gè)獨(dú)立應(yīng)用發(fā)布后實(shí)際是替換了 Zeus 這個(gè)大項(xiàng)目下的某個(gè)目錄。
5、(9月份)配置獨(dú)立。原來的配置由 saltstack 刷到服務(wù)器上,被服務(wù)器上多個(gè)應(yīng)用所共用,我們將其直接改成使用服務(wù)注冊中心的配置下發(fā)能力獲取單個(gè)應(yīng)用配置。在這個(gè)階段也基本上過渡到了軟負(fù)載。
6、(次年3月份)物理部署獨(dú)立。當(dāng)然這是解耦二期的內(nèi)容了。
 
當(dāng)然,這次拆分,還帶來了另外一個(gè)產(chǎn)物, Python 的 SOA 框架 zeus_core,zeus_core 要大概在 4 月份左右先于業(yè)務(wù)服務(wù)被拆分出來。
 
整個(gè)解耦一期,持續(xù)了大概半年時(shí)間。在期間,沒有發(fā)生因?yàn)椴鸱謱?dǎo)致的事故,也幾乎沒有什么冒煙。想想當(dāng)時(shí)沒有用什么高深的東西,工具落后,沒有專職測試,完全靠著一幫早期工程師和運(yùn)維同學(xué)的技術(shù)素養(yǎng)。
 
分庫分表
 
仍然是在 2015 年,大概是 9、10 月左右確定分庫分表要開始實(shí)施,而分庫分表的方案,在我介入時(shí)已經(jīng)幾乎敲定,并由 CI 部門的 DAL 團(tuán)隊(duì)主導(dǎo)。
 
為什么要做分庫分表?
 
一是扛不住并發(fā)。 當(dāng)時(shí)我們的訂單庫的 MySQL 是采取 1 主 5 從的架構(gòu),還有 1 臺做 MHA 。DB 不太能承受住當(dāng)時(shí)的并發(fā)壓力,并且,對風(fēng)險(xiǎn)的抵抗能力非常的弱。業(yè)務(wù)如果做一些活動(dòng)沒提前告知,我們的從庫一旦掛了一個(gè),就只能來回切,嚴(yán)重的時(shí)候只能大量限流。而且,那段時(shí)間,作為技術(shù),我們也在祈禱美團(tuán)外賣別在高峰期掛,美團(tuán)外賣一旦掛了,流量就會(huì)有一部分流到餓了么,我們就開始也緊張起來了。同樣的,那段時(shí)間,我們整站掛了,美團(tuán)外賣也不太能扛得住,大家都在經(jīng)歷相似的發(fā)展階段。
 
二是 DDL 成本太高 ,業(yè)務(wù)又處于戰(zhàn)斗高峰。當(dāng)時(shí)餓了么的單量在日均百萬出頭。有一些業(yè)務(wù)需求,希望在訂單上新增字段,然而,我們找到 DBA 評估的時(shí)候,給的答案是,樂觀估計(jì)需要停服 3 小時(shí),悲觀估計(jì)要 5 小時(shí),并且需要 CEO 審批。顯然,這個(gè)風(fēng)險(xiǎn),技術(shù)團(tuán)隊(duì)難以接受,而業(yè)務(wù)團(tuán)隊(duì)也無法接受。那么投機(jī)取巧的方案,就是在預(yù)留的 Json 擴(kuò)展字段中不斷的塞,這種方式一定程度上緩解了很長一段時(shí)間的壓力,然而,也埋下了非常多的隱患。
 
當(dāng)然,還有一些特殊的業(yè)務(wù)場景以及一些開放出去顆粒度很大的接口,會(huì)產(chǎn)生一些性能極差的 SQL ,都會(huì)引爆全站。
 
Shardin 后物理結(jié)構(gòu)如下:
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
一次更新操作邏輯如下:

                        千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
   
我們其實(shí)是做了兩維 Sharding ,兩個(gè)維度都是 120 個(gè)分片,但是可以通過三種方式路由(用戶 ID、商戶ID、訂單ID),寫入優(yōu)先保證用戶維度成功。由于資源的原因,用戶和商戶分片是交錯(cuò)混合部署的。

 (加粗部分其實(shí)是有一些坑的,這個(gè)特殊定制也是餓了么唯一,如果有興趣以后可以展開)
 
更具體分庫分表的技術(shù)細(xì)節(jié)不在這里展開,大致經(jīng)歷了幾個(gè)階段:
 
1、制定新的訂單號生成規(guī)則,并完成改造接入。
2、數(shù)據(jù)雙寫,讀舊,對比數(shù)據(jù)。
3、對不兼容的 SQL 進(jìn)行改造,比如跨分片的排序、統(tǒng)計(jì),不帶shardingkey的SQL等等。
4、數(shù)據(jù)雙寫,讀新。(與3有部分同步進(jìn)行)
5、完成數(shù)據(jù)庫切換,數(shù)據(jù)寫新讀新。
 
這段日子,作為業(yè)務(wù)團(tuán)隊(duì),大部分時(shí)間其實(shí)花在第三部分,也曾奮斗過好幾次到凌晨3、4點(diǎn)。
 
在 2016 年的春節(jié)前夕,為了頂過業(yè)務(wù)峰值和系統(tǒng)穩(wěn)定,我們甚至把 DB 里的數(shù)據(jù)做歸檔只留最近 15 天內(nèi)的訂單
 
記得最終切換的那一天,大概在 2016 年 3 月中旬,我和幾位同學(xué)早上 5 點(diǎn)多就到了公司,天蒙蒙亮。整個(gè)餓了么開始停服,然后阻斷寫請求,完成 DB 指向的配置,核對無誤,恢復(fù)寫請求,核驗(yàn)業(yè)務(wù)無誤,慢慢放開前端流量,重新開服。整個(gè)過程核心部分大概 10 分鐘,整個(gè)停服到完全開放持續(xù)了半個(gè)小時(shí)。
 
到了第二天,我們才得以導(dǎo)入最近 3 個(gè)月的歷史訂單。
 
這次變更做完,我們基本擺脫了 DB 的瓶頸和痛點(diǎn)(當(dāng)然,后邊的故事告訴我們,有時(shí)候還是有點(diǎn)天真的~~~)
 
消息廣播
 
那個(gè)時(shí)期,也是在 15 年的 7 月左右,受到一些架構(gòu)文章的影響,也是因?yàn)?JN 提到了這一點(diǎn),我們決定做訂單的消息廣播,主要目的是為了進(jìn)一步解耦。
 
在調(diào)研了 RabbitMQ、NSQ、RocketMQ、Kafka、ActiveMQ 之后,我得出的最終結(jié)論,選型還是 RabbitMQ ,其實(shí)當(dāng)時(shí)我認(rèn)為,RocketMQ 更為適合,特別是順序消息的特性,在交易某些業(yè)務(wù)場景下能夠提供天然的支持,然而,運(yùn)維團(tuán)隊(duì)主要的運(yùn)維經(jīng)驗(yàn)是在 RabbitMQ ??蚣軋F(tuán)隊(duì)和運(yùn)維團(tuán)隊(duì)的同學(xué)很自信,自從搭建以來,也沒有出過任何問題,穩(wěn)的一匹,如果選擇 RabbitMQ ,就能夠得到運(yùn)維團(tuán)隊(duì)的天然支持,這對于我們當(dāng)時(shí)的業(yè)務(wù)團(tuán)隊(duì)來說,能夠避免很多風(fēng)險(xiǎn)。
 
于是由框架團(tuán)隊(duì)承接了對 RabbitMQ 進(jìn)行一輪嚴(yán)謹(jǐn)?shù)男阅軠y試,給出部分性能指標(biāo)。這一場測試,最終搭建了一個(gè) 3Broker 組成的集群,單獨(dú)為訂單服務(wù),在此之前只有一個(gè) MQ 節(jié)點(diǎn),服務(wù)于 Zeus 體系的異步消息任務(wù)。
 
為了保證對交易主流程不產(chǎn)生影響,然后在 Client 端 SOA 框架進(jìn)行了一系列的容錯(cuò)改造,主要是針對連接 MQ 集群時(shí)的發(fā)送超時(shí)、斷開等容錯(cuò),消息發(fā)送異步進(jìn)行且重試一定次數(shù)。最終全新搭建了由 3 個(gè)節(jié)點(diǎn)組成的 MQ 集群,訂單的消息最終發(fā)往這個(gè)集群。
 
期間,其實(shí)踩了一個(gè)小坑。雖然框架團(tuán)隊(duì)已經(jīng)進(jìn)行了異常情況的容錯(cuò)。但畢竟消息廣播的發(fā)送時(shí)機(jī)是和主流程狀態(tài)扭轉(zhuǎn)緊密相連的,代碼在上線前,當(dāng)時(shí)一向謹(jǐn)慎的我,為首次上線加上了一個(gè)消息發(fā)送的開關(guān)。那是一個(gè)晚上,大概 8 點(diǎn)多,現(xiàn)在回想,當(dāng)時(shí)灰度和觀察時(shí)間是有一些短的,當(dāng)我全部發(fā)布完成后,很快,監(jiān)控上顯著看到接口開始嚴(yán)重超時(shí)(我們當(dāng)時(shí)采用框架默認(rèn)的超時(shí)設(shè)定, 30s,其實(shí)這個(gè)配置很嚴(yán)重),進(jìn)而產(chǎn)生了大量接口嚴(yán)重超時(shí),很明顯,有什么拖慢了接口。交易曲線斷崖式的下降,我立馬就被NOC 進(jìn)行了 on call ,迅速將消息發(fā)送的開關(guān)關(guān)閉,恢復(fù)也是一瞬間的事情,然后,人肉跑到架構(gòu)團(tuán)隊(duì)前邊跪求協(xié)助排查原因(終歸還是當(dāng)時(shí)的自己太菜)。
 
當(dāng)晚,我們開、關(guān)、開、關(guān)、開、關(guān)...流量從 5% 、10% 、30% 等等,不同嘗試、驗(yàn)證之后,最后得出的結(jié)論,是和當(dāng)時(shí)的 HAProxy 配置有關(guān),由于 HAProxy 提前關(guān)閉了和 RabbitMQ 集群的連接,服務(wù)的 Client 仍然拿著壞死的連接去請求,進(jìn)而造成了這次問題,并且, Client 確實(shí)沒對這種超時(shí)進(jìn)行容錯(cuò)。在調(diào)整了 HAProxy 的鏈接超時(shí)配置之后,癥狀就消除了。雖然,從日志上看遺留有一些隱患。
 
此時(shí),是長這樣的,每個(gè)接入的業(yè)務(wù)方需要申請一個(gè) Topic , Topic 之下掛多少 Queue 可以根據(jù)業(yè)務(wù)需求自己確定。
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
 這個(gè)物理架構(gòu)部署穩(wěn)定運(yùn)行了不到1年時(shí)間就存在不少問題,下章會(huì)再展開。
 
在使用上,當(dāng)時(shí)定下了這么幾條原則:
1、訂單不對外直接暴露自身狀態(tài),而是以事件的方式對外暴露。因?yàn)闋顟B(tài)是一個(gè)描述,而事件則代表了一個(gè)動(dòng)作,同時(shí)可以將訂單狀態(tài)細(xì)節(jié)和接入方解耦。
2、消息廣播僅用于廣播事件,而不用于數(shù)據(jù)同步,如消費(fèi)者需要更多的數(shù)據(jù)則反查訂單數(shù)據(jù)接口,時(shí)間戳包含事件產(chǎn)生時(shí)間和發(fā)送時(shí)間(時(shí)間是后來加上的)。即消息體包括 header 信息,僅放入用于解釋這個(gè)事件的內(nèi)容,還包括交易雙方主鍵和一些能夠用于做通用過濾或二次路由的信息。
3、費(fèi)者在消費(fèi)消息時(shí)應(yīng)當(dāng)保證自身的冪等性,同時(shí)應(yīng)當(dāng)讓自己在消費(fèi)時(shí)無狀態(tài)。如果一定要順序消費(fèi),那么自行通過Redis等方案實(shí)現(xiàn)。
4、消費(fèi)者接入時(shí), Topic 和 Queue 需要按照一定命名規(guī)范,同時(shí), Queue 的最大積壓深度為 10k ,超過則舍棄。消費(fèi)者要明確自身是否接受消息可損,同時(shí)要保證自身的消費(fèi)性能。按照當(dāng)時(shí)評估,消息堆積到達(dá)百萬時(shí)會(huì)使得整個(gè)集群性能下降 10% 。(在全局架構(gòu)的建議下,我們還提供了以 Redis 為介質(zhì),作為鏡像存儲了訂單事件,不過體驗(yàn)并不夠優(yōu)雅)
 
而這套消息廣播的邏輯架構(gòu),一直持續(xù)使用到今天,在解耦上產(chǎn)生了巨大的紅利。
 

初探


15 年中旬到 16 年初,我們處在每天的單量在百萬以上并逐步快速增長這么一個(gè)階段。


OSC
 
在那個(gè)時(shí)期,也看了很多架構(gòu)文章,ESB、SOA、微服務(wù)、CQRS、EventSource 等等,我們也在積極探討訂單系統(tǒng)如何重構(gòu),以支撐更高的并發(fā)。當(dāng)時(shí)聽的最多的,是京東的 OFC ,還特地買了《京東技術(shù)解密》在研讀,不過很快得出結(jié)論,幾乎無太大參考價(jià)值。主要原因是京東的 OFC ,很明顯是由零售業(yè)務(wù)的特性決定的,很多 OFC 里的概念,作為入行尚淺的我們,套到餐飲 O2O ,幾乎難以理解。但我們還是深受其影響,給小組取了一個(gè)相似的縮寫,OSC,Order Service Center 。
 
由于手頭上這套訂單已經(jīng)服役了 3 年多,公司的主要語言棧從人數(shù)上也由 Python 傾向到 Java ,沒多久,我們打算重寫這套訂單體系。于是,我設(shè)計(jì)了一套架構(gòu)體系,以 osc 為應(yīng)用的域前綴。這套體系的核心理念: 訂單是為了保持交易時(shí)刻的快照,盡可能的保持自己的簡潔,減少對各方的依賴,減輕作為數(shù)據(jù)通道的作用。
 
我們選取的語言棧選型是 Java ,也就是計(jì)劃開始轉(zhuǎn)型 Java 。(很不巧,我們真正轉(zhuǎn)型到 Java 最后發(fā)生在 2019 年).

此時(shí),正值 9 月。很巧的是,公司開始第一次開始設(shè)立新服務(wù)的架構(gòu)評審制度,我這個(gè)方案,大概就是參與評審的 Top1、2 小白鼠,新鮮的大錘正等著敲人。
 
其實(shí),在那之后的1年回過頭來看,還挺感謝這次架構(gòu)評審,不是因?yàn)橥ㄟ^了,而是因?yàn)楸痪芙^了。
 
說來也好笑,那一次,依稀記得參與架構(gòu)評審的評委成員, DA 負(fù)責(zé)人、基礎(chǔ) OPS 負(fù)責(zé)人、入職沒多久的一個(gè)架構(gòu)師。
       
架構(gòu)師當(dāng)時(shí)的提問關(guān)注點(diǎn)在這套架構(gòu)是能夠用1年還是3年,而基礎(chǔ)OPS負(fù)責(zé)人的提問,特別有意思,他問了第一個(gè)問題,這套系統(tǒng)是關(guān)鍵路徑嗎?我心想,這不是廢話嗎,我直接回答,最中間那部分是的。

然后第二個(gè)問題,出了問題,這個(gè)應(yīng)用可以降級嗎?我一想,這不也是廢話嗎,這個(gè)鏈路當(dāng)然沒法降級,這是最核心最基礎(chǔ)的鏈路,公司的核心業(yè)務(wù)就是圍繞交易。(可能是雙方的理解不在一個(gè)頻道上)。

于是,他給的結(jié)論是,關(guān)鍵路徑,又是核心的訂單,沒法降級,一旦出了問題,大家都沒飯吃。于是評審結(jié)束,結(jié)論是不通過。
 

組建測試團(tuán)隊(duì)

 
交易團(tuán)隊(duì)一直沒有專職的測試,也就是說,所有的內(nèi)容,都是由研發(fā)自測來保證的。而公司當(dāng)時(shí)的自動(dòng)化測試非常的弱,幾乎所有的測試都是依靠手工進(jìn)行。但是,我此時(shí)覺得非常有必要拿到測試資源。我強(qiáng)烈的要求成立一個(gè)測試小組來給訂單上線質(zhì)量加上一層防護(hù)。
 
當(dāng)時(shí)還發(fā)生了一些有趣的事情,據(jù) JN 去了解,框架團(tuán)隊(duì)是沒有測試的,然而他們似乎沒出什么問題,當(dāng)時(shí)他們很自豪的解釋,技術(shù)憑什么不應(yīng)該自己保障代碼的質(zhì)量。簡直理直氣壯,無懈可擊。我覺得這個(gè)觀點(diǎn)有一些理想,研發(fā)自己可能沒那么容易發(fā)現(xiàn)自己的錯(cuò)誤,引入另外一批人從另外一個(gè)角度切入,能夠進(jìn)一步提升質(zhì)量的保障,畢竟這個(gè)系統(tǒng)是如此的重要和高風(fēng)險(xiǎn),但是我們也并不應(yīng)該建立一個(gè)只能提供“點(diǎn)點(diǎn)點(diǎn)”的測試團(tuán)隊(duì)。
 
最后,在和 JN 長時(shí)間的溝通后,我們確定了當(dāng)時(shí)測試小組的定位和職責(zé): 保證代碼質(zhì)量是研發(fā)自己應(yīng)盡的責(zé)任,測試開發(fā)在此基礎(chǔ)上,主要提供工具支持,讓測試成本降低,同時(shí)在精力允許的情況,提供一定程度的測試保障。
 
于是,在 2016 年 2、3 月左右,交易團(tuán)隊(duì)來了第一位測試,差不多在 4 月的時(shí)候,測試 HC 達(dá)到了 4 人,整個(gè)測試小組由我來負(fù)責(zé)。
 

第一件事情,搭建自動(dòng)化集成測試


技術(shù)棧上的選擇,采用了 RobotFramework ,主要原因是整個(gè)團(tuán)隊(duì)當(dāng)時(shí)仍然以 Python 為主要語言,測試開發(fā)同學(xué)實(shí)際上 Python 和 Java 也都能寫;另外一點(diǎn)是  RobotFramwork 的關(guān)鍵字驅(qū)動(dòng),有一套自己的規(guī)范,和系統(tǒng)相關(guān)的lib可以被提煉出來,即使做語言棧轉(zhuǎn)型時(shí),成本也不會(huì)很高。
 
除了測試的流程規(guī)范和標(biāo)準(zhǔn)外,開始想搭建一個(gè)平臺,用于管理測試用例、執(zhí)行情況和執(zhí)行報(bào)告。
 
這套體系我命名為 WeBot :
  • 采用 RobotFramwork 來作為測試用例執(zhí)行的基礎(chǔ)
  • Jenkins 來實(shí)際調(diào)配在何處執(zhí)行,并且滿足執(zhí)行計(jì)劃的管理
  • 基于 Django 搭建了一個(gè)簡單的管理界面,用來管理用例和測試報(bào)告,并使得每一個(gè)測試用例可以被作為一個(gè)單元隨意組裝,如果對 Java 很熟悉的同學(xué),這里做一個(gè)近似的類比,這里每一個(gè)用例都可以當(dāng)成一個(gè) SPI 。
  • 另外引入了 Docker 來部署 slave 的環(huán)境,用的很淺,雖然當(dāng)時(shí)餓了么在生產(chǎn)還沒使用 Docker (餓了么生產(chǎn)上的容器化應(yīng)該在 17 年左右)。
 
想想自己當(dāng)時(shí)在測試環(huán)境玩的還是蠻歡樂的,很喜歡折騰。
 
大致的思路如:
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!

測試單元: Bussiness Library 其實(shí)是對 SOA 服務(wù)接口到 RobotFramwork 中的一層封裝,每一個(gè)測試單元可以調(diào)用一個(gè)或多個(gè)接口完成一次原子的業(yè)務(wù)活動(dòng)。

校驗(yàn)組件: 提供了對返回值,或者額外配置對Redis、數(shù)據(jù)庫數(shù)據(jù)的校驗(yàn)。

集成測試: 多個(gè)測試單元串行編排起來就完成了一個(gè)集成測試用例。其中每個(gè)測試單元執(zhí)行后,請求的入?yún)⒑统霾?,在集成測試用例的運(yùn)行域內(nèi)任何地方都是可以獲取到的。

回歸測試: 選取多個(gè)集成測試,可以當(dāng)成一個(gè)方案,配置執(zhí)行。
 
這樣就實(shí)現(xiàn)了多層級不同粒度的復(fù)用。根據(jù)集成測試和回歸測試的方案搭配,后臺會(huì)編譯生成對應(yīng)的  Robot 文件。
 
這個(gè)項(xiàng)目,最后其實(shí)失敗了。最主要的原因,測試開發(fā)的同學(xué)在開發(fā)上能力還不足,而界面上需要比較多的前端開發(fā)工作,一開始我直接套用了 Django 的擴(kuò)展管理界面 xadmin ,進(jìn)行了簡單的擴(kuò)展,然而當(dāng)時(shí)的精力,不允許自己花太多精力在上邊,內(nèi)置的前端組件在體驗(yàn)上有一些硬傷,反而導(dǎo)致效率不高。直到 5 月份,基本放棄了二次開發(fā)。
 
但這次嘗試也帶來了另外的一些成果。我們相當(dāng)于舍棄了使用系統(tǒng)管理用例,而 Jenkins + RobotFramwork 的組合被保留了下來。我們把寫好的一些集成測試用例托管在 Git 上,研發(fā)會(huì)把自己開發(fā)好的分支部署在指定環(huán)境,每天凌晨拉取執(zhí)行,研發(fā)會(huì)在早上根據(jù)自動(dòng)化測試報(bào)告來看最近一次要發(fā)布的內(nèi)容是否有問題。同時(shí),也允許研發(fā)手動(dòng)執(zhí)行,文武和曉東兩位同學(xué)在這塊貢獻(xiàn)了非常多的精力。
 
這個(gè)自動(dòng)化集成回歸的建立,為后續(xù)幾次訂單系統(tǒng)的拆分和小范圍重構(gòu)提供了重要的保障。讓研發(fā)膽子更大,步子能夠邁得更長了。研發(fā)自己會(huì)非常積極的使用這套工具,嘗到了很多顯而易見的甜頭。
 

第二件事情,搭建性能測試。


背景:
記得在 15 年剛剛接觸訂單的時(shí)候,有幸拜訪了還沒來餓了么,但后來成為餓了么全局架構(gòu)負(fù)責(zé)人的 XL 老師,談及如何做好訂單系統(tǒng),重點(diǎn)提及的一點(diǎn),也是壓測。

當(dāng)時(shí)有一些問題和性能、容量有一些關(guān)系,我們沒有什么提前預(yù)知的能力。比如,在我們完成 sharding 前有一次商戶端上線了一次訂單列表改版,因?yàn)槭褂昧爽F(xiàn)有的一個(gè)通用接口(這個(gè)接口粒度很粗,條件組合自由度很強(qiáng)),我們都沒能預(yù)先評估,這個(gè)查詢走了一個(gè)性能極差的索引。當(dāng)時(shí)午高峰接近,一個(gè)幾 k QPS 的查詢接口,從庫突然( 15 年我們的監(jiān)控告警體系還沒有那么完備)就被打垮了,從庫切一個(gè)掛一個(gè),不得不采取接口無差別限流 50% 才緩過來,整個(gè)持續(xù)了接近半個(gè)小時(shí)。最后追溯到近期變更,商戶端回滾了這次變更才真的恢復(fù)。而事后排查,造成此次事故的慢 SQL, QPS 大概幾百左右。
 
整個(gè)公司的性能測試組建,早于我這邊的規(guī)劃,但是當(dāng)時(shí)公司的性能測試是為了 517 外賣節(jié)服務(wù)的,有一波專門的測試同學(xué),這是餓了么第一次造節(jié),這件事的籌備和實(shí)施其實(shí)花了很長時(shí)間。

在壓測的時(shí)候需要不斷的解決問題,重復(fù)再壓測,這件事使得當(dāng)時(shí)很多同學(xué)見到了近鐵城市廣場每一個(gè)小時(shí)的樣子,回憶那段時(shí)光,我記得最晚的一次,大概是 5 月 6 號,我們到樓下已經(jīng)是凌晨 5 點(diǎn)半,我到家的時(shí)候兩旁的路燈剛剛關(guān)。
 
上邊是一點(diǎn)題外話,雖然全鏈路壓測一定會(huì)帶上我們,但是我們也有一些全鏈路壓不到的地方,還有一些接口或邏輯需要單獨(dú)進(jìn)行,需要隨時(shí)進(jìn)行。
 
搭建:
技術(shù)選型上選擇了 Locust ,因?yàn)?Python 的 SOA 框架及其組件,可以帶來極大的便利。此前在做公司級的全鏈路壓測時(shí),是基于 JMeter 的, JMeter 并不是很容易和 Java 的 SOA 框架進(jìn)行集成,需要有一個(gè)前端 HaProxy 來做流量的分流,不能直接使用軟負(fù)載,這在當(dāng)時(shí)造成了一定的不便性。另外一個(gè)原因, Locust 的設(shè)計(jì)理念,可以使一些性能測試的用例更為貼近業(yè)務(wù)實(shí)際場景,只觀測 QPS 指標(biāo),有時(shí)候會(huì)有一些失真。
 
有了全鏈路性能測試團(tuán)隊(duì)在前邊趟坑,其實(shí)我自己性能測試能力的搭建很快就完成了,整個(gè)搭建過程花費(fèi)了 1 個(gè)多月, 8、9 月基本可以對域內(nèi)服務(wù)自行組織性能測試。性能測試人員包括研發(fā)的學(xué)習(xí),需要一點(diǎn)過程。很快,我們這個(gè)小組的性能測試就鋪開到整個(gè)部門內(nèi)使用,包括之后和金融團(tuán)隊(duì)合并之后。
 
這次搭建使得我們在對外提供接口時(shí),對自己服務(wù)負(fù)載和性能上限有一定的預(yù)期,規(guī)避了一些有性能隱患的接口上線,特別是面向商戶端復(fù)雜查詢條件;也能夠模擬高并發(fā)場景,在我們一些重構(gòu)的階段,提前發(fā)現(xiàn)了一些并發(fā)鎖和調(diào)用鏈路依賴問題。
 

第三件事情,隨機(jī)故障演練。


1.0版本:
一開始的雛形其實(shí)很簡單,大致的思路是:
 
1、 在測試環(huán)境單拉出一個(gè)專門的環(huán)境,有單獨(dú)的監(jiān)控和 DB 。
2、構(gòu)造一個(gè) Client ,模擬用戶行為造數(shù)。(我們自動(dòng)化集成測試積累的經(jīng)驗(yàn)就排上用場了。
3、提供了一個(gè)工具來構(gòu)建被依賴服務(wù)的 Mock Server ,解決長鏈路服務(wù)依賴問題。Mock Server 可以根據(jù)輸入返回一些設(shè)定好的輸出。
4、另外,框架團(tuán)隊(duì)幫忙做了一些手腳,發(fā)了一個(gè)特殊版本,使得我們可以對流量打標(biāo)??梢愿鶕?jù) Client 對流量的標(biāo)記,來讓 Mock Server 模擬阻塞、超時(shí)等一些異常行為,反饋到我們的被測 server 上。
 
這是一個(gè)很簡單的雛形,而訂單經(jīng)過我們的幾次治理,對外依賴已經(jīng)很少,所以不到 2、3 天就完全成型。但僅僅是玩具而已,并不具備足夠的參考意義。因?yàn)椴l(fā)沒有做的很高, Mock Server 能夠做的事情也有限。
 
2.0版本:
JN 召集了一些同學(xué),參照 Netflix 的 Choas Monkey 為原型,造了一個(gè)輪子,我們稱之為 Kennel 。
 
控制中心設(shè)計(jì)圖如下:
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
在專項(xiàng)同學(xué)和運(yùn)維同學(xué)的幫助下,Kennel 在 2016 年的 10 月左右初步可用。這個(gè)工具提供了諸如: 模擬網(wǎng)絡(luò)丟包;接口異常注入;摘除集群中的某節(jié)點(diǎn);暴力干掉服務(wù)進(jìn)程等等。
 
這東西大家之前都沒嘗試過,我們也不知道能夠測出什么來,我在11月的時(shí)候想做第一波嘗試,我嘗試制定了 5 個(gè)需要驗(yàn)收的場景:
1、超長分布式事務(wù)
2、某個(gè)接口異常引起整個(gè)服務(wù)雪崩
3、集群中某個(gè)節(jié)點(diǎn)重啟或者機(jī)器重啟,調(diào)用方反應(yīng)明顯
4、集群某個(gè)節(jié)點(diǎn)CPU負(fù)載變高,負(fù)載不均
5、服務(wù)是單點(diǎn)的,集群行為不一致
 
根據(jù)這幾個(gè)場景,在測試同學(xué)中挑選一個(gè)人牽頭實(shí)施。不同服務(wù)的測試報(bào)告略有差異,其中一份的部分截圖如下:

千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
通過對交易主要的幾個(gè)服務(wù)測試一輪之后,我們確實(shí)發(fā)現(xiàn)了一些隱患:
 
  • 一些情況下部署的集群和服務(wù)注冊中心機(jī)器數(shù)量可能不一致,即服務(wù)節(jié)點(diǎn)被暴力干掉后,服務(wù)注冊中心不能主動(dòng)發(fā)現(xiàn)和踢出。這是一個(gè)比較大的隱患。
  • 每個(gè)集群都存在負(fù)載不均的現(xiàn)象,個(gè)別機(jī)器可能 CPU 利用率會(huì)偏高。(和負(fù)載均衡策略有關(guān))
  • 進(jìn)行“毀滅打擊”自恢復(fù)時(shí),某幾個(gè)節(jié)點(diǎn)的 CPU 利用率會(huì)顯著高于其他節(jié)點(diǎn),幾個(gè)小時(shí)之后才會(huì)逐漸均勻。(和負(fù)載均衡策略有關(guān))
  • 單節(jié)點(diǎn) CPU 負(fù)載較高時(shí),負(fù)載均衡不會(huì)將流量路由到其它節(jié)點(diǎn),即使這部分請求性能遠(yuǎn)差于其它節(jié)點(diǎn),甚至出現(xiàn)很多超時(shí)。(和負(fù)載均衡、熔斷的實(shí)現(xiàn)機(jī)制有關(guān),Python 的 SOA 是在服務(wù)端做的熔斷,而客戶端沒有)
  • 大量服務(wù)的超時(shí)設(shè)置配置有誤,框架支持配置軟超時(shí)和硬超時(shí),軟超時(shí)只告警不阻斷,然而默認(rèn)的硬超時(shí)長達(dá) 20s 之久,很多服務(wù)只配置了軟超時(shí)甚至沒有配置,這其實(shí)是一個(gè)低級錯(cuò)誤埋下的嚴(yán)重隱患,可能會(huì)沒法避免一些雪崩。
  • 個(gè)別場景下超時(shí)配置失效,通過對調(diào)用鏈路的埋點(diǎn),以及和框架團(tuán)隊(duì)復(fù)現(xiàn),最后鎖定是一些使用消息隊(duì)列發(fā)送消息的場景,Python 框架是利用了Gevent 來實(shí)現(xiàn)高并發(fā)的支持,框架沒能抓住這個(gè)超時(shí)。
  • ...
 
這個(gè)項(xiàng)目,幾個(gè)道理顯而易見,我們做了很多設(shè)計(jì)和防范,都必須結(jié)合故障演練來進(jìn)行驗(yàn)收,無論是低級錯(cuò)誤還是設(shè)計(jì)不足,能夠一定程度提前發(fā)現(xiàn)。

當(dāng)然我們也造成了一些失誤,一條信心滿滿的補(bǔ)償鏈路(平時(shí)不work),自己攻擊的時(shí)候,它失效了,后來發(fā)現(xiàn)是某次變更埋下的隱患。自己親手造的鍋,含著淚也要往身上背,但我反而更覺得故障演練是更值得去做的,誰能保證真正的故障來臨時(shí),不是一個(gè)更嚴(yán)重的事故。
 
除了系統(tǒng)利好外,人員也拿到了很多收益,比如測試和研發(fā)同學(xué)經(jīng)過這個(gè)項(xiàng)目的實(shí)時(shí),對我們的 trace 和 log 系統(tǒng)在使用上爐火純青,對我們 SOA 框架的運(yùn)作了解也更為透徹,這里的很多隱患和根因,就是測試同學(xué)刨根挖底找到的。 高水準(zhǔn)的 QA 同學(xué)很重要,提升 QA 同學(xué)的水平也同樣重要。
 
當(dāng)然,除了測試團(tuán)隊(duì)的工作外,單元測試我們也沒有落下,在 16 年長時(shí)間保持 80%~90% 的一個(gè)代碼行覆蓋率。

伴隨體量上漲的一系列問題

 

Redis使用的改進(jìn)

 
使用姿勢的治理:
 
2016 年年初主要瓶頸在數(shù)據(jù)庫,在上文其實(shí)已經(jīng)提到了分庫分表的事,可以稍微喘口氣,到了 6 月,大家最擔(dān)憂的,變成了 Redis 。當(dāng)時(shí) Zabbix 只能監(jiān)控到機(jī)器的運(yùn)行情況, Zabbix 其實(shí)也在逐步下線中, SRE 團(tuán)隊(duì)搭建了一套時(shí)效更高的機(jī)器指標(biāo)收集體系,直接讀取了 Linux 的一些數(shù)據(jù),然而,整個(gè) Redis 運(yùn)行情況仍然完全是黑盒。
 
餓了么在 twemproxy 和 codis 上也踩了不少坑, redis-cluster 在業(yè)界還沒被大規(guī)模使用,于是自研了一套 Redis proxy: corvus ,還提供了強(qiáng)大指標(biāo)上報(bào),可以監(jiān)控到 redis 的內(nèi)存、鏈接、 hit 率、key 數(shù)量、傳輸數(shù)據(jù)量等等。正好在這個(gè)時(shí)間點(diǎn)推出,用以取代 twemproxy ,這使得 Redis 的治理迎來轉(zhuǎn)機(jī)。
 
我們配合進(jìn)行了這次遷移,還真是不遷不知道,一遷嚇一跳。
 
當(dāng)時(shí)我們使用 Reids 主要有三個(gè)用途,一是緩存,類似表和接口緯度;二是分布式鎖,部分場景用來防并發(fā)寫;三是餐廳流水號的生成。代碼已經(jīng)是好幾年前的前人寫的。
 
老的使用姿勢,把表級緩存和接口緩存,配置在一個(gè)集群中;其余配置在另外一個(gè)集群,但是在使用上,框架包裝了兩種 Client ,有不同的容錯(cuò)機(jī)制(即是否強(qiáng)依賴或可擊穿)。
 
大家都知道外賣交易有個(gè)特點(diǎn),一筆訂單在短時(shí)間內(nèi),交易階段的推進(jìn)會(huì)更快,因此訂單緩存的更新更頻繁,我們在短暫灰度驗(yàn)證 Redis 集群的可用性之后,就進(jìn)行了全面切換(當(dāng)時(shí)的具體切換方案細(xì)節(jié)記不太清了,現(xiàn)在回想起來其實(shí)可以有更穩(wěn)妥的方案)。
 
參照原緩存的集群是 55G , OPS 準(zhǔn)備了一個(gè) 100G 的集群。在切換后 10min 左右,集群內(nèi)存就占滿了。
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
我們得出一個(gè)驚人的結(jié)論...舊集群的 55G ,之前就一直是超的(巧了,配合我們遷移的OPS也叫超哥)。
 
從監(jiān)控指標(biāo)上看,keys 增長很快而ttl下降也很快,我們很快鎖定了兩個(gè)接口, query_order 和 count_order ,當(dāng)時(shí)這兩個(gè)接口高峰期前者大概是 7k QPS ,后者是10k QPS ,這兩個(gè)接口之前的rt上看一點(diǎn)問題也沒有,平均也就 10ms 。
 
還得從我們的業(yè)務(wù)場景說起,這兩個(gè)接口的主要作用是查詢一段時(shí)間內(nèi)某家餐廳的訂單,為了保證商家能夠盡快的看到新訂單,商戶端是采取了輪詢刷新的機(jī)制,而這個(gè)問題主要出在查詢參數(shù)上。這兩個(gè)接口使用了接口級緩存,所謂的接口級緩存,就是把入?yún)⑸蓚€(gè) Hash 作為 key ,把返回值作為 value , cache 起來, ttl 為秒級,咋一看沒什么問題。如果查詢參數(shù)的時(shí)間戳,截止時(shí)間是當(dāng)天最后一秒的話,確實(shí)是的??吹竭@我相信很多人已經(jīng)猜到,截止時(shí)間戳傳入的其實(shí)是當(dāng)前時(shí)刻,這是一個(gè)滑動(dòng)的時(shí)間,也就引發(fā)了 cache 接近 100% miss 的同時(shí),高頻的塞入了新的數(shù)據(jù)。
 
 (因?yàn)樾屡f集群的內(nèi)存回收策略不一樣,新集群在這種情況下,頻繁 GC 會(huì)引發(fā)性能指標(biāo)抖動(dòng)劇烈)
 
這兩個(gè) cache ,其實(shí)沒任何用處...回滾過了一天后,經(jīng)過灰度,全面去掉了這兩個(gè)接口的 cache ,我們又進(jìn)行了一次切換,順帶將接口級緩存和表級緩存拆分到兩個(gè)集群。

接著,我們又發(fā)現(xiàn)了一些有趣的事情...
 
先來看看,我們業(yè)務(wù)單量峰值的大致曲線,對外賣行業(yè)來說,一天有兩個(gè)峰值,中午和傍晚,中午要顯著高于傍晚。
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
       切換后那天的下午大概 3 點(diǎn)多,內(nèi)存再次爆了... ,內(nèi)存占用曲線近似下圖:
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
緊急擴(kuò)容后,我們一直觀察到了晚上,最后的曲線變成了下圖,從 hit 率上看,也有一定提升(具體數(shù)據(jù)已不可考,在 88%~95% 之間,后來達(dá)到 98% 以上)。
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
為什么和業(yè)務(wù)峰值不太一樣...
 
其實(shí)還是要結(jié)合業(yè)務(wù)來說,很簡單,商戶端當(dāng)時(shí)的輪詢有多個(gè)場景,最長是查詢最近 3 天內(nèi)的訂單,還有一個(gè)頁面單獨(dú)查詢當(dāng)天訂單。
 
后端在輪詢時(shí)查了比前端每頁需要的更多條目,并且,并不是每個(gè)商戶當(dāng)天訂單一開始就是大于一頁的,因此,隨著當(dāng)天時(shí)間的推移,出現(xiàn)了上邊的現(xiàn)象。
 
為什么以前的性能指標(biāo)又沒看出什么問題呢?一是和舊 Redis 集群的內(nèi)存回收策略選取有關(guān),二是 QPS 的量很高,如果只看平均響應(yīng)時(shí)間,差的指標(biāo)被平均了, hit 率也被平均拉高了。
 
嗯,解決了這個(gè)問題之后,又又發(fā)現(xiàn)了新的問題...
 
大概1、2點(diǎn)這個(gè)夜深人靜的時(shí)候,被 oncall 叫起來,監(jiān)控發(fā)現(xiàn)內(nèi)存使用急劇飆升。
 
我們鎖定到一個(gè)調(diào)用量不太正常的接口上,又是 query_order。前段日子,清結(jié)算剛剛改造,就是在這種夜深人靜的時(shí)候跑賬,當(dāng)時(shí)我們的賬期比較長(這個(gè)是由于訂單可退天數(shù)的問題,下文還有地方會(huì)展開),這時(shí)候會(huì)拉取大量歷史訂單,導(dǎo)致占用了大量內(nèi)存,而我們的表級緩存時(shí)效是 12h ,如果不做清理,對早高峰可能會(huì)產(chǎn)生一定的影響。后來我們次日就提供了一個(gè)不走緩存的接口,單獨(dú)給到清結(jié)算。
 
這里核心的問題在于, 我們服務(wù)化也就不到 1 年的時(shí)間,服務(wù)的治理還不能做到很精細(xì),服務(wù)開放出去的接口,暴露在內(nèi)網(wǎng)中,誰都可以來調(diào)用,我們的接口協(xié)議也是公開的,任何人都很容易知道查閱到接口,并且,在公司的老人路子都比較野(不需要對接,有啥要啥,沒有就自己加)。Git 倉庫代碼合并權(quán)限和發(fā)布權(quán)限早在 15 年底就回收管控了,但那一刻 SOA 化還未完全,接口授權(quán)直到很后邊才支持。
 
Redis 的使用還是需要建立在深刻理解業(yè)務(wù)場景基礎(chǔ)上,并且關(guān)注各類指標(biāo)。
 
緩存機(jī)制的改進(jìn) 
我們當(dāng)時(shí)的緩存機(jī)制是這樣的:
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
這個(gè)架構(gòu)設(shè)計(jì)的優(yōu)點(diǎn):
1、有一條獨(dú)立的鏈路來做緩存的更新,對原有服務(wù)入侵性較小
2、組件可復(fù)用性較高
3、有 MQ 削峰,同時(shí)還有一級 Redis,做了聚合,進(jìn)一步減小并發(fā)
 
在很多場景,是一套蠻優(yōu)秀的架構(gòu)。
 
缺點(diǎn): 
1、用到了兩級隊(duì)列,鏈路較長
2、實(shí)時(shí)性較差
 
驅(qū)動(dòng)我們改造的原因,也源自一次小事故。

商戶訂單列表的查詢其實(shí)根據(jù)的是訂單狀態(tài)來查,獲取到的訂單應(yīng)當(dāng)是支付好了的。然而有一部分錯(cuò)誤的判斷邏輯,放在了當(dāng)時(shí)商戶端接單后端,這個(gè)邏輯會(huì)判斷訂單上的流水號是否是0(默認(rèn)值),如果是0推斷出訂單還未支付,就將訂單過濾掉。
 
在那次事故中,緩存更新組件跪了(并且沒有人知道...雖然這個(gè)架構(gòu)是框架的某些同學(xué)早期設(shè)計(jì)的,但太穩(wěn)定了以至于都被遺忘...)。由于緩存更新的不夠及時(shí),拿到了過時(shí)的數(shù)據(jù),表象就是,商戶看不到部分新訂單,看到的時(shí)候,已經(jīng)被超時(shí)未接單自動(dòng)取消的邏輯取消了,真是精彩的組合...
 
后邊改造成下邊的樣子:
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
相比起來,這個(gè)架構(gòu)鏈路就減少了很多,而且實(shí)時(shí)性得到了保障。但是為了不阻塞流程,進(jìn)行了一定的容錯(cuò),這就必須增加一條監(jiān)控補(bǔ)償鏈路。這次改進(jìn)之后,我們立馬去除了對 ZeroMQ 在代碼和配置上的依賴。
 

消息使用的改進(jìn)

 
分庫分表做完后,我們對 MQ 沒有什么信心,在接下來的幾個(gè)月,MQ 接連出了幾次異常...真的是墨菲定律,遺憾的是我們只是感覺它要出事情而不知道它哪里會(huì)出事情。
 
錯(cuò)誤的姿勢
在之前的章節(jié),我提到過曾經(jīng)搭建了一套訂單消息廣播機(jī)制,基于這套消息為契機(jī),商戶端針對高頻輪詢做了一個(gè)技術(shù)優(yōu)化,希望通過長連接,推拉結(jié)合,減小輪詢的壓力。簡單介紹一下這套方案,商戶端有一個(gè)后端服務(wù),接收訂單的消息廣播,如果有新訂單(即剛剛扭轉(zhuǎn)到完成支付商家可見的訂單),會(huì)通過與端上的長連接推送觸達(dá)到端上,接著端上會(huì)觸發(fā)一次主動(dòng)刷新,并發(fā)出觸達(dá)聲音提醒商戶。原先的輪詢則增加時(shí)間間隔,降低頻次。
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
那么問題在哪? 有部分時(shí)候,藍(lán)色這條線,整體花費(fèi)的時(shí)間居然比紅色這條線更少,也就是說,一部分比例的請求兜到外網(wǎng)溜一圈比內(nèi)網(wǎng)數(shù)據(jù)庫的主從同步還快。

商戶端提出要輪主庫,禽獸啊,顯然,這個(gè)頻次,想是不用想的,不可能答應(yīng),畢竟之前輪詢從庫還打掛過。由消費(fèi)者在本地 hold 一段時(shí)間再消費(fèi),也不太友好。畢竟有時(shí)候,快不一定是好事情,那么我們能不能讓它慢一點(diǎn)出來?
 
于是,binding 的拓?fù)浔晃覀兏某闪诉@樣,前段粉紅的這個(gè) Queue ,使用了 RabbitMQ 死進(jìn)隊(duì)列的特性(即消息設(shè)置一個(gè)過期時(shí)間,等過期時(shí)間到了就可以從隊(duì)列中舍棄或挪到另外的地方):
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
眼前的問題解決了,但也埋了坑,對 RabbitMQ 和架構(gòu)設(shè)計(jì)稍有經(jīng)驗(yàn)的同學(xué),應(yīng)該很快意識到這里犯了什么錯(cuò)誤。binding 關(guān)系這類 Meta 信息每一個(gè) Broker 都會(huì)存儲,用于路由。然而,消息的持久化卻是在 Queue 中,而 queue 只會(huì)存在一個(gè)節(jié)點(diǎn),本來是集群,在這個(gè)時(shí)候,拓?fù)渲锌壳暗囊徊糠肿兂闪藛吸c(diǎn)。
 
回到我一開始提到的 MQ 集群事故,因?yàn)橐恍┰驙窟B,我們這個(gè) MQ 集群某些節(jié)點(diǎn)跪了,很不幸,包含這個(gè)粉紅粉紅的 Queue 。于此同時(shí),暴露了另外一個(gè)問題,這個(gè)拓?fù)浣Y(jié)構(gòu),不能自動(dòng)化運(yùn)維,得依靠一定的人工維護(hù),重建新的節(jié)點(diǎn), meta 信息需要從舊節(jié)點(diǎn)導(dǎo)出導(dǎo)入,但是會(huì)產(chǎn)生一定的沖突。并且,早期我們的 Topic 和 Queue 的聲明沒有什么經(jīng)驗(yàn),沒有根據(jù)消費(fèi)者實(shí)際的消費(fèi)情況來分配 Queue ,使得部分節(jié)點(diǎn)過熱。權(quán)衡自動(dòng)運(yùn)維和相對的均衡之下,后邊的做法,實(shí)際是隨機(jī)選擇了一個(gè)節(jié)點(diǎn)來聲明 Queue 。

之后我們做了兩個(gè)改進(jìn),一是拓?fù)浣Y(jié)構(gòu)支持在服務(wù)的配置文件中聲明,隨服務(wù)啟動(dòng)時(shí)自動(dòng)到 MQ 中聲明;二是由商戶端后端服務(wù),接到新單消息來輪詢時(shí),對新單by單單獨(dú)請求一次(有 cache,如果 miss 會(huì)路由到主庫)。
 
于是,消息的拓?fù)浣Y(jié)構(gòu)變成了下邊這樣:
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
消息集群拆分
仍然是上邊這個(gè)故事的上下文,我們回到影響這次事故的原因。根據(jù)我們對 RabbitMQ 集群的性能測試,這個(gè)吞吐應(yīng)該能夠承受,然而 CPU 負(fù)載非常的高,還影響了生產(chǎn)者發(fā)送消息(觸發(fā)了 RabbitMQ 的自保護(hù)機(jī)制),甚至掛掉。
 
經(jīng)過架構(gòu)師的努力下,最后追溯到,這次事故的原因,在于商戶端使用的公共 SOA 框架中,消息隊(duì)列的客戶端,是部門自己獨(dú)立封裝的,這個(gè)客戶端,沒有很好理解 RabbitMQ 的一些 Client 參數(shù)(例如 get 和 fetch 模式, fetch 下的 prefetch_count參數(shù)等),其實(shí)這個(gè)參數(shù)需要一定的計(jì)算才能得到合理值,否則,即使機(jī)器還有 CPU 可用,消費(fèi)能力也上不去。

和訂單的關(guān)系又是什么?答案是 混布。這個(gè)集群通過 vhost 將不同業(yè)務(wù)的消息廣播隔開,因此上邊部署了訂單、運(yùn)單、商戶端轉(zhuǎn)接的消息等。
 
在事故發(fā)生當(dāng)天,運(yùn)營技術(shù)部老大一聲令下,無論怎么騰挪機(jī)器,當(dāng)天都必須搭建出一個(gè)獨(dú)立消息廣播集群給到訂單,運(yùn)營技術(shù)部和我們,聯(lián)合所有的消費(fèi)方,當(dāng)天晚上,即搭建了一個(gè)7節(jié)點(diǎn)的集群,將訂單的消息廣播從中單獨(dú)拆出來。
 
(一年后,這個(gè)集群也到了瓶頸,而且無法通過擴(kuò)容解決,主要原因,一是消費(fèi)方?jīng)]有使用RabbitMQ的特性來監(jiān)聽消息,而是本地過濾,導(dǎo)致白白耗費(fèi)一部分處理資源;二是隨著集群規(guī)模的上升,連接數(shù)達(dá)到了瓶頸。后者我們在生產(chǎn)者額外發(fā)了一份消息到新搭建的一個(gè)集群,得到了一定的緩解。真正解決,還是在餓了么在 RabbitMQ 栽了這么多跟頭,使用 Go 自研的 MaxQ 取代 RabbitMQ 之后)。
 
PS: 如果時(shí)光倒流,當(dāng)初的改進(jìn)項(xiàng)里,會(huì)提前加一個(gè)第三點(diǎn),針對使用`*`這個(gè)通配符來訂閱消息的,都要求訂閱方根據(jù)真實(shí)需要更改。這里腐化的原因,主要還是把控和治理的力度不夠,標(biāo)準(zhǔn)和最佳實(shí)踐建議在最初的說明文檔就有,后續(xù)也提供了一些可供調(diào)整參數(shù)的計(jì)算公式,不能完全指望所有消費(fèi)者都是老實(shí)人,也不完全由技術(shù)運(yùn)營來把控,服務(wù)提供方是需要。
 

虛擬商品交易以及創(chuàng)新

 

早餐:

2015 年下旬到 2016 年上旬,餓了么的早餐業(yè)務(wù),雖然單量占比不高,但對當(dāng)時(shí)技術(shù)架構(gòu)沖擊感,是比較大的。
 
一開始外賣和早餐的交互是這樣的:

千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
我猜這時(shí)候,一定會(huì)有小朋友有一堆問號...
我解釋一下背景:
1、早餐獨(dú)立于餐飲完全搭建了一套新的體系(用戶、店鋪、訂單、配送等等)。
2、因?yàn)橹Ц稕]法獨(dú)立搞,而支付在2016年初之前,是耦合在用戶系統(tǒng)里的,并且,這套支付就是純粹為外賣定制的。
 
于是,作為「創(chuàng)新」部門的「創(chuàng)新業(yè)務(wù)」,為了快速試錯(cuò),完全自己搭建了一套完整的電商雛形,而為了使用支付,硬湊著“借”用了外賣的交易鏈路。這個(gè)方案是早餐的研發(fā)同學(xué)和支付的研發(fā)同學(xué)確定并實(shí)施的,訂單無感知的當(dāng)了一把工具人。
 
當(dāng)初我知道的時(shí)候,就已經(jīng)長這樣了。我是什么時(shí)候知道的,出鍋的時(shí)候,很真實(shí)。當(dāng)時(shí) PPE 和 PROD 沒有完全隔離,一次錯(cuò)誤的操作導(dǎo)致 PROD 的異步任務(wù)被拉取到 PPE ,再經(jīng)過一次轉(zhuǎn)移,最后沒有 worker 消費(fèi)導(dǎo)致訂單被取消。
 

餓配送會(huì)員卡

在 2016 年初,業(yè)務(wù)方提過來一個(gè)需求,希望餓了么配送會(huì)員卡的售賣能夠線上化,此前是做了實(shí)體卡依靠騎手線下推銷的方式。正好,經(jīng)過之前的架構(gòu)評審,我們也需要一個(gè)流量較小的業(yè)務(wù)模式,來實(shí)踐我們新的架構(gòu)設(shè)想,于是,就有了我們這套虛擬商品售賣的訂單系統(tǒng)。
 
我們抽象了一套最簡單的狀態(tài)模型:
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
最核心的觀點(diǎn):
 1、天下所有的交易,萬變不離其宗,主要的節(jié)點(diǎn)是較為穩(wěn)定的。
2、C 端購買行為較為簡單,而 B 端的交付則可能千變?nèi)f化。
3、越是核心的系統(tǒng),越應(yīng)該保持簡單。
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
上下游交互如上,商品的管理、營銷、導(dǎo)購等,都交給業(yè)務(wù)團(tuán)隊(duì)自己,交易系統(tǒng)最核心的職責(zé)是提供一條通路和承載交易的數(shù)據(jù)。
 
在數(shù)據(jù)上的設(shè)計(jì),買賣雙方、標(biāo)的物、進(jìn)行階段,這三個(gè)是當(dāng)時(shí)我們認(rèn)為較為必要的,當(dāng)然,現(xiàn)在我可以給出更為標(biāo)準(zhǔn)的模型,但是,當(dāng)時(shí),我們真沒想那么多。
 
 所以,交易主表拆成了兩。

 一張基礎(chǔ)表,包含主要買方ID、買方ID、狀態(tài)碼、業(yè)務(wù)類型、支付金額。業(yè)務(wù)類型是用來區(qū)分不同買賣方體系的。

另一張成為擴(kuò)展表,包含標(biāo)的物列表、營銷信息列表、收貨手機(jī)號等等,屬于明細(xì),允許業(yè)務(wù)方有一定的自由空間。
 
(PS: 事后來看,標(biāo)的物、營銷信息等等,雖然是可供上游自己把控的,但是需要對范式從代碼層面進(jìn)行約束,否則治理會(huì)比較麻煩,業(yè)務(wù)方真是什么都敢塞...)
 
拆兩張表,背后的原因,一是訂單一旦生成,快照的職責(zé)就幾乎完成了,剩下最關(guān)鍵的是狀態(tài)維護(hù),高頻操作也集中在狀態(tài)上,那么讓每條記錄足夠的小有助于保障核心流程;二是參照餐飲訂單的經(jīng)驗(yàn), 2/3 的存儲空間是用在了明細(xì)上,特別是幾個(gè) Json 字段。
 
整個(gè)虛擬訂單系統(tǒng)搭建好之后,很多平臺售賣性質(zhì)的業(yè)務(wù)都通過這套系統(tǒng)接入,對我們自身來說,接入成本開發(fā)+測試只需要 2~3 天以內(nèi),而整個(gè)業(yè)務(wù)上線一般一個(gè)星期以內(nèi)就可以,我們很開心,前臺業(yè)務(wù)團(tuán)隊(duì)也很開心。因?yàn)闆]有大規(guī)模查詢的場景,很長一段時(shí)間,穩(wěn)定支持每日幾十萬的成單,幾十核的資源綽綽有余。
 
這其實(shí)是一個(gè)簡單的平臺化系統(tǒng)的雛形了。
 

其它

圍繞交易,我們其實(shí)還衍生出一些業(yè)務(wù),廣義上,當(dāng)時(shí)是訂單團(tuán)隊(duì)來負(fù)責(zé),也是組織架構(gòu)影響導(dǎo)致,
 
例如「準(zhǔn)時(shí)達(dá)」這個(gè)IP,技術(shù)側(cè)是我團(tuán)隊(duì)主own從無到有實(shí)現(xiàn)的,同時(shí)又衍生出一塊 「交易賠付中心」,用來收口一筆交易過程中所有的賠付(包括紅包、代金券、現(xiàn)金、積分等),;
 
為了提升用戶交易體驗(yàn),我們發(fā)起了一個(gè)「交易觸達(dá)中心」(后演化為公司通用的觸達(dá)中心),收口了交易過程中對用戶的短信、push、電話等等觸達(dá)方式,特別是提升了極端case的觸達(dá)率,同時(shí),減少對用戶的反復(fù)騷擾。
 

服務(wù)和業(yè)務(wù)治理

 
上邊說的大都是一些技術(shù)細(xì)節(jié)上的提升,下邊兩件事,則是應(yīng)用架構(gòu)上的重大演化,也奠定了之后應(yīng)用架構(gòu)的走向。
 

逆向中的售中和售后

2016  年中旬,業(yè)務(wù)背景,為了提升用戶在不滿場景下的體驗(yàn)(在我們的白板上密密麻麻貼了幾十個(gè)case),同時(shí)為了縮短結(jié)算賬期(因?yàn)槟嫦蛴行r(shí)間長達(dá)七天,結(jié)算強(qiáng)依賴了這個(gè)時(shí)間)。
 
在 JN 的發(fā)起下,我們從原來的訂單中,單獨(dú)把逆向拆出來,并且將原來的訂單組拆分成兩個(gè)團(tuán)隊(duì),我推薦了其中一位同學(xué)成為新團(tuán)隊(duì)的 Team Leader 。
 
對于正向來說,最核心的職責(zé)是保障交易的順暢,因此它重點(diǎn)追求的是高性能、高并發(fā)和穩(wěn)定性,越是清晰簡單越好,主次清楚,依賴干凈,越容易快速定位問題,快速恢復(fù)。
 
逆向的并發(fā)遠(yuǎn)小于正向,只有 1% 的訂單才會(huì)需要走到逆向,然而,業(yè)務(wù)邏輯的分支和層次關(guān)系復(fù)雜度,則遠(yuǎn)大于正向,需要更強(qiáng)的業(yè)務(wù)抽象。雖然穩(wěn)定和性能對逆向同樣很重要,但是相對沒那么高。
 
因?yàn)楹诵膯栴}域不同,服務(wù)要求級別不同,拆分是順理成章的事情。
 
實(shí)際拆分過程,還是蠻痛苦的,大家都是在探索,我和逆向組,包括和老板,我們口水戰(zhàn)打了無數(shù)次。
 
當(dāng)時(shí)的最終形態(tài)如下(也還是有問題的,在后邊的幾年我負(fù)責(zé)逆向后,把售中和售后合并了):

千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
第一步,是增加一個(gè)訂單狀態(tài),用以表示訂單完成(約等于收貨,因?yàn)槭肇浐笠话懔ⅠR就完成了,但二者概念上還是有一些差別)。光增加這個(gè)狀態(tài),推動(dòng)上下游,包括APP的升級,花費(fèi)了近3個(gè)月。

第二步,搭建一套退單,訂單完成狀態(tài)灰度完成后,以這個(gè)狀態(tài)作為訂單生命周期的完結(jié)點(diǎn),后續(xù)由退單負(fù)責(zé)。這樣清結(jié)算的入賬和扣款也就相互獨(dú)立了。

第三步,將訂單中涉及到售中的邏輯也一并切流到售中服務(wù)。(關(guān)于售中、售后的演化,后邊還有機(jī)會(huì)再展開)
 
我們當(dāng)時(shí)踏入的其中一個(gè)坑,是沒有把狀態(tài)和上層事件剝離的比較干凈,最終體現(xiàn)在業(yè)務(wù)邊界和分布式事務(wù)上有很多問題。
 
后來吵過幾次之后,訂單系統(tǒng)的主干邏輯其實(shí)已經(jīng)被剝離的比較簡單了,主要工作就是定義了狀態(tài)之間的關(guān)系,比如 A->C,B->C,A->B,這里的A、B、C和能否扭轉(zhuǎn)都是訂單定義的,這層的業(yè)務(wù)含義很輕,重點(diǎn)在 *->C 我們認(rèn)為是一個(gè)場景,上層來負(fù)責(zé)。

舉個(gè)例子, C 這個(gè)狀態(tài)是訂單無效,除開完結(jié)狀態(tài)的訂單,任何狀態(tài)都有一定條件可變到無效,滿足什么樣的條件是由業(yè)務(wù)形態(tài)決定,適合放在售中服務(wù)中,他來決定要不要觸發(fā)訂單去扭轉(zhuǎn)狀態(tài)。類似的還有訂單收貨。
 
這個(gè)時(shí)候已經(jīng)有了狀態(tài)機(jī)的神在(重構(gòu)成狀態(tài)機(jī)的實(shí)現(xiàn)方式,放到17年初再說)
 
特別要說明的是紅色的那條線,確實(shí)是這種時(shí)效要求較高的交易場景下一個(gè)折中的設(shè)計(jì),這條線最主要的任務(wù),純粹就是打標(biāo),在訂單上打一個(gè)標(biāo)表示是否有售后。我們參考了當(dāng)時(shí)的電商(淘寶、京東),從端上的頁面就完成垂直拆開,對系統(tǒng)設(shè)計(jì)來說,要簡單的多,而我們沒辦法這么做,這個(gè)是由業(yè)務(wù)形態(tài)決定的,商家在極短時(shí)間內(nèi)要完成接單,同時(shí)還要時(shí)刻關(guān)注異常case,很多頁面在權(quán)衡下,要照顧用戶體驗(yàn)。也就是說,雖然系統(tǒng)拆開了,但是在最上層的業(yè)務(wù)仍然不能拆開,甚至,內(nèi)部也有很多聲音,我們只是希望退款,為什么要我識別、區(qū)分并對接兩套系統(tǒng)。因此,一部分?jǐn)?shù)據(jù)是回寫到了訂單上。
 
在這個(gè)階段,最受用的兩句話:
 
1、對事不對人: 無論怎么吵,大家都是想把事情做的更好,底線是不要上升到人;(沒有什么是一杯下午茶解決不了的)。
2、堅(jiān)持讓一件事情變成更有益的: 誰也不是圣賢,無論當(dāng)初的決定是什么,沒有絕對的說服對方,拍板后就執(zhí)行,發(fā)現(xiàn)問題就解決,而不是抱怨之前的決策。(與之相對的是,及時(shí)止損,二者并不沖突,但同樣需要決斷)。
 

物流對接

8月初計(jì)劃把 MQ 業(yè)務(wù)邏輯交接給我,因?yàn)樵O(shè)計(jì)理念不同,語言棧也不同,第一件事情便是著手重構(gòu)。
 
在這里先談?wù)剝蓚€(gè)“過時(shí)的”架構(gòu)設(shè)計(jì)。
 
ToC & ToB & ToD:


在2016年初,有一個(gè)老的名詞,現(xiàn)在絕大部分人都不知道的東西: BOD。

這是早起餓了么自配送的形態(tài),這套業(yè)務(wù)體現(xiàn),把訂單、店鋪、配送、結(jié)算等在業(yè)務(wù)上全耦合在一團(tuán)。餓了么自己的大物流體系從 2015 年中旬開始搭建,到了這個(gè)時(shí)間,順應(yīng)著要做一個(gè)大工程, BOD 解耦。
 
這次解耦,誕生了服務(wù)包、ToB單、ToD單。
 
稍稍解釋一下業(yè)務(wù)背景,那時(shí)候的訴求,平臺將一些服務(wù)打包售賣給商戶,和商戶簽約,這里售賣的服務(wù)中就包括了配送服務(wù)。那么,商戶使用配送與否,就影響到了商戶的傭金和應(yīng)收,然而,這個(gè)行業(yè)的特色創(chuàng)新,就是在商戶接單的時(shí)候,告訴商戶,交易完成,你確切能夠收入的錢是多少,相當(dāng)于預(yù)先讓商戶看到一個(gè)大概率正確(不考慮售中的異常)的賬單,還得告訴商家,最終以賬單為準(zhǔn)。
 
這其實(shí)是分賬和分潤的一些邏輯,就把清結(jié)算域的業(yè)務(wù)引入到交易鏈路上,清結(jié)算是常年做非實(shí)時(shí)業(yè)務(wù)的,那么計(jì)算商戶預(yù)計(jì)收入這件事,撕了幾天之后,自然就落到到了訂單團(tuán)隊(duì)上。另外一個(gè)背景,當(dāng)時(shí)有很多攜程系過來的同學(xué),攜程的業(yè)務(wù)形態(tài)是用戶向平臺下單,平臺再到供應(yīng)商去下單,于是,ToC、ToB、ToD的概念,就這么被引入了。
 
我接到的任務(wù),就是要做一套 ToB 單。當(dāng)時(shí)覺得這個(gè)形態(tài)不對,餓了么的交易和攜程的交易是不一樣的。我向主管表示反對這個(gè)方案,但是,畢竟畢業(yè)半年沒多少沉淀,我拿不出來多少清晰有力的理由,也有一些其他人掙扎過,總之,3月初正式上線灰度。
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
這個(gè)圖可以看出來幾個(gè)顯而易見的問題:
1、交易被拆成了幾段,而用戶、商戶實(shí)際都需要感知到每一段。并且每個(gè)階段對時(shí)效、一致性都有一定的要求。
2、平臺和物流只通過紅色的先來交互,這個(gè)通道很重
3、公式線下同步...

ToD
 上邊的架構(gòu)實(shí)施后,到了 7 月份,ToD 這部分,變成了平臺和物流唯一的通道,太重了,業(yè)務(wù)還沒發(fā)展到那個(gè)階段,弊大于利。商戶端配送組的同學(xué)不開心,物流的同學(xué)不開心,訂單的同學(xué)也不開心。
 
正好,訂單在做增加完結(jié)狀態(tài)這個(gè)事。我們認(rèn)為,訂單需要管控的生命周期,應(yīng)該延伸到配送,并且配送屬于子生命周期,是交易的一部分。于是,7 月底, ToD 也交給了我,又到了喜聞樂見的重構(gòu)環(huán)節(jié)。
 
作為商戶端技術(shù)體系的外部人員來看,當(dāng)時(shí) ToD 的設(shè)計(jì)非常的反人類。
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
我們真正接手的時(shí)候發(fā)現(xiàn),當(dāng)時(shí)商戶端的應(yīng)用架構(gòu)大概是這樣的:

            千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!

有這么一個(gè)基礎(chǔ)設(shè)施公共層,這一層封裝了對 DB、Redis 等公共操作。也就是說,同一個(gè)領(lǐng)域的業(yè)務(wù)邏輯和數(shù)據(jù),是根據(jù)這個(gè)體系的分層原則分在了不同層級的服務(wù)中,一個(gè)域內(nèi)的業(yè)務(wù)層要操作它自己的數(shù)據(jù),也需要通過接口進(jìn)行。它可能有一定道理在(包括 2020 年我在面試一些候選人的時(shí)候發(fā)現(xiàn),也有一些公司是這種做法),但是,交接出來的時(shí)候,痛苦!復(fù)雜的耦合,相當(dāng)于要從一個(gè)錯(cuò)綜復(fù)雜的體系里剝出一條比較干凈獨(dú)立的線。

那后來,我們改成下邊的樣子:
 
千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
1、ToB 和 ToD 被合并成為了一層,放在了 osc.blink 這個(gè)服務(wù)里,并且消滅這兩個(gè)概念,作為訂單的擴(kuò)展數(shù)據(jù),而不是從交易中切出來的一段。
2、平臺和物流如果有數(shù)據(jù)交互,不一定需要通過這個(gè)對接層,這條鏈路最好只承載實(shí)時(shí)鏈路上配送所必須的數(shù)據(jù)。物流 Apollo 可以自己到平臺其它地方取其需要的數(shù)據(jù)。(這里其實(shí)有一些問題沒解,osc.blink 和 Apollo 在兩方的定位并不完全一致,Apollo 作為運(yùn)單中心收攏了和平臺對接的所有數(shù)據(jù))
3、節(jié)點(diǎn)與節(jié)點(diǎn)之間的交互盡可能簡單,節(jié)點(diǎn)自身保證自身的健壯性。原先推單是通過消息進(jìn)行,現(xiàn)在改成了 RPC 進(jìn)行,推的一方可以主動(dòng)重推(有一個(gè)憑證保證冪等),拉的一方有補(bǔ)償拉取鏈路。

 (圖示的3.1,是由于當(dāng)時(shí)外賣平臺和物流平臺,機(jī)房部署在不同城市,多次跨機(jī)房請求影響巨大,所以鏈路上由這個(gè)服務(wù)進(jìn)行了一次封裝)。

到了8月底,呼單部分就完成上線。9月份開始把數(shù)據(jù)進(jìn)行重構(gòu)。
 
 

小結(jié)


 

到了 2016 年底,我們的交易體系整體長這樣:


千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!
 
當(dāng)時(shí)一些好的習(xí)慣和意識,挺重要:
 
1、理清權(quán)力和職責(zé):代碼倉庫權(quán)限的回收,發(fā)布權(quán)限的回收,數(shù)據(jù)庫和消息隊(duì)列連接串管控等等。

2、保持潔癖:
a. 及時(shí)清理無用邏輯(例如,我每隔一兩個(gè)月就會(huì)組織清理一批沒有流量的接口,也會(huì)對流量增長不正常的接口排查,下游有時(shí)候會(huì)怎么方便怎么來).
b. 及時(shí)清理無用的配置,不用了立馬干掉,否則交接幾次之后估計(jì)就沒人敢動(dòng)了.
c. 及時(shí)治理異常和解決錯(cuò)誤日志,這將大大的減小你告警的噪音和排查問題的干擾項(xiàng)。

3、理想追求極致但要腳踏實(shí)地。

4、堅(jiān)持測試的標(biāo)準(zhǔn)和執(zhí)行的機(jī)制。
a. 堅(jiān)持自動(dòng)化建設(shè)
b. 堅(jiān)持性能測試
c. 堅(jiān)持故障演練

5、不斷的請教、交流和思維沖撞。

6、Keep Simple, Keep Easy.

7、對事不對人。
 
架構(gòu)的演進(jìn),最好是被業(yè)務(wù)驅(qū)動(dòng),有所前瞻,而不是事故驅(qū)動(dòng)?;剡^頭發(fā)現(xiàn),我們有一半的演進(jìn),其實(shí)是伴隨在事故之后的。值得慶幸的是,那個(gè)時(shí)候技術(shù)可自由支配的時(shí)間更多一些。
 
如果你閱讀到這里,有很多共鳴和感觸,但是又說不出來,那么你確實(shí)把自己的經(jīng)歷整理出一些腦圖了。
 
在實(shí)習(xí)的半年,每個(gè)月都會(huì)感覺日新月異,在畢業(yè)的最初 1 年半里,總覺得 3 個(gè)月前的自己弱爆了,最初的這 2 年,是我在餓了么所經(jīng)歷的最為寶貴的時(shí)間之一。
 
上篇內(nèi)容就到這里,如果有所收獲,可以關(guān)注公眾號,等待下篇的內(nèi)容。

作者信息:
楊凡,花名挽晴,餓了么高級架構(gòu)師,2014 年加入餓了么,2018 年隨餓了么被阿里巴巴收購一同加入阿里巴巴,4 年團(tuán)隊(duì)管理經(jīng)驗(yàn),4 年主要從事餓了么交易系統(tǒng)建設(shè),也曾負(fù)責(zé)過餓了么賬號、評價(jià)、IM、履約交付等系統(tǒng)。

特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:

千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!

長按訂閱更多精彩▼

千萬級餓了么交易系統(tǒng)架構(gòu) 5 年演化史!

如有收獲,點(diǎn)個(gè)在看,誠摯感謝

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動(dòng)電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動(dòng)電源

在工業(yè)自動(dòng)化蓬勃發(fā)展的當(dāng)下,工業(yè)電機(jī)作為核心動(dòng)力設(shè)備,其驅(qū)動(dòng)電源的性能直接關(guān)系到整個(gè)系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動(dòng)勢抑制與過流保護(hù)是驅(qū)動(dòng)電源設(shè)計(jì)中至關(guān)重要的兩個(gè)環(huán)節(jié),集成化方案的設(shè)計(jì)成為提升電機(jī)驅(qū)動(dòng)性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機(jī) 驅(qū)動(dòng)電源

LED 驅(qū)動(dòng)電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個(gè)照明設(shè)備的使用壽命。然而,在實(shí)際應(yīng)用中,LED 驅(qū)動(dòng)電源易損壞的問題卻十分常見,不僅增加了維護(hù)成本,還影響了用戶體驗(yàn)。要解決這一問題,需從設(shè)計(jì)、生...

關(guān)鍵字: 驅(qū)動(dòng)電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動(dòng)電源的公式,電感內(nèi)電流波動(dòng)大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計(jì) 驅(qū)動(dòng)電源

電動(dòng)汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動(dòng)汽車的核心技術(shù)之一是電機(jī)驅(qū)動(dòng)控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機(jī)驅(qū)動(dòng)系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動(dòng)汽車的動(dòng)力性能和...

關(guān)鍵字: 電動(dòng)汽車 新能源 驅(qū)動(dòng)電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進(jìn)步,高亮度白光發(fā)光二極管(LED)因其獨(dú)特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動(dòng)電源 LED

LED通用照明設(shè)計(jì)工程師會(huì)遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動(dòng)電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動(dòng)電源的電磁干擾(EMI)問題成為了一個(gè)不可忽視的挑戰(zhàn)。電磁干擾不僅會(huì)影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動(dòng)電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機(jī)重量也有所下降,所以,現(xiàn)在的LED驅(qū)動(dòng)電源

關(guān)鍵字: LED 驅(qū)動(dòng)電源 開關(guān)電源

LED驅(qū)動(dòng)電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動(dòng)LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動(dòng)電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動(dòng)電源
關(guān)閉