一個架構師的自白 | 那些追源碼的平凡之路
- 2020 年 12 月 28 日
- 筆記
在斯坦福大學, 喬布斯做了一場我認為他最精彩的演講。他講的第一個故事是 connecting the dots,這也是貫穿他一生非常重要的思想。
你不可能充滿預見地將生命的點滴串聯起來;只有在你回頭看的時候,你才發現這些點點滴滴之間的聯繫。所以,你要堅信,你現在所經歷的將在你未來的生命中串聯起來… 正是這種信仰讓我不會失去希望,它讓我的人生變得與眾不同。
我不禁在想:我的編碼以及架構生涯中,那些點是什麼,又終將會連成怎樣的線?
十年前剛進入 IT 行業的時候,我是一個很普通的工程師,腦袋也不靈光,工作老是得不到要領,而我的同學智商很高,他看一次代碼基本就會寫了,我得花很長時間去消化吸收,我對自己能不能在這一行生存下去都產生了質疑。
沒有辦法,只能笨鳥先飛,當遇到問題的時候,我都抱着死咬不放的心態去尋找最佳解決方案。洗澡的時候、吃飯的時候、甚至上廁所的時候都會去思考。很自然的,”追” 源碼也成為我程序人生的一部分。
我閱讀過很多源碼,和大家分享幾個對我職業影響比較大的源碼追尋經歷。
01 數據庫連接池 Durid
這是在2013年,我負責重構一個彩票算獎服務,原有代碼是 C# 版本的,每次計算訂單金額需要耗費 2~3 個小時,很多用戶反饋體驗很差,因為收到獎金很晚。
我當時採用 Druid 作為新項目的數據庫連接池,重構後效果很明顯,算獎性能提升到了原來的 10 倍。
不過,有一個問題是:每天第一次數據庫請求總會報連接錯誤。當時我也不怎麼會看源碼,就直接給 Druid 的作者溫少(也是 FastJson 作者)發了一封郵件:
溫少給我回復了郵件,我馬上翻看源碼,發現我配置的連接心跳有問題。核心點在於連接池每隔一段時間就會發送心跳包到數據庫服務器,而數據庫為了節省資源,會關閉掉長期沒有讀寫的連接。
這次簡單的源碼之旅給了我很大的激勵,也讓我更加關注技術背後的原理。
1、精神層面:向別人請教問題是會上癮的。
2、技能層面:理解連接池的實現原理。druid是基於數組實現的,後來用到的 jedis 連接池基於 commons-pool 實現的,netty 連接池是基於 FixChannelPool 實現的。
3、架構層面:客戶端和服務端的長連接通信需要考慮心跳。類似 druid 連接池發送心跳的機制,以及 netty 中的 idleStateHandler。
02 分庫中間件 Cobar
還是在 2013 年,當時移動互聯網大潮奔涌而來,各大互聯網公司的數據爆炸般的增長。
我曾在 JavaEye 上看到淘寶訂單的技術人員分享他們分庫分表的帖子,頓時覺得如獲至寶,可惜受限於篇幅,文章沒講很細節的原理,總感覺隔靴搔癢。
沒曾想到不久後阿里將 Cobar 開源了,用 Navicat 配置好 Cobar 信息,就像連單個 MySQL 一樣,而且數據會均勻地分佈到多個數據庫中。這對於我當時還很孱弱的技術思維來講,簡直就像三體里的水滴遇到人類艦隊般,給了我很大的刺激。
因為對分庫分表原理的渴求,我花了大約 3 個月的時間把整個 Cobar 的核心代碼抄了一次。真的是智商不夠,體力來湊。
但光有體力是真的不夠,經常會陷入懷疑,有些地方還是看不懂,邊抄代碼邊學習好像進步沒那麼明顯。那好,總得找一個突破口吧。
網絡通訊是非常重要的一環,因此我決定把 Cobar 的網絡通訊模塊剝離出來,去深刻理解使用原生 NIO 實現通訊的模式。剝離的過程同樣很痛苦,但我有目標了,不至於像沒頭的蒼蠅,後來也就有了人生第一個 GitHub 項目。
在寫 NIO 工程的同時,我還學習到了 Maven 的 Assemble 打包模式,這個現在聽起來很簡單,但在 2013 年還是以 Tomcat 部署 war 包佔主流的年代,讓當時的我眼前一亮。
追 Cobar 的過程中,我也找到了和阿里大牛面對面交流的機會,雖然我資質駑鈍,但這位大牛對我的問題耐心解答,似乎打通了我的任督二脈。因此,這次經歷說成是我編碼生涯中最重要的一次經歷也不為過。
後來在藝龍網工作,和藝龍做數據庫中間件的架構師溝通時,因為我有研究 Cobar 源碼的底子,理解他的思路也很快。另外,也幫助他找到了分佈式事務的一個 Bug。
03 阿里的消息中間件 MetaQ
2015年我加入神州專車,那個時候神州專車處於上升期,各個系統遇到了較大的瓶頸。MetaQ 在那個時期發揮了很重要的作用,相關引申的知識點也很多。
3.1 廣播消息在推送系統中的使用
當時我們使用的 MetaQ 是庄曉丹在 GitHub 開源的版本。2016年初,我 checkout 了 MetaQ 的源碼,邊理解業務邊深入理解它的機制。
很有幸地和架構部的同事討論了專車推送的設計方案。最開始的時候,我們也是使用極光推送,後來因為定製化的需求越來越多,因此決定自研推送系統。
那服務器如何推送消息到每一個連接的專車 APP 呢?方案很簡單:採用 MetaQ 的廣播模式就可以實現這個功能。
1、業務系統推送消息到 MetaQ
2、TCP 網關廣播模式消費 MetaQ 的消息
3、TCP 網關獲取當前服務器所持有的 Session 會話,推送數據給 App
後來,我仔細研讀了京東京麥系統的 TCP 網關設計,關於推送方面的實現和我們上面的方案非常相似。
2018年,我服務的電商公司研發直播答題系統,我用這個方案又實現了推送題目的功能。
3.2 ZK 崩潰引申的一連串知識
我們都知道 MetaQ 依賴 ZooKeeper 來實現負載均衡。突然有一天,專車整個 ZK 集群 down 掉了。架構負責人修改了 ZK 的 JVM 參數,問題貌似解決了,但疑問隨之產生了。
MetaQ 和服務治理共用一套 ZK 集群合適嗎?
閱讀 MetaQ 源碼後,我發現 MetaQ 在消費者很多的情況下,啟動時會頻繁的爭搶鎖,另外消費的過程,也會對 offset 頻繁的修改。在 Topic 數量增多,partition 數量增多的情況下,MetaQ 對 ZK 實際上是有寫壓力的。
後來,神州架構部確實也是將 MetaQ 的 ZK 集群和服務治理的 ZK 集群分開了。當然遷移也是有技巧的,這裡就不展開了。
ZK 作為神州體系的註冊中心是否有瓶頸?
ZK 會存儲各個服務的 IP 和端口,以及對外暴露的方法。隨着專車系統的服務越來越多,ZK 真的可以承受得了嗎?後來,公司領導邀請了京東的研發同學來給大家答疑。
京東的註冊中心服務信息用什麼實現的呢?京東同學的回答是:MySQL。我在旁邊聽得目瞪口呆,什麼?MySQL?後來,淘寶中間件博客出了一篇文章:阿里巴巴為什麼不用 ZooKeeper 做服務發現?
當數據中心服務規模超過一定的數量,作為註冊中心的 ZooKeeper 很快就會像驢子一樣不堪重負。
後來我參考張開濤寫的一篇博客以及在 GitHub 上零落的 JSF 代碼, 手擼了一個 AP 模型、客戶端使用 BerkeleyDB 的註冊中心。
我們也知道 2019年,阿里在 SpringCloud 生態上發力,Nacos 誕生了。Nacos 同時支持 AP 和 CP 兩種模型,開源世界的選擇更有多樣性了。當我們使用 ZooKeeper 的時候,一定要注意集群規模和使用場景。
04 任務調度系統 XXL-Job
時間已經到了 2018 年,技術部需要一個可靠的任務調度系統。最開始使用的是 XXL-Job,但不知道怎麼搞的,老是使用遇到有問題。從追源碼到優化改造共經歷了 3 個階段。
第 1 階段,我看了 XXL-Job 的源碼後,第一直覺是「簡單」。因為作者已經最大限度的將這個系統做成了開箱即用,去掉了 Quartz 的集群調度模式,自研基於數據庫的調度器。
但當前公司已經有自研的 RPC 服務,讓其他團隊配合 XXL-Job 添加JobHandler 好像也不太容易。所以最開始,我修改了調度器的代碼,使用了公司的 RPC 來執行,只不過將 XXL-Job 的 JobHandler 替換為公司 RPC 的ServiceId。運行起來還行,能滿足公司要求。
第 2 階段,為什麼我想再優化一波呢?因為當前的 RPC 調度是同步執行,不支持異步,假如 RPC 執行任務很長,那麼 XXL-Job 的調度線程就會被阻塞。
我找到了美團的朋友,向他請教他們公司是如何設計任務調度系統的。他給我演示了美團任務調度系統 Crane 的執行過程,考慮到保密他僅僅給我講了其中的原理。我根據他的描述做了如下架構設計:
Schedule-Client 收到調度請求後,會將任務丟到線程池中異步執行,同時立馬返回給調度服務器,這樣就不會阻塞了。
第 3 階段,光有架構設計沒用,工程上如何做到更優雅的實現呢? 我想到了阿里雲的schedulex,假如我是一名阿里雲的開發者,我會怎麼設計一個好的任務調度系統以支持每天千億級別的任務調度呢?
我翻看了 schedulex 的開放文檔以及 client 端源碼,想不到真的是寶藏。schedulex client 里包含如下幾個亮點:
1、RPC 調用類似 RocketMQ remoting
2、任務調度通過 RPC 觸發,有統一的註冊中心(NameServer 模式)
3、支持多端口啟動,以防當前端口啟動失敗
4、任務執行和任務調度的線程池做了隔離
借鑒這些優點,我很快完成了工程實現,技術同事們對這個改造還算滿意。但我深知,當前的系統還有兩個待解決的問題:
1、任務調度重度依賴數據庫, 當真正有 10 萬、20 萬級別的任務的時候,任務的分配以及調度的觸發肯定會有瓶頸。
2、當系統是容器的時候,是否可以正常使用?
於是,今年年初我在 GitHub 上寫下了自己相對完整的一個任務調度系統,將 Quartz 替換成了時間輪,將任務觸發改成服務端推送模式。
在寫任務調度的過程中,實際上也是不斷超越自己的過程,我想把它做成一個可以和行業對標的作品,就必須向業界最先進的技術產品以及優秀的同行們學習。
05 寫在最後
回顧那些追源碼的點滴,心潮澎湃,久久不能平復。它們是我架構師之路最美好的記憶。
喬布斯的演講里提到:工作將佔據你生命中的很大一部分,只有從事你認為具有非凡意義的工作,方能給你帶來真正的滿足。而從事一份偉大工作的唯一方法,就是去熱愛這份工作。
專註當前做的事情,並且把每件事情做到能力範圍內的極限,也許此時沒有那麼大的成就感,但在未來的某個時間節點,你可能突然就有一種「驀然回首,那人卻在燈火闌珊處」的後知後覺。
親愛的程序員朋友,愛你所選,全情投入,相信你必有所得。
作者簡介:985碩士,前亞馬遜工程師,現58轉轉技術總監
歡迎掃描下方的二維碼,關注我的個人公眾號:IT人的職場進階,精彩原創不斷!