天天看點

餓了麼技術往事(中)

餓了麼技術往事(中)

在上一篇文章《餓了麼技術往事(上)》中,我介紹了餓了麼最早期 All in One 階段的架構,以及第二階段業務系統拆分與團隊營運的一些思考,以及我對于架構師職責的感受,接下來我會詳細介紹餓了麼全面服務化的架構演進曆程。

一、中間件

業務線的工程師深陷到快速的疊代和業務複雜性當中,業務的快速增長、外賣行業午晚高峰業務特點帶來的并發挑戰,領域拆分後所需的服務體系架構支撐,責任自然落到了中間件團隊。

當時中間件團隊主要負責的三件事就是釋出系統、SOA架構、統一的資料通路層。

1. 釋出系統

外賣業務周末的單量通常比工作日要高,但是工作日事故率要高于周末,為什麼?變更是萬惡之源,周末很少釋出。是以,釋出系統接手管控,取消手動釋出的模式,解決釋出復原的問題,通過釋出自動化提高效率的同時,回收伺服器的權限,降低安全和穩定性的隐患。當然釋出系統的作用遠不止于此,後續這個體系及其團隊充當起了基礎架構演進的核心角色。這個是後話了。

2. SOA 架構

SOA架構是支撐業務服務的骨架。和多數類似架構一樣,為應對複雜的服務體系,服務注冊和發現,常見的基于Design for failure的設計,熔斷、限流、艙壁、多叢集隔離這些功能都一樣。但是,較特殊的地方在于——我們有兩套SOA架構,Java 版和 Python 版。前面提到,我們有兩個主要的技術棧 —— Java 和 Python,使得我們凡是需要 SDK 的地方,都需要支援兩種語言,毫無疑問會對增加中間件團隊負擔。在當時确實是個難題,這個現在當然也有解,後面會提到。

體會和教訓——是否應該統一技術棧?

關于是否應該統一技術棧,沒有一個标準的答案。每個公司的技術棧和技術體系,有其形成的背景,如同架構一樣,不放在上下文裡面讨論合理性,往往沒有結果,煙囪型也好、L型也好,隻要是适合自己的技術和架構就好。

Python 技術棧當時已經支撐了很多核心系統,推翻現有系統,換技術棧的時間成本不可忽視。而當時市場競争非常激烈,對于餓了麼這樣的創業公司,資料、時間和人是最寶貴的。而且,有一支能力非常強的 Python 技術團隊,從裡面抽調部分工程師,支撐 Python 技術棧的中間件建設,也不會帶來額外的人力成本。維護兩個技術棧,中間件團隊的負擔會增加,但是,換取的是時間和優秀的工程師,還是劃算。這些 Python 工程師裡面,負責業務系統的很多人後來也成長為獨擋一面的角色,跟上了業務快速增長的步伐(後續會有相關的内容分享)。而負責中間件的 Python 工程師,他們的一些創造性實踐,也為我們後續架構演進奠定了基礎。

好的技術體系和架構,起決定性的不是技術棧,最終還是優秀的工程師。

3. 資料通路層

因為多技術棧的存在,DAL 層選擇了中心化的方案,而沒有采取 SDK 。統一的資料通路層為後續分庫分表、限流保護、多資料中心上線後的資料糾偏打下了基礎。為了保證系統有足夠強的吞吐能力,DAL 層采取了異步 IO 的方案來處理出入流量,中間件的最高境界是大家會忘記它的存在,DAL 層涉及到底層和資料庫的互動,尤為敏感,而這個中間件幾乎做到了,沒有出現過重大事故,也很少有開發吐槽這一層的問題。後來,這個一直穩健的團隊在餓了麼多資料中心建設當中,負責了核心的流量排程及容災切換管控體系。大家都習慣了叫 DAL,很多人不知道這個系統叫 Athena。

基于 DAL 的上線,DBA 和 DA 這個時期就忙着給各個團隊做分庫分表的事情:

