坑爹,線上同步近 3w 個使用者導致鏈路阻塞引入發的線上問題,你經歷過嗎?

語言: CN / TW / HK

theme: Chinese-red

本文正在參加 「金石計劃 . 瓜分6萬現金大獎」

分享一個印象深刻的線上問題,希望能夠給 xdm 帶來一點思考

一個稀鬆平常的工作日,正準備下班的時候,不巧,突發線上緊急問題,心中一萬個不情願,可還是要硬著頭皮去定位問題

簡單的表象為微服務之間 gRPC通訊的通道預設是用了預設值,並沒有按照實際業務去設定通道接受和傳送的位元組大小

用過 golang grpc 通訊的 xdm 就知道,grpc 通道預設的傳送和接收的訊息大小為 4M,由於傳送的資料包大於了 4M,導致通道阻塞,一直報錯 rpc 錯誤,

rpc error: code = ResourceExhausted desc = grpc: received message larger than max (6394324 vs. 4194304)

於是便有了一個定位並想辦法解決或者規避問題的慢慢長夜

簡述基本介紹通訊流程

整個業務架構比較複雜,我們簡單的提出出問題的服務鏈路來進行闡述

服務 A - 專門和第三方對接

有一個服務 A 是專門找第三方同步源讀取第三方系統的使用者組織結構,並轉換成咱們平臺自己的資料結構,將資料傳送給 服務 B

服務 B - 專門處理使用者相關資料

服務 B 專門處理關於使用者組織結構資料的,處理完畢之後落盤,並將資料給到服務 C

服務 C - 主要做資料轉發

服務 C 主要是做資料的通道,會將資料轉發給到節點中的應用 D,因為 應用 D 和 服務 A 和 服務 B 沒有辦法直接通訊

應用 D,處理處理實際的流量即管控

應用 D 接收或者去找 服務 C 拉取資料後做相應的業務,做基本的流量管控和使用者認證等

其中上述兩者之間都是通過 gRPC 的方式通訊

問題 1 - rpc 通道傳送和接收訊息設定過小

萬萬沒想到的是,在做第三方組織結構同步的時候,居然是服務 A 從第三方同步源中獲取所有的使用者組織結構(包括所有的組,所有的使用者),不管總量多少,一口氣全部弄過來,然後再一口氣全部推給 服務 B

可這一次線上問題,正是因為這麼 low 的做法和處理方式,導致超出了 gRPC 的預設訊息大小 4M,顯現就是服務 A 將資料傳送給到服務 B 的時候,傳送沒有問題,但是服務 B 接收的時候出了問題,日誌中瘋狂列印上述的 rpc 錯誤

rpc error: code = ResourceExhausted desc = grpc: received message larger than max (6394324 vs. 4194304)

當然,這個時候不允許我們停下來去思考如何優化的事項,必須第一時間解決或者規避問題

立刻評估,將涉及到的服務,gRPC 的 send 和 receive 的地方全部統一修改為 32 M(1024102432) ,這個問題暫時得以規避

可以檢視到 grpc 原始碼中的說明

MaxRecvMsgSize 接受訊息 在 grpc 中預設大小為 $$$$4M --(102410244)= 2^{22} $$$$

MaxSendMsgSize 傳送訊息在 grpc 中預設是$$$$ MaxInt32 = 1<<31 - 1 也就是 4M * 2^9 -1 即 2048 M = 2G $$$$

則在 grpc.NewServer 的時候,將上述的 option 加上去就可以了,例如這樣

// 例如設定接收訊息大小為 math.MaxInt32 var opts []grpc.ServerOption opts = append(opts, grpc.MaxRecvMsgSize(math.MaxInt32)) newSvr := grpc.NewServer(opts...) // grpc RegisterxxxxServer()

問題 2 - 組織結構同步的層級太小,不支援 16 層

本以為問題就這麼規避了,然而還是太年輕

當服務 A 將資料全部打包傳送到服務 B 的時候,才發現,原來問題才剛剛開始,由於資料量比之前測試過的資料量大了好幾倍,導致各種問題接二連三的出來,這也體現了整個平臺的健壯性太差

當前遇到的問題是服務 B 處理的組織結構層數,最大隻能有 8 層,超過 8 層的資料就直接不要了😂😂😂,看到這裡,what??? 之前是誰設計的😢😢😢

迅速閱讀原始碼,檢視相關聯的邏輯,調整關聯程式碼,火速將支援的 8 層,調整為 16 層組織結構,還好程式碼不是太複雜,否則這麼大半夜的,真的不敢大動,畢竟這個時候已經不是一個人了,只是半個人😅😅😅

此處的組織結構好在不是傳遞的一棵樹,如果是一棵樹的話,大概率是要棧空間超限的,我們知道棧空間一般 2M,超過就要溢位了

好在是傳遞的是一個使用者的 list,元素是關於使用者的絕對路徑,例如 /a/b/c/小花

問題 3 - 同步近 3 w 個使用者居然花了近 8 分鐘,近 3w 使用者放到一個訊息裡

