下面是對這篇文章做的一些解讀。
二、過載保護基本概念
1.什麼是服務過載?
服務過載就是服務的請求量超過服務所能承受的最大值,從而導致服務器負載過高,響應延遲加大,用戶側表現就是無法加載或者加載緩慢,這會引起用戶進一步的重試,服務一直在處理過去的無效請求,導致有效請求跌 0,甚至導致整個系統產生雪崩。
2.為什麼會發生服務過載?
互聯網天生就會有突發流量,秒殺,搶購,突發大事件,節日,甚至惡意攻擊等,都會造成服務承受平時數倍的壓力,微博經常出現某明星官宣結婚或者離婚導致服務器崩潰的場景,這就是服務過載。
3.過載保護的好處
主要是為了提升用戶體驗,保障服務質量,在發生突發流量時仍然能夠提供一部分服務能力,而不是整個系統癱瘓,系統癱瘓就意味著用戶流失,口碑變差,夫妻吵架,甚至威脅生命安全(假如騰訊文檔崩潰,這個文檔正好用於救災)。
三、微信中的過載場景
微信採用的是微服務,說是微服務,其實我理解就是採用統一的 RPC 框架搭建的一個個獨立的服務,服務之間互相調用,實現各種各樣的功能,這也是現代服務的基本架構。畢竟誰也不想看到我朋友圈崩了,導致我聊天也不行了。
在大規模微服務場景下,過載會變得比較複雜,如果是單體服務,一個事件只用一個請求,但微服務下,一個事件可能要請求很多的服務,任何一個服務過載失敗,就會造成其他的請求都是無效的。如下圖所示。
比如在一個轉賬服務下,需要查詢分別兩者的卡號, 再查詢 A 時成功了,但查詢 B 失敗,對於查卡號這個事件就算失敗了,比如查詢成功率只有 50%, 那對於查詢兩者卡號這個成功率只有 50% * 50% = 25% 了, 一個事件調用的服務次數越多,那成功率就會越低。
為啥不使用響應時間?因為響應時間是跟服務相關的,很多微服務是鏈式調用,響應時間是不可控的,也是無法標準化的,很難作為一個統一的判斷依據。
那為什麼不使用 CPU 負載作為判斷標準呢, 因為 CPU 負載高不代表服務過載,因為一個服務請求處理及時,CPU 處於高位反而是比較良好的表現。實際上 CPU 負載高,監控服務是會告警出來,但是並不會直接進入過載處理流程。
採用平均等待時間還有一個好處,是這個是獨立於服務的,可以應用於任何場景,而不用關聯於業務,可以直接在框架上進行改造。
當平均等待時間大於 20ms 時,以一定的降速因子過濾調部分請求,如果判斷平均等待時間小於 20ms,則以一定的速率提升通過率,一般採用快降慢升的策略,防止大的服務波動,整個策略相當於一個負反饋電路。
五、過載保護策略
一旦檢測到服務過載,需要按照一定的策略對請求進行過濾,前面分析過,對於鏈式調用的微服務場景,隨機丟棄請求會導致整體服務的成功率很低。所以請求是按照優先級進行控制的, 優先級低的請求會優先丟棄。
1.業務優先級
對於不同的業務場景優先級是不同的, 比如登錄場景是最重要的業務,不能登錄一切都白瞎,另外支付消息比普通消息優先級高,因為用戶對金錢是更敏感的,但普通消息又比朋友圈消息優先級高,所以在微信內是天然存在業務優先級的。
用戶的每個請求都會分配一個優先級,並且在微服務的鏈式調用下,下游請求的優先級也是繼承的,比如我請求登錄,那麼檢查賬號密碼等一系列的的後續請求都是繼承登錄優先級的,這就保證了優先級的一致性。
2. 用戶優先級
很明顯,只基於業務優先級的控制是不夠的,首先不可能因為負載高,丟棄或允許通過一整個業務的請求,因為每個業務的請求量很大,那一定會造成負載的大幅波動,另外如果在業務中隨機丟棄請求,在過載情況下還是會導致整體成功率很低。
為了解決這個問題,可以引入用戶優先級,首先用戶優先級也不應該相同,對於普通人來說通過 hash 用戶唯一 ID,計算用戶優先級,為了防止出現總是打豆豆的現象,hash 函數每小時更換,跟業務優先級一樣,單個用戶的訪問鏈條上的優先級總是一致的。
這裡有個疑問,為啥不採用會話 ID 計算優先級呢,從理論上來說採用會話 ID 和用戶 ID 效果是一樣的,但是採用會話 ID 在用戶重新登錄時刷新,這個時候可能用戶的優先級可能變了,在過載的情況下,他可能因為提高了優先級就恢復了,這樣用戶會養成壞習慣,在服務有問題時就會重新登錄,這樣無疑進一步加劇了服務的過載情況。
因為引入了用戶優先級,那就和業務優先級組成了一個二維控制平面,根據負載情況,決定這臺服務器的准入優先級(B,U),當過來的請求業務優先級大於 B,或者業務優先級等於 B,但用戶優先級高於 U 時,則通過,否則決絕。
如何根據負載情況調整優先級呢?最簡單的方式是從右到左遍歷,每調整一次判斷下負載情況,這個時間複雜度是 O(n), 就算使用二分法,時間複雜度也為 O(logn),在數千個優先級下,可能需要數十次調整才能確定一個合適的優先級,每次調整好再統計優先級,可能幾十秒都過去了,這個方法無疑是非常低效的。
微信提出了一種基於直方圖統計的方法快速調整准入優先級,服務器上維護者目前准入優先級下,過去一個週期的(1s 或 2000 次請求)每個優先級的請求量,當過載時,通過消減下一個週期的請求量來減輕負載,假設上一個週期所有優先級的通過的請求總和是 N,下一個週期的請求量要減少 N*a,怎麼去減少呢,每提升一個優先級就減少一定的請求量,一直提升到 減少的數目大於目標量,恢復負載使用相反的方法,只不是係數為 b ,比 a 小,也是為了快降慢升。根據經驗值 a 為 5%,b 為 1%。
為了進一步減輕過載機器的壓力,能不能在下游過載的情況下不把請求發到下游呢?否則下游還是要接受請求,解包,丟棄請求,白白的浪費帶寬,也加重了下游的負載。
為了實現這個能力,在每次請求下游服務時,下游把當前服務的准入優先級返回給上游,上游維護下游服務的准入優先級,如果發現請求優先級達不到下游服務的准入門檻,直接丟棄,而不再請求下游,進一步減輕下游的壓力。
- 當用戶從微信發起請求,請求被路由到接入層服務,分配統一的業務和用戶優先級,所有到下游的字請求都繼承相同的優先級。
- 根據業務邏輯調用 1 個或多個下游服務,當服務收到請求,首先根據自身服務准入優先級判斷請求是接受還是丟棄。服務本身根據負載情況週期性的調整准入優先級。
- 當服務需要再向下游發起請求時,判斷本地記錄的下游服務准入優先級,如果小於則丟棄,如果沒有記錄或優先級大於記錄則向下遊發起請求。
- 下游服務返回上游服務需要的信息,並且在信息中攜帶自身准入優先級。
- 上游接受到返回後解析信息,並更新本地記錄的下游服務准入優先級。
整個過載保護的策略有以下三個特點:
- 業務無關的,使用請求等待時間而不是響應時間,制定用戶和業務優先級,這些都與業務本身無關。
- 獨立控制和聯合控制結合,准入優先級取決於獨立的服務,但又可以聯合下游服務的情況,優化服務過載時的表現。
- 高效且公平, 請求鏈條的優先級是一致的,並且會定時改變 hash 函數調整用戶優先級,過載情況下,不會總是影響固定的用戶。
參考:https://www.cs.columbia.edu/~ruigu/papers/socc18-final100.pdf