按業務功能領域切分——拆庫

按照通路頻率、動靜态屬性等等規則——垂直分表

基于Hash Partition(需要注意的是避免熱點和Rebalance帶來的成本)—— 水準Sharding

總之就是選擇合适的 Partition 政策,降低資料庫單個執行個體的負載。存儲後來能支撐住千萬級單量,除了上遊隊列的削峰、緩存的緩沖、資料庫讀寫分離以外,也得益于适當的 Data Partition 政策。

二、大前端

其他團隊還在拼命追趕業務、填坑補課的時候,大前端團隊滿足業務需求的同時,還為開源社群貢獻出了非常優秀的産品 Element。就在大家認為這支團隊會繼續在前端領域上一騎絕塵下去的時候,令人沒有想到的是,這個團隊幾年後會爆發出巨大的潛力,成為整個架構體系更新中一個舉足輕重的角色。為什麼叫大前端,因為他們和傳統的前端團隊做的事情不太一樣,後面會講到。

體會和教訓——找到優秀的工程師多麼不容易

招聘優秀的工程師,持續招聘優秀的工程師,這是一句正确的廢話。但是有多難,帶過團隊的應該都深有體會,特别是你的公司還沒有自帶光環的情況下。優秀的工程師會吸引來更多更優秀的工程師,反之亦然,面試這個過程是雙向的,尤其是優秀的工程師。有業務壓力的時候,主管很容易扛不住,降低要求。當時大前端團隊校招淘汰率還是挺驚人的,換來的是這個團隊的工程師很高的技術素養和基本功,為後面成為一個真正的全棧團隊打下了的基礎。

Leader的個人能力,決定了他(她)是這個團隊的地基還是天花闆。

三、大資料

基于 Hadoop、Spark、HBase 的經典大資料架構這個時候也搭建起來了,因為是自建的資料中心,是以這些産品都需要有一個專業的團隊來運維,是以大資料也有了自己的運維和中間件團隊。在這個階段,線上和離線資料同步、資料治理上面還不完善,因為産品化還在路上,很多工具缺失,導緻很多團隊都要自己直接去從數倉取數,不得不維持營運團隊支撐定制化的手工取數需求。各個團隊喊得最多的就是大資料的人不夠,想要自己做。核心還是業務發展太快。後面随着大資料團隊逐漸壯大,更多強援加入,各個産品相繼成熟才得以緩解。

四、風控安全

這是一個不得不說,但是也不能說太多的團隊,是以這部分隻能務虛一些,任何一個到了一定規模的企業,風控安全團隊是“真”底線。其他技術團隊在面對這個同樣是負責技術的團隊面前,有時候确實也挺一言難盡的,這個時候高層的支援至關重要。尤其是從 0 開始建設這個團隊,對内的掃盲和對外風控,一樣艱難。

如果說一個技術公司,系統毀了,有什麼還能留下來,就還能重建,那肯定是資料(現在可能還要加一個算法模型)。有什麼缺失了,随時都可能垮掉,那肯定是風控安全。

餓了麼的這支風控安全團隊,對内、對外、對線上、對線下、對其他……都面臨很多挑戰和沖突,堪稱業務專家的羊毛黨和無孔不入的黑客,确實令人歎為觀止。而我們的風控也經曆了從開始的粗粒度限制、到依賴業務規則針對各種補貼、賬期等場景兜底、再到依賴算法模型實時風控主動攔截的階段。

如果大家身邊有做風控安全的同學,請珍惜,哪怕他們有時候看到系統到處是窟窿的時候,脾氣暴躁。因為他們整天面對這麼多黑暗面,還能對這個世界報以希望。開個玩笑,從人道的角度出發,這個團隊需要定期的心理按摩。

