循證與決策路徑
在前文中提過,循證大概是我們讀技術(shù)干貨文章的一個原始訴求,通過分析別人走過的路徑,來撥開自己技術(shù)道路探索上的迷霧。
關(guān)于 IM 類消息應(yīng)用最重要的一個技術(shù)決策就是關(guān)于消息模型,微信采用了存儲轉(zhuǎn)發(fā)模型,其具體描述如下(參考[1]):
消息被發(fā)出后,會先在后臺臨時存儲;為使接收者能更快接收到消息,會推送消息通知給接收者;最后客戶端主動到服務(wù)器收取消息。
簡單描述這個模型就是三個步驟:
消息接收后在服務(wù)端臨時存儲,并通知發(fā)送端已發(fā)送成功。
通知接收端有消息,請來拉取。
接收端收到通知后,再來拉取真正的消息。
初一看這個模型多了一層通知再拉取的冗余,為什么不直接把消息推下去?對,最早期我們自己做 IM 就設(shè)計的先嘗試直接推消息下去,若接收端沒有確認(rèn)收到,再臨時存儲的模型。后者減少了臨時存儲的量和時間,也沒有一個多余的通知。
但后面這個模型增加了另一層復(fù)雜度,在早期的 PC 互聯(lián)網(wǎng)時期,推送并確認(rèn)效率還算挺高的,但在移動環(huán)境下,就不太行了。而且引入了移動端,實際就導(dǎo)致了另一層復(fù)雜性,多終端在線,多終端確認(rèn),多終端已讀和未讀,都需要在服務(wù)端記錄各個端的狀態(tài)。所以,之后我們也就慢慢演變成同時存儲和推送消息的并行模型,存儲是為了方便各終端拉取各自的離線消息,但推送因為需要考慮舊終端版本的支持,還得直接推消息本身而并不容易簡化成消息通知來取消掉消息的接收確認(rèn)過程。
循證,即便你看到了一個更好的方式,但也要結(jié)合自身的實際情況去思考實踐的路徑。所以,如今我們的模型相比微信是一個更妥協(xié)的版本,若是五年多前要改成微信這樣的模型,也許只需要一兩個程序員一周的時間。但如今也許需要好幾個不同的開發(fā)團隊(各終端和后端)配合弄上一兩個季度也未必能將所有用戶切換干凈了。
切磋與思考方式
IM 中還有個大家特別常用和熟悉的功能 —— 群消息。關(guān)于群消息模型,微信采用的是寫擴散模型,也就是說發(fā)到群里的一條消息會給群里的每個人都存一份(消息索引,參考[1])。這個模型的最大缺點就是要把消息重復(fù)很多份,通過犧牲空間來換取了每個人拉取群消息的效率。
好多年前我們剛開始做群時,也是采用了的寫擴散模型,后來因為存儲壓力太大,一度又改成了讀擴散模型。在讀擴散模型下,群消息只存一份,記錄每個群成員讀取群消息的偏移量(消息索引號,單調(diào)增長)。之所以存儲壓力大在于當(dāng)時公司還沒有一個統(tǒng)一的存儲組件,我們直接采用的 Redis 的內(nèi)存存儲,當(dāng)時原生的 Redis 在橫向和縱向上的擴展性上都比較受限。這在當(dāng)時屬于兩害相權(quán)取其輕,選擇了一個對我們研發(fā)團隊來說成本更低的方案。
再后來公司有了擴展性和性能都比較好的統(tǒng)一存儲組件后,實際再換回寫擴散模型則更好。畢竟讀擴散模型邏輯比較復(fù)雜,考慮自己不知道加了多少個群了,每次打開應(yīng)用都要檢查每個群是否有消息,性能開銷是呈線程遞增的。唯一的問題是,寫好、測好、上線運行穩(wěn)定幾年的程序,誰也不想再去換了對吧,每一次的技術(shù)升級和架構(gòu)優(yōu)化其實是需要一個契機的。
另外一個是所有分布式后臺系統(tǒng)都有的共性問題 —— 性能問題。只要你的用戶量到了一定規(guī)模,比如 100 萬,以后每上一個量級,對技術(shù)支撐的挑戰(zhàn)實際上并不是呈線性的。微信春晚紅包的案例(參考[2])給出了一個很好的參考和啟發(fā),因為市面上幾乎很少有系統(tǒng)能到這個量級了。
微信 2015 年春節(jié)的紅包峰值請求是 1400 萬每秒,而微信后臺其實也采用了微服務(wù)的架構(gòu),其拆分原則如下(參考[1]):
實現(xiàn)不同業(yè)務(wù)功能的 CGI 被拆到不同 Logicsrv,同一功能但是重要程度不一樣的也進行拆分。例如,作為核心功能的消息收發(fā)邏輯,就被拆為 3 個服務(wù)模塊:消息同步、發(fā)文本和語音消息、發(fā)圖片和視頻消息。
服務(wù)拆散了,在自動化基礎(chǔ)設(shè)施的輔助下,運維效率下降不大,而開發(fā)協(xié)作效率會提升很多,但性能會下降。那么在面對微信春晚紅包這樣的極端性能場景下,該如何應(yīng)對?在電商里,正常下單和秒殺下單多是分離的兩套系統(tǒng)來支撐,秒殺專為性能優(yōu)化,簡化了很多正常流程,而且秒殺本身需要支持的 sku 不多,所以它具備簡化的基礎(chǔ)。而微信給出的方案中實際也是類似的思路,但它有個特殊點在于,能把拆散的服務(wù)為了性能又合并回去。
在如此海量請求之下,在這個分布式系統(tǒng)中,任何一點網(wǎng)絡(luò)或服務(wù)的波動都可能是災(zāi)難性的。最終我們選擇把搖一搖服務(wù)去掉,把一千萬每秒的請求干掉了,把這個服務(wù)挪入到接入服務(wù)。但這樣做,有一個前提:不能降低接入服務(wù)的穩(wěn)定性。因為不單是春晚搖一搖請求,微信消息收發(fā)等基礎(chǔ)功能的請求也需要通過接入服務(wù)來中轉(zhuǎn),假如嵌入的搖一搖邏輯把接入服務(wù)拖垮,將得不償失。
這里面的黑科技在于 C++ 技術(shù)棧的優(yōu)勢,同一臺接入服務(wù)器上實際由不同的進程來處理不同的功能,達到了隔離的效果。而不同進程間又可以通過共享內(nèi)存來通信,這比用 Socket 網(wǎng)絡(luò)通信高效多了,又有效的規(guī)避了網(wǎng)絡(luò)層帶來的波動性影響,這是我們用 Java 做后臺沒法做到的事。
切磋,你不能看見別人的功夫套路好,破解難題手到擒來,就輕易決定改練別人的功夫。表面的招式相同,內(nèi)功可能完全不同,就像金庸小說里的鳩摩智非要用小無相功催動少林七十二絕技,最后弄的自廢武功的結(jié)局。切磋主要是帶給你不同的思維方式,用自己的功夫?qū)で笃平庵馈?/p>
連結(jié)與有效提取
如何選擇干貨,我在前文《技術(shù)干貨的選擇性問題》中最后給出的結(jié)論是,給自己結(jié)一張網(wǎng),形成知識體系。暫時離你的網(wǎng)太遠的技術(shù)潮流性的東西,可以暫不考慮,結(jié)合功利性和興趣原則去不斷編織和擴大自己的技術(shù)之網(wǎng)。在編織了一些新結(jié)點入網(wǎng)后,就需要進一步有效提取這些結(jié)點的價值。
剛做 IM 時,曾經(jīng)有個疑惑,就是 IM 的長連接接入系統(tǒng),到底單機接入多少長連接算合適的?很早時運維對于長連接有個報警指標(biāo)是單機 1 萬,但當(dāng)時我用 Java NIO 開 2G 最大堆內(nèi)存,在可接受的 GC 停頓下,在一臺 4 核物理機上測試極限支撐 10 萬長連接是可用的。那么平時保守點,使用測試容量的一半 5 萬應(yīng)該是可以的。
之后一次機會去拜訪了當(dāng)時阿里旺旺的后端負(fù)責(zé)人,我們也討論到了這個長連接的數(shù)量問題。當(dāng)時淘寶有 600 萬賣家同時在線,另外大概還有 600 萬買家實時在線。所以同時大概有 1200 萬用戶在線,而當(dāng)時他們后端的接入服務(wù)器有 400 臺,也就是每臺保持 3 萬連接。他說,這不是一個技術(shù)限制,而是業(yè)務(wù)限制。因為單機故障率高,一旦機器掛了,上面的所有用戶會短暫掉線并重連。若一次性掉線用戶數(shù)太多,恢復(fù)時間會加長,這會對淘寶的訂單交易成交產(chǎn)生明顯的影響。
他還說了一次事故,整個機房故障,導(dǎo)致單機房 600 萬用戶同時掉線。整個故障和自動切換恢復(fù)時間持續(xù)了數(shù)十分鐘,在此期間淘寶交易額也同比下降了約 40% 左右。因為這種旺旺在線和交易的高度相關(guān)性,所以才限制了單機長連接的數(shù)量,而當(dāng)時已經(jīng)有百萬級的單機長連接實驗證明是可行的。
在微信春晚紅包的那篇文章里提到:
在上海跟深圳兩地建立了十八個接入集群,每個城市有三網(wǎng)的接入,總共部署了 638 臺接入服務(wù)器,可以支持同時 14.6 億的在線。
簡單算一下,大概就是 228.8 萬單機長連接的容量規(guī)劃,14.6 億怕是以當(dāng)時全國人口作為預(yù)估上限了。實際當(dāng)然沒有那么多,但估計單機百萬長連接左右應(yīng)該是有的。這是一個相當(dāng)不錯的數(shù)量了,而采用 Java 技術(shù)棧要實現(xiàn)這個單機數(shù)量,恐怕也需要多進程,不然大堆的 GC 停頓就是一個不可接受和需要單獨調(diào)優(yōu)的工作了。
連結(jié),便是這樣一個針對同一個問題或場景,將你已知的部分連結(jié)上新的知識和實踐,形成更大的網(wǎng),再去探索更多的未知。