處理完問題 2 之後,嘗試同步一次組織結構,發現近 3 w 的使用者,居然同步花費了 8 分鐘左右,其中 服務A 到服務B耗時近 2 分鐘,然後服務 B 就處理了4 分鐘,其餘時間花費在別的服務處理

這個時候,就看到頁面上一直在轉圈,如果是使用者看到一幕,那可能直接就是退貨的節奏了🤣🤣

不過好在是同步成功了,暫時先把同步的按鈕先關了吧,畢竟資料都過來了,接下來的事情都是可以調整的

其實看到這裡,一個訊息放近 3 w 個使用者,只能說這一塊根本沒有設計,需求是趕鴨子上架趕出來的吧?對於這一塊的優化放到下一篇來分享

問題 4 - 操作介面一直轉圈,前端處理資料極慢

然而,當上一個問題還沒有完全解決的時候,發現又爆出另外一個問題,看來年輕的不僅僅是一點點大,這個時候甲方爸爸要開始喊 細狗你行不行啊

開啟平臺,檢視使用者相關頁面,卡的一匹,足以和上世紀開啟網頁的程度比慢了,簡直沒眼看,。處理 3 w 資料耗時 20 分鐘,才看到正常的頁面

原來處理方式是這樣的:

前端找後臺查詢這個租戶所有的使用者,然後前端再進行樹形展示,看到這裡是不是蒙圈了???

哪有這麼去實現功能的?基本的懶載入不會嗎?暫時後端提供相應的介面, 前端 調整邏輯得以規避

懶載入, 是一種獨特而又強大的資料獲取方法,它能夠在使用者滾動頁面的時候自動獲取更多的資料,而新得到的資料不會影響原有資料的顯示,同時最大程度上減少伺服器端的資源耗用

然後,大部人的回答是,我也不知道會有那麼多資料呀🤣🤣🤣,誒,還是吃虧在太年輕,不夠專業,甚至還有人提議,這個頁面暫時讓客戶不要點😥😥

問題 5 - 大資料日誌上報,由於資料量猛增,導致日誌通道阻塞

真是福無雙至,禍不單行啊

由於大資料日誌上報模組也需要通過 grpc 根使用者資料更新時間來一次性查詢使用者,同樣的問題,這一條鏈路也卡的要死,顯示由於請求超時,因為 rpc 超時時間程式碼中預設就設定了 10 s , 後面將時間改大了之後得以規避,最終由於大資料模組處理資料慢,花費了 2 個小時才把近 3w 的資料搞定

心中想,這也是大資料??😪😪,感覺這個產品要完蛋了

問題 6 - 系統本身僅支援 1 w 使用者,前線默默的攬了一個 5w 使用者的客戶

處理到這裡,天也漸漸矇矇亮了,從效能測試的小夥伴報告中瞭解到,之前做的效能測試最多就支援 1 個租戶下有 1w 個使用者,然而,銷售吹牛皮招攬了遠遠大於這個數的客戶,且還不告知研發內部

其實銷售也沒想到,怎麼我們的平臺這麼弱雞?逐步喪失信心。。

實際上出現的問題遠遠不止上述幾個,接下來便是無盡的優化和思考,希望暴露的問題能給 xdm 帶來一些提醒和思考

無論之前架構如何,對於第三方組織結構同步的時候,咱們需要考慮這些問題

  • 產品的基本資料指標要同步給前線等相關方,避免自己人坑自己人,火急火燎的,很難有效的解決好問題
  • 從第三方獲取組織結構的時候,基本的分頁要有,不僅僅是分頁從第三方同步源獲取,還要分頁的給出去
  • 對於服務 A 將資料給到服務 B 的時候,先分頁給組,再分頁給使用者
  • 對於 rpc 中訊息大小設計,需要在設計之初根據業務考慮到可能的訊息情況,去設定一個合理的數值
  • 對於同步資料的時候,需要分步驟,分狀態來進行處理,需要實現斷點續傳,需要能夠應對各種異常場景
  • 對於前端從後臺獲取使用者樹的時候,務必記得使用懶載入(簡單來說就是可以先獲取最外層的組,當點選到某個組的時候,再去查詢這個組下面的使用者資訊和子組資訊,而不是一口氣將整個租戶的組織結構加載出來🤣🤣,這也太心大了)

自然,實際落地的時候還需要考慮很多,我們需要更多的往前看一步,不給別人留坑,也不給自己挖坑

自然我們需要提升自己的能力,提升自己的思維,不斷向大佬看齊,可能低階問題我們都會犯,但要有進步,要有變化,做需求,做設計,需要考慮更加全面,否則你永遠不知道你帶來的影響有多大

關於如何去優化第三方組織結構同步這個功能,感興趣的朋友可以思考一下,評論區討論一下哦,下一篇會進行闡述對其優化的方法,以及落地的方案

文中提到的技術感興趣的可以檢視如下相關文章,或者自行擴充套件:

  • 懶載入
  • 分頁,同步

感謝閱讀,歡迎交流,點個贊,關注一波 再走吧

歡迎點贊,關注,收藏

朋友們,你的支援和鼓勵,是我堅持分享,提高質量的動力

好了,本次就到這裡

技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是阿兵雲原生,歡迎點贊關注收藏,下次見~