這個階段,我們初嘗了算法的威力。一開始隻有搜尋,但是還沒有推薦召回系統,當時給推薦系統的實體機是我們能拿得出手的最好的實體機,其他業務系統配置設定的大都是虛機。系統上線以後,效果、轉化率都還不錯。之後不久這一待遇被另一個團隊承包——負責配送履約的智能排程團隊,大資料、機器學習、算法模型需要充分發揮功效,需要長時間緊貼業務、深刻了解業務,在智能排程領域我們也做過不少艱難的嘗試、吃過不小苦頭,直到我們有了自己的算法專家團隊。

這個階段我們還經曆了第一次外賣行業的大促——517大促,讓大家真切感受到了這個市場的巨大潛力,同時系統的一系列短闆也暴露無遺,除了積累了大促的經驗以外,更大的收獲是讓我們看到架構還有很大的更新空間。還收獲了一支全鍊路壓測團隊,他們在今後架構更新以及系統品質、容量等穩定性保障過程中,扮演了關鍵角色。

在餓了麼技術往事系列文章的開篇,我提到了餓了麼的技術體系經曆了以下四個階段:
核心系統 All in one 的早期架構;

以系統領域化拆分、業務系統和中間件等基礎設施分離為基礎的全面服務化的架構;

随着自動化平台、容器排程體系成熟,治理從傳統運維向 DevOps 轉變的基礎設施體系;

多資料中心體系基礎上的 Cloud Ready 架構成型。

現在我們前兩個階段基本完成了,開始了相對而言最艱難的階段了……

第三階段:脆弱的系統,苦逼的運維

這個階段,我們的業務已經發展到一定規模,系統的長時間抖動或者崩潰,很容易上熱搜,尤其是飯點時段。發生事故時候,沖在第一線的除了各業務線的工程師,還有運維團隊,他們往往是最先響應,排障沖在第一線的團隊。這個階段說是靠他們生扛頂住了穩定性的壓力也不為過:日常基礎設施部署、事故發生時的應急響應、事故發生後的基礎設施優化和改進措施落地,他們都承擔了很多。

事故的教訓,也讓我們學會了遵循一系列業界積累下來的設計原則,為架構演進到下一階段打下基礎。

業務領域拆分、基礎設施和業務系統分别建設後,給業務快速發展解綁了。但是包括穩定性在内的一系列挑戰依然需要面對:

基礎設施部署的标準化

系統的生命周期怎麼管理?

每次故障都是昂貴的學費,故障可以避免嗎?

複雜性帶來的挑戰:團隊裡面幾乎沒有人面臨過這個體量的業務、這個複雜度的系統。快速傳遞的同時,如何保證系統的穩定和健壯?

我們的系統架構接下來如何演進?

1. DevOps

因為雲上資源的靈活性,我們在雲上搭建了兩個測試環境:alpha作為開發環境,用于軟體工程師日常開發調試;beta作為內建測試環境,用于測試工程師完成系統傳遞上線前的內建、回歸測試。費了九牛二虎之力才達成所有團隊的共識,推動beta環境的系統和資料的完整性建設。在這裡面發揮重要作用的,除了各個業務的開發、測試、運維團隊,還有一個就是之前提到的負責釋出系統的團隊,這個團隊不僅僅提供了一個簡單的釋出系統,基于持續內建和持續部署實作的開發、測試、生産環境相似化,是我們的系統架構繼續演進的開端。

技術團隊職責細分後,運維團隊提供了保姆式的服務,這把雙刃劍的另一面,就是開發團隊很容易形成惰性,對自己的系統管生不管養,對系統的容量、治理關心不夠,因為有運維團隊。這就帶來很多問題,代碼不是運維工程師寫的,但是有些團隊系統甚至是運維工程師部署的。因為開發團隊最貼近業務需求,需求變更可能帶來未來的潛在容量風險,他們比較有發言權;而容量水位的現狀反過來是運維團隊更了解。因為這個時候,很多基礎設施運維還沒完全自動化,是以難以統一化、标準化,每個運維工程師都有自己的運維風格,日常排障上,有時候需要開發和運維一起才能完成。

