架構師于小波:魅族實時消息推送架構
這個系統數據情況是這樣的,實時在線的用戶是2500萬左右,下面有一個趨勢圖,從今年1到10月份的都列出來了,這個系統一天PV量是50億左右,這個系統推送速度可以達到600萬條/分鐘。
數據結構
系統架構設計
系統架構邏輯上劃分,劃分為四層,最下面的一個是提供魅族手機的接入。第二層是消息分發服務,主要的作用就是提供上行消息的路由和用戶下行消息的路,這邊有一個用戶路由表。第三層是訂閱信息,第四層是存儲,包括離岸消息存儲,包括訂閱消息的存儲。
推送系統架構
左上角有一個服務管理和業務監控,其實是兩個獨立的東西。有一個推送平臺,是提供給業務使用的推送業務的接口。我們都有獨立的服務,是有單獨的DEMO的集群,可以獨立的部署和獨立的擴容。
踩過的坑&心得
-
手機的功耗問題
手機功耗問題
手機功耗問題主要涉及兩個點, 第一個是流量,第二個是電量 。先看流量的問題,怎么樣解決流量的問題,通過協議選擇,現在傳統的互聯網上,有比較典型的使用通訊協議的話,是這兩個協議,XMPP、SIP,這兩個協議有很多的優點, 第一個是開元組件非常多,如果想快速的搭建一脫系統出來,這兩個協議是比較好的選擇。 不足的地方是協議很復雜,單獨的標準文檔就有幾十頁,如果完全把它看完,弄懂,估計要花長的時間,因為這兩個協議是基于互聯網的,并不是針對移動互聯網進行優化的,所以協議是比較重的,像XMPP,有很多無用的標簽,我們根本用不到這些標簽,包括SIP協議,也有很多頭,和XMPP協議差不多的。 最重要的一點是非常的耗流量,所以我們就自己定義了IDG的協議,輕量、編解碼屬于快,是上面兩個協議的10倍左右的編解碼速度,最重要的一點是節約流量,使用中發現節約流量到50%到70%。
還有一個電量的問題我們怎么樣解決?洲際電量就要涉及到長連接的保持,要想保持,就是要發送心跳包到用戶手機上,固定的心跳,固定3分鐘、5分鐘或者是10分鐘來發送心跳,網絡情況是比較復雜的,有時候網絡情況是好的,有時候網絡情況是差的,選擇最短的時間。前面有一個協議,IDG的協議來降低用戶的流量問題,通過智能心跳的問題,來降低手機的電量的消耗。
下面講一下有個延遲推送,系統使用過程中,我們發現有些用戶對實時性的要求并不是特別的敏感,比如說系統的升級、應用的升級,早幾分鐘,或者是晚幾分鐘,并不會影響用戶的使用體驗。我們對于這種實時性要求不高的消息,可以使得手機在喚醒的狀態下才把信息推送下去,我們怎么知道手機處于喚醒狀態呢?這個服務端是可以感知得到的,前面講過手機要維持長連接,就要發心跳,發心跳就要喚醒手機,服務端收到心跳包的時候,再把消息推送下去,這樣就可以相對來說降低一點手機的功耗問題。通過這三個點的優化,基本上相對來說是比較完美的解決了手機的功耗問題,不過這個只是其中一步其中一個坑,我們革命還未成功。
-
移動網絡的問題
國內的移動網絡有一個特點,不穩定,因為我們不知道什么時候會掉線,還有延遲是非常大的,移動網絡的不穩定和高延遲,會讓我們碰到一些什么問題? 首先是碰到的重復消息的問題,重復消息是怎么產生的, 可以看這個圖的流程。
服務端向客戶端推送一條消息的時候,正常情況下,消息推送下去,客戶端到這個消息,給它回一個應答,如果回應答的過程中,服務端就收不到這個應答,服務端就有兩種處理方式,一種是離線,一種是超時重試,重試幾次,直到收到為止,這樣服務端要保持每一條推送消息的狀態,這樣子如果服務端后端因為不單是一個服務組成的,是有很多服務組成的,后端的調動頁就會拉得很長,每一個節點上面都需要保持狀態,任何一個節點出現問題,整個都會認為這個消息是推送失敗了。重復消息的話,客戶端發送應答,服務端沒有接到這個應答,而網絡好的時候,再推送一次,那就出現重復了。
那怎么解決這個問題?設置了消息基于序列號的交互方式,首先推送消息的時候,不是把消息直接推送下去,是發一個通知到客戶端,告訴你有消息,客戶端拿到這個通知,發送一個指令上來,說獲取這個消息,會帶上一個收到最近消息的最大的序列號。這里有一個大坑,也就是DNS也容易出現問題,相信很多人都碰到過這樣的情況。怎么解決這個問題?用全IP的方式,要接入服務器的話,我們這邊有一個WEBService,里面有很多的IP列表,IP列表都拉下來,客戶端直接選取一個IP地址直接去連接,看下面第一個圖的第一步,通過HTTP訪問客戶端的,但是也存在一個域名的問題,我們做了預埋,直接用DNS訪問,如果這個DNS訪問不通,就可以用預埋的IP來訪問,就可以拿到一個IP地址。
-
海量連接出現問題
解決了IP的DNS的問題,革命勝利還有半天,還有最后一個問題了,就是海量連接的問題。大并發話,我們的目標,單機可以達到400萬的長連接,我們實現C++來實現的,是性能的原因才用C++的。還有多進程+epoll,內存池防止內存碎片。這里比較推薦Tcmalloc這個是谷歌開元的組件,非常好用。最后我們還做了內核參數調優的優化,內核參數調優,系統在一臺服務器上跑的時候,達到百萬級連接的時候,我們發現有一個問題,其中有一個CPU的負載會非常高,其他的CPU負載相對來說是比較低的,是比較空閑的,最后我們發現說,因為我們的網卡是多個,網卡的中斷都由這個CPU響應,既要響應網卡的中斷,又要響應業務的需求,這個非常的繁忙,怎么解決?把這個網卡中斷綁定,轉到其他CPU上,達到負載均衡。第二個是TCP RTO修改,我們用2.0的系統內核,默認的只是200毫秒的,如果200毫秒出現超時重傳,但是網絡延遲比較大的,200毫秒的話,會經常會出現重傳,會很頻繁的重傳,會導致協議站的性能會降低,所以我們就在內核系統打了補丁,改為3秒左右,性能會得到一部分的提升。
解決了大并發,下一個是負載均衡的問題,比較常規的做法是前面加LVS,后臺有幾臺服務器,但是LVS并不適合我們的業務場景,一臺服務器可以接受400萬的長連接,一個LVS,下面掛三臺服務器,LVS首先存在單點問題,LVS一臺就要承受1200萬連接,這個是肯定扛不住這樣多的連接數的,我們就放棄了使用單點運營。第一個是服務端做,第二個是客戶端做,前面也講過,獲取IP列表,在獲取的時候,我們LVS已經對那些IP進行過排序了,負載比較低的服務器是排在前面,拉下來之后,客戶端直接拿前面的IP地址連就可以了,在這個地方直接解決負載均衡。解決負載均衡的同時也解決了跨運營商網絡慢的問題。第一次客戶端拉取IP列表,前面的服務器都是負載比較低的,但是客戶端是對這個IP列表進行緩存的,緩存之后,如果斷線重連,再連的時候,就不知道哪臺服務器負載高,哪臺服務器負載高,這里就有一個策略,客戶端把IP劃分成多個IP區間,每一個區間有多個IP,選取IP發一個探測包到服務器,服務器會針對探測包有響應包回來的,哪個先回來,就用哪個IP地址,這樣就同時解決了跨運營商的網絡慢的問題。
但是這樣做的話,其實還是有問題的,不能單獨在客戶端簡單做一個發一個探測包,先收到就用這個,這個也不行,服務端也需要做一些相應的策略假設某一臺服務器負載比較高的時候,剛好收到探測包,立刻回一個響應,很快的客戶端收到這個響應就會連上來,但是本身的負載已經比較高了,那服務端怎么解決這個問題?根據自身的負載情況來做延遲響應,負載達到一定的閥值的時候,比如說是300萬,每超過10萬,延遲時間稍微加50毫秒,收到你的響應包,我晚個50毫秒之后再回應答,這樣跑馬策略是可以解決整個的負載均衡。
系統的監控和灰度發布
-
系統的監控
我們的系統是由很多小服務組成的,我們很難依靠簡單的業務監控來發現未來可能存在的問題,為什么要用“可能”這個詞?假如說每一個業務都是有一個單獨的集群,集群中有一個節點如果出了問題,并不會影響整個業務的使用,只有當累加到足夠大的程度的時候,錯誤累積到很大的程度,才會導致整個系統不可用,這樣其實是不行的,所以我們必須要引入一套嚴格的監控體系,來發現未來可能發生的問題,快速的定位出來,所以針對每一個集群里面的每一個節點都要有監控,有一些比較嚴格的監控指標,這里列了幾個我們認為比較重要的。 第一個是錯誤計數,假如說我有一個節點出現了很多的錯誤計數,其實這個是需要報警通知開發人員。第二個是接發隊列,接收和發送隊列,如果隊列中積累很多的服務,那這個很可能是過載了。第三個是請求數,為什么要做請求數的監控和報警呢? 假如說請求開始前面是一個比較低的水平,慢慢的用戶量越來越多,如果沒有很好的報警體系的話,有可能有一天瞬間把你整個集群沖垮,也就是說達到一定量的時候提前預警,整個業務需要加服務器了。 第四個是處理的接口的調用延時,正常業務調用是1毫秒以內,如果超過了,假如說某一天發現有大狼的100毫秒或者是200毫秒的處理延時,這個業務可能是出現問題的。 最后是服務可用性檢查,是系統服務不可用了,這個肯定是需要報警的,這個是強指標。
-
灰度發布
灰度發布,是用戶無感知的發布,還有用戶可以平滑的遷移,用戶平滑遷移之后,我們做在灰度里面,負載均衡里面也是可以用到這個功能,假設一個集群里面,某一個節點的用戶數比較多的時候,可以把負載高的遷一部分用戶到負載比較低的節點上面去。
灰度發布的流程是這樣的,首先是一個節點的發布,一個節定發布之后,跑幾天看一下,如果沒有什么問題,灰度放量,擴散到幾個節點,這個集群里面選幾個節點,讓這些用戶去用,如果還是OK,沒有什么問題,我們就擴散到全部的節點。
在沒有灰度發布之前,開發人員的狀態大概是這樣的,因為以前發布的話,基本上都是深夜,都是夜深人靜的時候發布,看是否有異常的情況。引入了灰度之后,開發人員就是這樣一種狀態。
來自:http://mp.weixin.qq.com/s?__biz=MzA4NDc2MDQ1Nw==&mid=2650238078&idx=1&sn=6f135fc2a9b911bb3e73a4438b63a29b&chksm=87e18e98b096078e442367ad26c83765e0911f4d2e3adadd024cf79d336e4d38cb0675f9060b&scene=0#wechat_redirect