大數據

餓了麼技術往事(中)

image.png

在上一篇文章《餓了麼技術往事(上)》中,我介紹了餓了麼最早期 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月加入餓了麼,負責全局架構的工作。

Leave a Reply

Your email address will not be published. Required fields are marked *