此外,隻生不養的思維方式,客觀上也容易造成算力成本變成糊塗賬。這個時候,開發、部署、系統營運(治理)角色的不統一帶來的問題就會凸顯。

應用Owner要成為名副其實的Owner,需要有應用的全景視角,對應用生命周期的把控能力。這個階段,開始推動從虛拟化到容器化的轉型,釋出系統從一個簡單的CI、CD的體系,延伸到了算力和排程的領域。基于一系列運維自動化工具的建設和全面容器化排程的實施,進而帶來标準化的運維,才能把開發工程師(應用的Owner)推到應用完整的生命周期營運的位置上,勝任DevOps的角色。這個時候,事實上底層的算力平台,已經具備雲上PaaS的雛形了。

在這個過程中,也做了不少嘗試,比如,為了提高 alpha/beta 這兩個測試環境的基礎設施傳遞效率,有過一段時間基于 slack 的 ChatOps 實踐,工程師都比較歡迎;還有過 Infrastructure as Code 和 GitOps 的實踐,很可惜當時各方面條件和時機都不夠成熟,沒有持續推廣。

體會和教訓——DevOps

alpha 和 beta 環境:

工程師在開發機上自測是不是就可以了,“在我機器上是好的”這句話估計開發工程師都說過或者聽過,在開發階段提供alpha環境,目的就是為了開發、測試、生産環境的盡量接近,避免由于開發、測試、生産三個階段由于環境差異巨大帶來的問題。解決不了“在我機器上是好的”這個問題,沒有辦法大規模順利上雲。工程師自己的電腦,某種程度上是一台“mommy server”,上面運作着需要的一切環境,而且每個工程師的祖傳環境還不一樣,這類環境在生産上是不可複制的。

Build & Release:

怎麼做到高品質快速傳遞,保證系統的穩定?

在快速疊代的同時,做到快速試錯、快速糾錯、快速回退。需要釋出系統做到每個編譯的版本、每次釋出的版本,像代碼一樣,可回溯可跟蹤。關鍵在于build和release是immutable的

首先,build和release有唯一的ID,才可追溯,可復原;

其次,是配置分離,把和環境(dev/test/product)相關的config從代碼中剝離開來,否則系統很難遷移,更不用說大規模上雲。第一反應可能是,把和環境相關的config寫在xml或者yaml檔案就可以了,但是,這些檔案也是代碼。

類似的,将這些随環境變化的config寫在釋出流水線的腳本裡面,都不是徹底分離的方式。因為釋出環境會發生變化,可能将來有更多的測試環境、更多的資料中心、每個資料中心裡面可能還有多泳道。

是以,要做到“build once, deploy many times/every where”,config要存儲在環境的上下文中,比如開發、測試、生産環境各自有一個配置中心,線上系統拉起的時候,先從配置中心拉取配置資訊。要衡量環境相關的config和代碼是否已經分離,看看能不能開源就知道了(抛開價值和代碼品質不談)。

OPS

接觸過傳統的運維工程師都知道,這是一群責任心極強的人(删庫跑路,鏟平資料中心的事情是不可能幹出來的,雖然有能力……),他們維護着系統的底線,第一次517大促事故的時候,我們靠運維工程師救了大家一命。

但是,即使有操作的SOP,隻要是人,執行重複任務的次數足夠多,總會犯錯。而每個資深的運維工程師,都有自己祖傳的腳本,一夫當關萬夫莫開,但是休假就麻煩了,特别是在高鐵上信号不好的時候……最佳實踐→ SOP → 腳本 → 自動化工具産品,沿着這個路徑疊代似乎不可避免。

傳統的運維工程師角色的演進方向,一個是為雲上的IaaS/PaaS服務,對作業系統和底層硬體有着豐富經驗的,還是運維工程師,他們當中開發能力強的,轉型SRE,對運維産品了解深的,可以選擇 Technical Product Manager 的角色,為雲上運維相關平台産品提供解決方案,或者憑借豐富的雲上系統落地實施經驗,為各上雲企業提供實施方案。

