一、推送平臺特點
vivo推送平臺是vivo公司向開發者提供的訊息推送服務,通過在雲端與客戶端之間建立一條穩定、可靠的長連線,為開發者提供向客戶端應用實時推送訊息的服務,支援百億級的通知/訊息推送,秒級觸達移動使用者。
推送平臺的特點是併發高、訊息量大、送達及時性較高。目前現狀最高推送速度140w/s,單日最大訊息量150億,端到端秒級線上送達率99.9%。
二、推送平臺Redis使用介紹
基於vivo推送平臺的特點,對併發和時效性要求較高,並且訊息數量多,訊息有效期短。所以,推送平臺選擇使用Redis中介軟體作為訊息儲存和中轉,以及token資訊儲存。之前主要使用兩個Redis叢集,採用Redis Cluster 叢集模式。兩個叢集如下:
對Redis的操作,主要包括如下幾方面:
1)推送環節,在接入層儲存訊息體到msg Redis叢集,訊息過期時間為msg Redis儲存訊息的過期時間。
2)推送服務層經過一系列邏輯後,從msg Redis叢集查出訊息體,查詢client Redis叢集client資訊,如果client線上,直接推送。如果client不線上,將訊息id寫到等待佇列。
3)如果連線上來,推送服務層,讀取等待佇列訊息,進行推送。
4)儲存管理服務,會定期掃描cii索引,根據cii儲存的最後更新時間,如果14天都沒更新,說明是不活躍使用者,會清理該token資訊,同時清理該token對應的等待佇列訊息。
推送環節操作Redis流程圖如下:
三、推送平臺線上問題
如上面介紹,推送平臺使用Redis主要msg叢集和client叢集,隨著業務的發展,系統對效能要求越來越高,Redis出現一些瓶頸問題,其中msg Redis叢集在優化前,規模已達到220個master,4400G容量。隨著叢集規模變大,維護難度增加,事故率變高。特別是4月份,某某明星離婚事件,實時併發訊息量5.2億,msg Redis叢集出現單節點連線數、記憶體暴增問題,其中一個節點連線數達到24674,記憶體達到23.46G,持續30分鐘左右。期間msg Redis叢集讀寫響應較慢,平均響應時間500ms左右,影響到整體系統的穩定性和可用性,可用性降到85%。
四、推送平臺Redis優化
Redis一般從以下幾方面優化:
1)容量:Redis屬於記憶體型儲存,相較於磁碟儲存型資料庫,儲存成本較昂貴,正是由於記憶體型儲存這個特性使得它讀寫效能較高,但是儲存空間有限。因此,業務在使用時,應注意儲存內容儘量是熱資料,並且容量是可預先評估的,最好設定過期時間。在儲存設計時,合理使用對應資料結構,對於一些相對大的value,可以壓縮後儲存。
2)熱key傾斜:Redis-Cluster把所有的物理節點對映到[0-16383]slot(槽)上,每個節點負責一部分slot。當有請求呼叫時,根據 CRC16(key) mod 16384的值,決定將key請求到哪個slot中。由於Redis-cluster這個特性,每個節點只負責一部分slot,因此,在設計key的時候應保證key的隨機性,特別是使用一些hash演算法對映key時,應保證hash值的隨機分佈。另外,控制熱點key併發問題,可以採用限流降級或者本地快取方式,防止熱點key併發請求過高導致Redis熱點傾斜。
3)叢集過大:Redis-Cluster採用無中心結構,每個節點儲存資料和整個叢集狀態,每個節點都和其他所有節點連線。每個節點都儲存所有節點與slot對映關係。當節點較多時,每個節點儲存的對映關係也會變多。各節點之間心跳包的訊息體內攜帶的資料越多。在擴縮容時,叢集重新進行clusterSlots時間相對較長。叢集會存在阻塞風險,穩定性受影響。因此,在使用叢集時,應該儘量避免叢集節點過多,最後根據業務對叢集進行拆分。
這裡有個問題:為什麼Redis-Cluster使用16384個slot,而不是更多,最多可以有多少個節點?
官方作者給出了解釋,並且在解釋中說明,Redis-Cluster不建議超過1000個主節點。
基於以上一些優化方向,和自身業務特性,推送平臺從以下幾方面開啟Redis優化之路。
- msg Redis叢集容量優化;
- msg Redis大叢集根據業務屬性拆分;
- Redis熱點key排查;
- client Redis叢集併發呼叫優化。
4.1 msg Redis叢集容量優化
前文提及,msg Redis叢集規模達到220個master、4400G容量,高峰期已使用容量達到3650G,使用了83%左右,如果後續推送提量,還需擴容,成本太高。於是對msg Redis叢集儲存內容進行分析,使用的分析工具是雪球開源RDB分析工具RDR 。github網址:這裡不多介紹,大家可以去github網址下載相應的工具使用。這個工具可以分析Redis快照情況,包括:Redis不同結構型別容量、key數量、top 100 largest keys、字首key數量和容量。
分析後的結論:msg Redis叢集中,mi:開頭的結構佔比80%左右,其中單推訊息佔比80%。說明:
- 單推:1條訊息推送1個使用者
- 群推:1條訊息可以重複推送多個使用者,訊息可以複用。
單推的特點是一對一推送,推送完或者推送失敗(被管控、無效使用者等)訊息體就不再使用。
優化方案:
- 及時清理單推訊息,如果使用者已經收到單推訊息,收到puback回執,直接刪除Redis訊息。如果單推訊息被管控等原因限制傳送,直接刪除單推訊息體。
- 對於相同內容的訊息,進行聚合儲存,相同內容訊息儲存一條,訊息id做標識推送時多次使用。
經過這個優化後,縮容效果較明顯。全量上線後容量縮小了2090G,原最高容量為3650G,容量縮小了58%。
4.2 msg Redis大叢集根據業務屬性拆分
雖然進行了叢集容量優化,但是高峰期msg Redis壓力依然很大。
主要原因:
1)連線msg Redis的節點很多,導致高峰期連線數較高。
2)訊息體還有等待佇列都儲存在一個叢集,推送時都需要操作,導致Redis併發很大,高峰期cpu負載較高,到達90%以上。
3)老叢集Redis版本是3.x,拆分後,新叢集使用4.x版本。相較於3.x版本有如下優勢:
- PSYNC2.0:優化了之前版本中,主從節點切換必然引起全量複製的問題。
- 提供了新的快取剔除演算法:LFU(Least Frequently Used),並對已有演算法進行了優化。
- 提供了非阻塞del和flushall/flushdb功能,有效解決刪除了bigkey可能造成的Redis阻塞。
- 提供了memory命令,實現對記憶體更為全面的監控統計。
- 更節約記憶體,儲存同樣多的資料,需要更少的記憶體空間。
- 可以做記憶體碎片整理,逐步回收記憶體。當使用Jemalloc記憶體分配方案的時候,Redis可以使用線上記憶體整理。
拆分方案根據業務屬性對msg Redis儲存資訊進行拆分,把訊息體和等待佇列拆分出來,放到獨立的兩個叢集中去。這樣就有兩種拆分方案。
方案一:把等待佇列從老叢集拆分出來
只需推送節點進行修改,但是傳送等待佇列連續的,有狀態,與clientId線上狀態相關,對應的value會實時更新,切換會導致資料丟失。
方案二:把訊息體從老叢集拆分出來
所有連線msg Redis的節點替換新地址重啟,推送節點進行雙讀,等到老叢集命中率為0時,直接切換讀新叢集。由於訊息體的特點是隻有寫和讀兩個操作,沒有更新,切換不用考慮狀態問題,只要保證可以寫入讀取沒問題。並且訊息體容量具有增量屬性,需要能方便快速的擴容,新叢集採用4.0版本,方便動態擴縮容。
考慮到對業務的影響及服務可用性,保證訊息不丟失,最終我們選擇方案二。採用雙讀單寫方案設計:
由於將訊息體切換到新叢集,那在切換期間一段時間(最多30天),新的訊息體寫到新叢集,老叢集儲存老訊息體內容。這期間推送節點需要雙讀,保證資料不丟失。為了保證雙讀的高效性,需要支援不修改程式碼,不重啟服務的動態規則調整措施。
大致規則分為4個:只讀老、只讀新、先讀老後讀新、先讀新後讀老。
設計思路:服務端支援4種策略,通過配置中心的配置決定走哪個規則。
規則的判斷依據:根據老叢集的命中數和命中率決定。上線初期規則配置“先讀老再讀新”;當老叢集命中率低於50%,切換成"先讀新後讀老";當老叢集命中數為0後,切換成“只讀新”。
老叢集的命中率和命中數通過通用監控增加埋點。
方案二流程圖如下:
拆分後效果:
- 拆分前,老msg Redis叢集同時期高峰期負載95%以上。
- 拆分後,同時期高峰期負載降低到70%,下降15%。
拆分前,msg Redis叢集同時期高峰期平均響應時間1.2ms,高峰期存在呼叫Redis響應慢情況。拆分後,平均響應時間降低到0.5ms,高峰期無響應慢問題。
4.3 Redis熱點key排查
前面有說過,4月某某明星熱點事件,出現msg Redis單節點連線數、記憶體飆升問題,單節點節點連線數達到24674,記憶體達到23.46G。
由於Redis叢集使用的虛擬機器,起初懷疑是虛擬機器所在宿主機存在壓力問題,因為根據排查發現出現問題的節點所在宿主機上掛載Redis主節點很多,大概10個左右,而其他宿主機掛載2-4個左右主節點,於是對master進行了一輪均衡化優化,使每臺宿主機分配的主節點都比較均衡。均衡化之後,整體有一定改善。但是,在推送高峰期,尤其是全速全量推送時,還是會偶爾出現單節點連線數、記憶體飆升問題。觀察宿主機網路卡出入流量,都沒出現瓶頸問題,同時也排除了宿主機上其他業務節點的影響。因此懷疑還是業務使用Redis存在熱點傾斜問題。
通過高峰期抓取呼叫鏈監控,從下圖可以看到,我們11:49到12:59這期間呼叫msg Redis的hexists命令耗時很高,該命令主要是查詢訊息是否在mii索引中,鏈路分析耗時的key大都為mii:0。同時對問題節點Redis記憶體快照進行分析,發現mii:0容量佔比很高,存在讀取mii:0熱點問題。
經過分析排查,發現生成訊息id的雪花演算法生成的messageId,存在傾斜問題,由於同一毫秒的序列值都是從0開始,並且序列長度為12位,所以對於併發不是很高的管理後臺及api節點,生成的messageId基本都是最後12位為0。由於mii索引key是mi:${messageId%1024},messageId最後12位為0,messageId%1024即為0,這樣就導致msg Redis中mii:0這個key很大,查詢時命中率高,因此導致了Redis的熱key問題。
優化措施:
1)雪花演算法改造,生成訊息id時使用的sequence初始值不再是0,而是從0~1023隨機取一個數,防止熱點傾斜問題。
2)通過msg訊息體中訊息型別及訊息體是否存在來替換調hexists命令。
最終效果:優化後,mii索引已分佈均勻,Redis連線數很平穩,記憶體增長也較平穩,不再出現Redis單節點記憶體、連線數暴增問題。
4.4 client Redis叢集併發呼叫優化
上游節點呼叫推送節點是通過clientId進行一致性hash呼叫的,推送節點會快取clientInfo資訊到本地,快取時間7天,推送時,優先查詢本地快取,判斷該client是否有效。對於重要且經常變更的資訊,直接查詢client Redis獲取,這樣導致推送高峰期,client Redis叢集壓力很大,併發高,cpu負載高。
優化前推送節點操作快取和client Redis流程圖:
優化方案:對原有clientInfo快取進行拆分,拆分成三個快取,採取分級方案。
- cache還是儲存原來clientInfo一些資訊,這些資訊是不經常變更的,快取時間還是7天。
- cache1快取clientInfo經常變更的資訊,如:線上狀態、cn地址等。
- cache2快取ci加密部分引數,這部分快取只在需要加密時使用,變更頻率沒那麼高,只有連線時才會變更。
由於新增了快取,需考慮快取一致性問題,於是新增一下措施:
1)推送快取校驗,呼叫broker節點,根據broker的返回資訊,更新和清理本地快取資訊。broker新增不線上、aes不匹配錯誤碼。下次推送或者重試時,會重新從Redis中載入,獲取最新的client資訊。
2)根據手機端上行事件,connect和disconnect時,更新和清理本地快取資訊,下次推送或者重試時,會重新從Redis中載入,獲取最新的client資訊。
整體流程:訊息推送時,優先查詢本地快取,快取不存在或者已過期,才從client Redis中載入。推送到broker時,根據broker返回資訊,更新或失效快取。上行,收到disconnect、connect事件,及時更新或失效快取,再次推送時重新從client Redis載入。
優化後推送節點操作快取和client Redis流程圖:
優化後效果:
1)新增cache1快取命中率52%,cache2快取命中率30%。
2)client Redis併發呼叫量減少了近20%。
3)高峰期Redis負載降低15%左右。
五、總結
Redis由於其高併發效能和支援豐富的資料結構,在高併發系統中作為快取中介軟體是較好的選擇。當然,Redis是否能發揮高效能,還依賴業務是否真的理解和正確使用Redis。有如下幾點需要注意:
1)由於Redis叢集模式,每個主節點只負責一部分slot,業務在設計Redis key時要充分考慮key的隨機性,均勻分散在Redis各節點上,同時應避免大key出現。另外,業務上應避免Redis請求熱點問題,同一時刻請求打到少部分節點。
2)Redis實際吞吐量還與請求Redis的包資料大小,網路卡有關,官方文件有相關說明,單個包大小超過1000bytes時,效能會急劇下降。所以在使用Redis時應儘量避免大key。另外,最好根據實際業務場景和實際網路環境,頻寬和網路卡情況進行效能壓測,對叢集實際吞吐量做摸底。
以我們client Redis叢集為例:(僅供參考)
- Network:10000Mb;
- Redis Version:3.x;
- Payload size:250bytes avg;
- 命令:hset(25%)、hmset(10%)、hget(60%)、hmget(5%);
- 效能情況:連線數5500、48000/s、cpu 95%左右。
Redis在實時分析這塊支援較少,除了基本指標監控外,實時記憶體資料分析暫不支援。在實際業務場景下如果出現Redis瓶頸,往往監控資料也會缺失,定位問題較難。對Redis的資料分析只能依賴分析工具對Redis快照檔案進行分析。因此,對Redis的使用依賴業務對Redis的充分認知,方案設計的時候充分考慮。同時根據業務場景對Redis做好效能壓測,瞭解瓶頸在哪,做好監控和擴縮容準備。
作者:vivo網際網路伺服器團隊-Yu Quan