聊聊我與流式計算的故事
聊聊流式計算吧 , 那一段經(jīng)歷于我而言很精彩,很有趣,想把這段經(jīng)歷分享給大家。
1 背景介紹
2014年,我在藝龍旅行網(wǎng)促銷團隊負責紅包系統(tǒng)。
彼時,促銷大戰(zhàn)如火如荼,優(yōu)惠券計算服務也成為藝龍促銷業(yè)務中最重要的服務之一。
而優(yōu)惠券計算服務正是采用當時大名鼎鼎的流式計算框架 Storm。
流式計算是利用分布式的思想和方法,對海量“流”式數(shù)據(jù)進行實時處理的系統(tǒng),它源自對海量數(shù)據(jù)“時效”價值上的挖掘訴求。
優(yōu)惠券計算服務的邏輯是:每個城市每個酒店的使用優(yōu)惠券的規(guī)則并不相同,當運營人員修改規(guī)則之后,觸發(fā)優(yōu)惠券計算服務,計算完成之后,用戶下單時在使用優(yōu)惠券時會呈現(xiàn)最新的規(guī)則。
優(yōu)惠券計算服務是我們團隊的明星項目,很多研發(fā)的同學都對 Storm 特別感興趣 , 因為 Storm 的核心開發(fā)語言是 clojure , 比較小眾。
于是,在團隊內部,發(fā)現(xiàn)一個很有趣的現(xiàn)象:很多同學的辦公桌上放著《clojure in Action 》這本書。
clojure in Action
藝龍開始發(fā)力移動互聯(lián)網(wǎng),業(yè)務量的激增,優(yōu)惠券計算服務開始遇到了瓶頸。
比如運營人員修改全量規(guī)則時,整個計算流程要耗時一上午,也就談不上實時計算了。
CTO 幾次找團隊負責人,并嚴厲批責成他盡快優(yōu)化。經(jīng)過一個半月幾次優(yōu)化,系統(tǒng)的瓶頸依然明顯,時不時運營同事會走到我們的工位附近,催促我們:“系統(tǒng)生效了么?”
我并不負責計算服務,每當同事被質疑時,我都感到很疑惑:“優(yōu)惠券計算服務真的那么復雜嗎?” , 同時也躍躍欲試:“ Storm 真有那么難搞嗎?”
我心中暗暗下定了決心,一定要弄清楚優(yōu)惠券計算服務的邏輯 。
2 國圖學習
北京有很多景點都讓我流連忘返,比如史鐵生小說里的地壇,滿山楓葉的香山,如詩如畫的頤和園,美侖美奐的天壇 。
在我心里,有一處很神圣的地方,它是知識和希望的象征,那就是國家圖書館 。
中國國家圖書館位于北京市中關村南大街33號,與海淀區(qū)白石橋高粱河、紫竹院公園相鄰。它是國家總書庫,國家書目中心,國家古籍保護中心,同時也是世界最大、最先進的國家圖書館之一 。
每到周末,當我想安靜下來,專注思考時,我就會背著筆記本電腦來到國家圖書館。
選擇自己喜歡的書,然后將筆記本電腦打開,一邊看書,一邊在電腦上寫點筆記。
偶爾抬起頭,望著那些正在閱讀的讀者,心里面感覺很陽光,覺得生命充滿了希望。
想要揭開 Storm 神秘面紗的探索欲,同時探尋優(yōu)惠券計算服務為什么會這么慢的渴望,讓我好幾天晚上沒睡好。
于是周六上午9點半, 我來到國家圖書館 ,想讓自己安靜下來,思考如何解決這個問題。解決問題的快感,是我一直追求的。
當我把筆記本電腦放平在桌上,我很興奮,同時靈臺一片澄清:優(yōu)惠券計算服務的核心是 Storm ,那么我需要先了解 Storm 的整體概念。
打開官網(wǎng),瀏覽官網(wǎng)的文檔,第一次看到 Storm 的邏輯流程圖時, 做為程序員,我第一次竟然感受到抽象之美:從源頭流下來的水通過水龍頭( Spout ),再經(jīng)過層層轉接頭( Bolt )過濾,不就是我們想要的純凈水嗎?
storm邏輯圖,已授權
其實我們原來都是 CRUD boy ,機械的使用那些框架,只會做增刪改查,并不會思考框架背后的設計思路。但框架到底是什么?從來沒有思考過。我一直覺得我很笨拙,學什么都很慢,但那一刻我突然恍然大悟:框架本身是將解決問題的思路抽象化,從而便于研發(fā)人員使用,把復雜的問題抽象成有美感,是需要功底的。
了解完 Storm 整體概念 , 下一步也就是大家熟知的寫 Hello World 階段了 。
我參考教程寫了一個簡單的 Storm 應用(簡稱:拓撲),在部署后,程序正常跑了起來。
我腦海里一直有一個疑問:“是不是優(yōu)惠券計算服務的 storm 集群的配置沒有調優(yōu),才導致計算的性能太差 ? ” 所以我必須去理解 storm 的并發(fā)度是如何計算的。
整個下午,我一直在查閱相關的資料,并結合下圖思考:Nimbus, Supervisor ,Worker ,Task 這些名詞到底是什么概念,以及他們之間是如何交互的。
進而思考:拓撲到底會啟動幾個進程,每個進程內部線程模型是怎樣的,頗有些庖丁解牛的味道。
這個習慣一直保持到現(xiàn)在,當我看到一個系統(tǒng),我會下意思的去思考:“這個系統(tǒng)的線程模型如何,每次操作有哪些線程參于,他們之間如何交互”。我知道有更厲害的大牛,運行一行代碼就知道 CPU 會運行的哪些指令,我做不到,但我覺得那就更加深刻了。
不管怎樣,這一天,我的思緒經(jīng)過多次的變化,興奮,猶疑,放棄,陽光,激動,畏難心理一直存在,很多次想放棄,但好奇心一直鼓勵著我。
等天色已黑,我走出國圖的大門,腦子里全部都是 Storm 進程,線程模型,內心里面,有了莫名的自信。感覺自己就像仙劍奇?zhèn)b傳里的酒劍仙,伴隨著激昂的 BGM ,拔劍四顧,斬妖除魔。
御劍乘風來,除魔天地間,有酒樂逍遙,無酒我亦癲。
一飲盡江河,再飲吞日月,千杯醉不倒,唯我酒劍仙。
3 找到瓶頸
當我理解了 Storm 的整體概念,接下來我需要去找到優(yōu)惠券計算服務的性能瓶頸。這個時候,梳理計算服務整體流程非常關鍵。
優(yōu)惠券流式計算拓撲
計算服務整體流程分為三個步驟 :
- 抽取數(shù)據(jù):酒店信息拉取服務拉取酒店信息,并存儲到水源頭( Redis A/B 集群 ) ;
- 計算過程:Storm 拓撲從水源頭獲取酒店數(shù)據(jù),通過運營配置的規(guī)則對數(shù)據(jù)進行清洗 ,將計算好的數(shù)據(jù)存儲到水存放池 ( Redis C 集群) ;
- 入庫階段:入庫服務從水存放池獲取數(shù)據(jù),將計算結果存儲到數(shù)據(jù)庫 。
當我們把整個計算的過程拆分成 抽取-->計算 --> 存儲 三個階段的時候,計算服務的架構就變得異常清晰,那到底在哪個階段最耗時 ,也成為我追查的目標。
優(yōu)惠券計算服務當時沒有詳細的性能監(jiān)控體系,所以我只能先從日志著手。在運營同事觸發(fā)全量計算后,分別觀察三個階段對應服務的日志:
- 抽取數(shù)據(jù):酒店信息拉取服務
- 計算過程: Storm 拓撲
- 入庫階段: 入庫服務
令人驚訝的現(xiàn)象:一次全量計算需要耗時4個多小時,但抽取數(shù)據(jù)的任務竟然跑了2個多小時,和我預期完全不一樣。
假如我把酒店信息拉取服務比作抽水泵,那么整個系統(tǒng)最大的問題竟然是抽水泵抽水馬力不足。
4 推進重構
為什么抽水泵抽水馬力不足 ?
通過閱讀源碼,我發(fā)現(xiàn)因為線程模型不夠好,應用在部署多個節(jié)點后,每個節(jié)點只能有兩個線程執(zhí)行拉取酒店信息。
怎么處理呢?在原有代碼上優(yōu)化可行嗎?好像也不太容易,因為老代碼最初是一個 C# 研發(fā)同事寫的,他當時也不熟悉 JAVA ,從設計層面來講,有很多冗余且不合理的代碼,而且經(jīng)過3年左右的維護,代碼老化嚴重,于是我只能想到重構。
當我把想法和團隊負責人溝通后,他有點半信半疑,他認為我的判斷沒有問題,但不確定我是否可以將系統(tǒng)重構好。我那時候信心爆棚,主動請纓,打包票不會出問題的。可能是由于 CTO 逼的太緊了,他同意了。
在重構之前,梳理好系統(tǒng)的整體邏輯。
酒店拉取服務邏輯圖
重構的重點原則有兩條:
- 拉取服務可水平擴展,若性能不足時,增加服務節(jié)點即可提升性能;
- 配置文件可配置 worker 線程數(shù)量。
那思想層面,我已經(jīng)做好準備了,那硬實力層面我有沒有做好準備嗎?非常自信的講,準備好了,因為我遇到了 RocketMQ 。
我在《我與消息隊列的8年情緣》這篇文章寫到:
2014年,我搜羅了很多的淘寶的消息隊列的資料,我知道MetaQ的版本已經(jīng)升級MetaQ 3.0,只是開源版本還沒有放出來。
大約秋天的樣子,我加入了RocketMQ技術群。誓嘉(RocketMQ創(chuàng)始人)在群里說:“最近要開源了,放出來后,大家趕緊fork呀”。他的這句話發(fā)在群里之后,群里都炸開了鍋。我更是歡喜雀躍,期待著能早日見到阿里自己內部的消息中間件。
終于,RocketMQ終于開源了。我迫不及待想一窺他的風采。
因為我想學網(wǎng)絡編程,而RocketMQ的通訊模塊remoting底層也是Netty寫的。所以,RocketMQ的通訊層是我學習切入的點。
我模仿RocketMQ的remoting寫了一個玩具的rpc,這更大大提高我的自信心。正好,藝龍舉辦技術創(chuàng)新活動。我想想,要不嘗試一下用Netty改寫下Cobar的通訊模塊。于是參考Cobar的源碼花了兩周寫了個netty版的proxy,其實非常粗糙,很多功能不完善。后來,這次活動頒給我一個鼓勵獎,現(xiàn)在想想都很好玩。
在重構酒店信息拉取服務時,我將 RocketMQ 如何創(chuàng)建線程的知識點正好也用了上去,并學習如何將模塊拆分得更加合理。同時在重構過程中,不斷 Review 新老代碼的差別,確保核心邏輯正確。
非常幸運,大概一周時間,我就重構完了。
重構完成并不意味著結束,怎么驗證呢 ?我當時采取了兩種方式:
- 代碼評審
我拉著優(yōu)惠券計算服務的同事,一起 review 代碼 。整個過程,大家也并沒有提出異議,并對我創(chuàng)建線程的技巧感到很好奇。我心中竊喜:”那是學習 RocketMQ 的“。 - 測試環(huán)境數(shù)據(jù)驗證
我們將新舊兩版服務同時觸發(fā),比對兩個版本的數(shù)據(jù)的異同,將比對結果輸出到日志文件,然后從中找到差異的地方,修復重構版的 BUG 。然后在測試環(huán)境部署重構版,觀察一段時間,確保無異常。
從編寫第一行代碼,三周時間,重構版終于上線了。我將原來的老服務替換后,部署了3個節(jié)點, 每個節(jié)點8個worker 并行拉取酒店信息 。
令人開心和激動的是,重構是非常成功的。因為業(yè)務給我們的時間需求也是1個小時左右。一次全量計算從原來4個小時急速縮減到1小時15分鐘,整個酒店拉取服務耗時40分鐘左右。
我心里長舒一口氣,內心吟誦李白的詩:"十步殺一人,千里不留行。事了拂衣去,深藏身與名。"
5 向前一步
前 Facebook COO 謝麗爾·桑德伯格寫了一本書《向前一步》,我特別喜歡這本書的書名 。
在優(yōu)化優(yōu)惠券計算服務的前期,團隊經(jīng)過一個多月的時間,也沒有什么成效。我自己也猶豫:”我能不能解決這個問題?“ ,但最終我還是向前一步,并幫助團隊大大提升了服務的性能,負責人也有了信心,他也敢投入資源優(yōu)化Storm 拓撲和入庫流程。
在閱讀優(yōu)惠券計算服務的代碼中,我發(fā)現(xiàn)兩個問題:
- 流式計算邏輯中有大量網(wǎng)絡 IO 請求,主要是查詢特定的酒店數(shù)據(jù),用于后續(xù)計算;
- 每次計算時需要查詢基礎配置數(shù)據(jù),它們都是從數(shù)據(jù)庫中獲取。
對于Storm 拓撲優(yōu)化,我提了兩點建議:
- 流式計算拓撲和酒店拉取服務各司其職,將流式計算中的網(wǎng)絡 IO 請求挪到酒店拉取服務,將數(shù)據(jù)前置準備好;
- 基礎配置緩存化,引入讀寫鎖(也是 RocketMQ 名字服務的技巧)。
對于入庫流程,一位研發(fā)同學將原來的單條數(shù)據(jù)入庫修改成批量入庫。
經(jīng)過大家一起努力 ,優(yōu)惠券計算服務的整體性能大大提升了,全量計算耗時已經(jīng)變成40分鐘了,再也不會有運營同事在我們的工位附近吐槽系統(tǒng)慢了。
6 寫到最后
2014年,我向前一步推動了公司流式計算服務的優(yōu)化,并取得了一點點進步。
時光荏苒,我已中年,生命中遇到越來越多的挫折,有的時候也會讓人低落,但每當想起這個故事,我會深深感動于當時的一往無前。
當再次面臨選擇時,我希望自己也能夠向前一步,想著如何幫助讀者成長,或是實現(xiàn)一個產品幫助更多的人。