另一個方向,由于合規和其他原因,還有部分沒有上雲的企業,依然需要基礎設施運維工程師。随着雲逐漸變成和水電煤一樣的社會基礎設施,運維工程師隻寫作業系統腳本、實施部署的時代已經漸行漸遠了。

架構的曆次演進,和幾次事故或者險些釀成事故的“冒煙”事件,有着很大的關系:

交易系統崩潰的“餓死了”事故,我們開始分離關鍵路徑和非關鍵路徑,建設了非關鍵路徑的降級能力。故障應急響應正常三闆斧:重新開機、復原、降級,至此完備。

第一次 517 大促入口崩潰的事故,是我們核心系統上雲的開端。

F5 的 CPU 被打滿,讓我們意識到網關作為入口難以擴充的巨大風險,進而基于重新建構的大網關體系,取代了 F5 這一層硬體負載均衡。大網關體系是我們多資料中心架構最核心的系統之一。

基于 VIP 的 keepalived+HaProxy 負載均衡體系下,各種 failover 和上下遊頻繁擴縮容過程中,相關的穩定性冒煙或者事故頻發,促成了充當 data plane 的 sidecar  上線,這是我們建構類 Service Mesh 架構體系最重要的元件。

核心交換機 bug 引發的資料中心故障,對我們下決心建設多資料中心體系有着很大的影響

關于這些事故和架構的故事,随着架構的演進,後面會逐個展開。

那個時候,我們常常自嘲是“事故驅動”型開發(Disaster Driven Development)。很多工程師除了自己的工位,在公司裡面最有“感情”的就是整面牆都是監控大屏的NOC作戰室,大小事故、各種大促活動值守,熬夜全鍊路壓測,裡面常常擠滿熟悉的面孔。

體會和教訓——

(1)事故複盤

事故複盤和定期的故障驗屍總結會是一個很好的機制。很容易被忽略的是,除了找到事故發生的 root cause,還需要從中發現存在的隐患,而不是 case by case 的解決問題,複盤的目的是阻止類似的事情再次發生,必要的時候,可以引入業務、産品、技術共同解決。

另一個陷阱是,故障複盤變成追責的過程,那麼參與複盤的各方就很容易陷入互相指責、洗脫責任的怪圈,反而忘記了複盤的根本目的,也容易浪費大量時間,引起不必要的内耗。隻要是參與複盤的人,都是有責任在身上的,為将來的故障負責,如果類似事故再次發生,或者沒有在複盤中發現應該發現的隐患,參與的人都難辭其咎。

複盤結果要避免懲罰為目的 —— 除非違反了規章制度(底線,不排除有些是惡法,但不在讨論範圍内)。否則甩鍋、不作為的氛圍會日漸滋生,自省有擔當和有作為的個人或者團隊,很容易成為吃虧的一方。事故複盤的過程,是了解各個團隊甚至組織文化的一個視角。

(2)彈性設計

物流、交易經曆事故後,各自采取的措施再次印證了,反脆弱的設計是我們的應用發展到今天的核心設計思路之一。

傳統思路是基于一個上下文可控的理想系統環境下做出的設計,盡量避免一切意外的發生。而反脆弱的設計,恰恰假設黑天鵝事件一定發生,是墨菲定律的信徒,開句玩笑話,雲廠商如果承諾你“我們一定會挂”,你一定要珍惜,你面對的是一個坦誠相待的乙方,值得托付。這不是推責給雲廠商,這是由雲上基礎設施的特征決定的,大多數場景下,雲上提供的服務是基于大規模标準化伺服器(Off-the-shelf hardware)建構的虛拟化、容器化基礎設施(Immutable Servers),而不是超高規格的個性化定制獨占裝置(Snowflake Servers)——無法規模化,成本也會大規模上升,是以,會更注重快速恢複能力,水準擴充能力,整體的健壯性,而不是具體某一個單機 SLA。

是以雲上系統更強調算力的抽象,CPU核數、記憶體、網絡帶寬,把資料中心看作一個超級計算機,和 CPU 具備糾錯機制一樣,雲上基礎設施不是不會發生錯誤,隻是結合它的“作業系統”(比如 Kubernetes),提供的是糾錯能力(比如容器的故障轉移 —— 故障容器銷毀,新容器拉起,本質上也是備援),而雲上業務系統需要适配這類糾錯機制實作自己的自愈 —— 面向雲程式設計 —— 接受短時間的抖動(Transient Fault)會不時發生的這一個事實。

物流通過補償機制增強自己的健壯性,交易引入 chaos engineering,都是基于這個上下文。要求應用是 stateless 或者 disposable 的,目的是為了 crash 後能夠迅速拉起,快速自愈——是以,盡量分布式緩存,盡量少本地緩存,應用拉起時初始化的工作盡量少,交給獨立的服務幹這些事。業界的很多模式實踐:bulkhead, circuit breaker, compensation transaction, retry都是指向提升系統的彈性(resilience),足夠健壯的系統能夠在經曆系統抖動後,迅速自愈。

故障和意外一樣,難以避免。我們能做的是減少人禍,敬畏生産環境,因為一次故障影響的可能是騎手一天的生計、商戶一天的營收、使用者的一日三餐。同時,提高系統的健壯性和自愈的能力,在故障發生的時候,盡可能的避免演變成更大的災難,及時止損。

2. 黑天鵝

這個階段,我們經曆了一個大事故,起因就是核心交換機挂了,可能有人問,不都堆疊的嗎,不都有主備嗎,不都自動切換的嗎,說得都對,但是都挂了。因為交換機的一個bug,主備切換後,備機也很快被網絡風暴打挂,沒經曆過我們也不相信。這次又“餓死了”,我們隻能坐等供應商的工程師抱着裝置打車到機房更換,這個時候,一群人擠在應急響應指揮室(NOC作戰室)裡一點辦法都沒有。

在第一次517大促之後,我們就開始第一次容災嘗試了,當時采取的是最快最簡單粗暴的方案,用最短的時間,在雲上搭建一個了災備環境并跑通了業務鍊路。但這是一個冷備的環境,冷備最大的風險,就是日常沒有流量,真正 failover 切換的時候,有比較大的不确定性。這次事故再加上另一個因素,我們下決心将技術體系推進到下一個階段。

體會和教訓——上雲

2016年第一次517大促,10點開搶的瞬間,我們系統崩掉了,要不是當時一個很穩的運維工程師,淡定操作限流,可能不少人在餓了麼的職業生涯當時就結束了。因為對當時的基于Nginx和部分自研插件的網關層比較自信,不相信網關層會頂不住,是以全鍊路壓測的時候根本沒有壓這一層,事後複盤的時候發現是作業系統一個參數配置的問題,如果壓測一定能重制。

因為業務的效果很好,大促就成為常态,事實上第一次大促,我們是在自己的IDC裡面用正常業務系統來扛的,是以影響到了非大促的正常交易。後面專門針對大促高并發大流量的場景設計了一套系統,也是隔離、排隊、CDN、限流這些正常的套路,沒什麼特别的。但是,對我們影響更深遠的在于,這套體系完全是在雲上搭建的,2016年之前雖然雲上有系統,但是生産環境流量很少,頂多是短信觸達這類系統在上面,更多是用于搭建測試環境。在當時看來,雲上強大的流量清洗、資源 scale out 能力,很适合大促的場景,後面,這套體系經曆了多次大促,沒有波瀾。

在雲上搭建大促體系以及災備節點的經曆,讓我們後續在雲上搭建全站的網關,并進一步建構整個資料中心,有了非常大的信心。下一篇我将繼續介紹餓了麼架構演變到了Cloud-Ready的狀态,技術體系演進為業務發展提供了更多可能性。

作者介紹:黃曉路(脈坤),2015年10月加入餓了麼,負責全局架構的工作。