國民級應用:微信是如何防止崩潰的?

語言: CN / TW / HK

圖片

導讀 | 微信作為月活過10億的國民級應用,經常面臨特殊節點消息量暴增的問題,服務很容易出現過載。但微信的服務一直比較穩定,是如何做到的呢?本文邀請到了騰訊WXG後開開發工程師alexccdong以微信 2018 年發表於Socc會議上的文章《Overload Control for Scaling Wechat Microservices》 為基礎,介紹微信大規模微服務的過載保護策略,其中很多方法很有借鑑意義。歡迎繼續閲讀。

圖片

過載保護基本概念

1)什麼是服務過載?

服務過載就是服務的請求量超過服務所能承受的最大值,從而導致服務器負載過高,響應延遲加大。用户側表現就是無法加載或者加載緩慢。這會引起用户進一步的重試,服務一直在處理過去的無效請求,導致有效請求跌 0,甚至導致整個系統產生雪崩。

2)為什麼會發生服務過載?

互聯網天生就會有突發流量。秒殺、搶購、突發大事件、節日甚至惡意攻擊等,都會造成服務承受平時數倍的壓力。微博經常出現某明星官宣結婚或者離婚導致服務器崩潰的場景,這就是服務過載。

3)過載保護的好處

提升用户體驗、保障服務質量。在發生突發流量時仍然能夠提供一部分服務能力,而不是整個系統癱瘓,系統癱瘓就意味着用户流失、口碑變差、夫妻吵架甚至威脅生命安全(例如提供醫療資源協調服務的app)。

圖片

微信中的過載場景

微信採用的是微服務。微服務採用統一的 RPC 框架搭建一個個獨立的服務,服務之間互相調用,實現各種各樣的功能,這也是現代服務的基本架構。畢竟誰也不想看到自己朋友圈崩掉導致聊天功能也無法正常使用。

微信的服務是分三層:接入服務、邏輯服務、基礎服務。大多數服務屬於邏輯服務,接入服務如登錄、發消息、支付服務,每日請求量在 10 億-100 億之間,入口協議觸發對邏輯服務和基礎服務更多的請求,核心服務每秒要處理上億次的請求。

圖片

在大規模微服務場景下,過載會變得比較複雜。如果是單體服務,一個事件只用一個請求。但微服務下,一個事件可能要請求很多的服務,任何一個服務過載失敗,就會造成其他的請求都是無效的。如下圖所示:

圖片

比如在一個轉賬服務下,需要查詢分別兩者的卡號,再查詢 A 時成功了,但查詢B失敗,對於查卡號這個事件就算失敗了,比如查詢成功率只有 50%,那對於查詢兩者卡號這個成功率只有 50% * 50% = 25% 了,一個事件調用的服務次數越多,那成功率就會越低。

圖片

如何判斷過載

通常判斷過載可以使用吞吐量、延遲、CPU 使用率、丟包率、待處理請求數、請求處理事件等等。微信使用在請求在隊列中的平均等待時間作為判斷標準,就是從請求到達,到開始處理的時間。

為啥不使用響應時間?因為響應時間是跟服務相關的,很多微服務是鏈式調用,響應時間是不可控的,也是無法標準化的,很難作為一個統一的判斷依據。

那為什麼不使用 CPU 負載作為判斷標準呢?因為 CPU 負載高不代表服務過載,一個服務請求處理及時,CPU 處於高位反而是比較良好的表現。實際上 CPU 負載高,監控服務是會告警出來,但是並不會直接進入過載處理流程。

騰訊微服務默認的超時時間是 500ms,通過計算每秒或每 2000 個請求的平均等待時間是否超過 20ms,判斷是否過載,這個 20ms 是根據微信後台 5 年摸索出來的門檻值。

採用平均等待時間還有一個好處是這個是獨立於服務的,可以應用於任何場景,而不用關聯於業務,可以直接在框架上進行改造。

當平均等待時間大於 20ms 時,以一定的降速因子過濾調部分請求。如果判斷平均等待時間小於 20ms,則以一定的速率提升通過率。一般採用快降慢升的策略,防止大的服務波動。整個策略相當於一個負反饋電路。

圖片

圖片

過載保護策略

一旦檢測到服務過載,需要按照一定的策略對請求進行過濾。前面分析過,對於鏈式調用的微服務場景,隨機丟棄請求會導致整體服務的成功率很低。所以請求是按照優先級進行控制的, 優先級低的請求會優先丟棄。

1)業務優先級

對於不同的業務場景優先級是不同的。比如登錄場景是最重要的業務,不能登錄一切都白費。支付消息也比普通消息優先級高,因為用户對金錢是更敏感的。但普通消息又比朋友圈消息優先級高。所以在微信內是天然存在業務優先級的。

用户的每個請求都會分配一個優先級。在微服務的鏈式調用下,下游請求的優先級也是繼承的。比如我請求登錄,那麼檢查賬號密碼等一系列的的後續請求都是繼承登錄優先級的,這就保證了優先級的一致性。

每個後台服務維護了業務優先級的hash表。微信的業務太多,並非每個業務都記錄在表裏,不在表裏的業務就是最低優先級。

圖片

2)用户優先級

很明顯,只基於業務優先級的控制是不夠的。首先不可能因為負載高,丟棄或允許通過一整個業務的請求。每個業務的請求量很大,那一定會造成負載的大幅波動。另外如果在業務中隨機丟棄請求,在過載情況下還是會導致整體成功率很低。

解決這個問題可以引入用户優先級。首先用户優先級也不應該相同,對於普通人來説通過 hash 用户唯一 ID,計算用户優先級,為了防止出現總是打豆豆的現象,hash 函數每小時更換,跟業務優先級一樣,單個用户的訪問鏈條上的優先級總是一致的。

為啥不採用會話 ID 計算優先級呢?從理論上來説採用會話 ID 和用户 ID 效果是一樣的。但是採用會話 ID 在用户重新登錄時刷新,這個時候可能用户的優先級可能變了,在過載的情況下,可能因為提高了優先級就恢復了。這樣用户會養成壞習慣,在服務有問題時就會重新登錄,這樣無疑進一步加劇了服務的過載情況。

引入了用户優先級,那就和業務優先級組成了一個二維控制平面。根據負載情況,決定這台服務器的准入優先級(B,U),當過來的請求業務優先級大於 B,或者業務優先級等於 B,但用户優先級高於 U 時,則通過,否則決絕。

圖片

3)自適應優先級調整

在大規模微服務場景下,服務器的負載是變化非常頻繁的,所以服務器的准入優先級是需要動態變化的。微信分了幾十個業務優先級,每個業務優先級下有 128 個用户優先級,所以總的優先級是幾千個。

如何根據負載情況調整優先級呢?最簡單的方式是從右到左遍歷,每調整一次判斷下負載情況,這個時間複雜度是 O(n), 就算使用二分法,時間複雜度也為 O(logn)。在數千個優先級下,可能需要數十次調整才能確定一個合適的優先級,每次調整好再統計優先級,可能幾十秒都過去了,這個方法無疑是非常低效的。

微信提出了一種基於直方圖統計的方法快速調整准入優先級,服務器上維護者目前准入優先級下,過去一個週期的(1s 或 2000 次請求)每個優先級的請求量,當過載時,通過消減下一個週期的請求量來減輕負載,假設上一個週期所有優先級的通過的請求總和是N。下一個週期的請求量要減少N*a,怎麼去減少呢?每提升一個優先級就減少一定的請求量,一直提升到減少的數目大於目標量,恢復負載使用相反的方法,只不是係數為b,比a小,也是為了快降慢升。根據經驗值a為 5%,b為1%。

圖片

為了進一步減輕過載機器的壓力,能不能在下游過載的情況下不把請求發到下游呢?否則下游還是要接受請求、解包、丟棄請求,白白浪費帶寬也加重了下游的負載。

為了實現這個能力,在每次請求下游服務時,下游把當前服務的准入優先級返回給上游,上游維護下游服務的准入優先級,如果發現請求優先級達不到下游服務的准入門檻,直接丟棄,而不再請求下游,進一步減輕下游的壓力

圖片

總結

微信整個負載控制的流程如圖所示:

圖片

當用户從微信發起請求,請求被路由到接入層服務,分配統一的業務和用户優先級,所有到下游的字請求都繼承相同的優先級。根據業務邏輯調用1個或多個下游服務。當服務收到請求,首先根據自身服務准入優先級判斷請求是接受還是丟棄。服務本身根據負載情況週期性的調整准入優先級。當服務需要再向下游發起請求時,判斷本地記錄的下游服務准入優先級。如果小於則丟棄,如果沒有記錄或優先級大於記錄則向下遊發起請求。下游服務返回上游服務需要的信息,並且在信息中攜帶自身准入優先級。上游接受到返回後解析信息,並更新本地記錄的下游服務准入優先級。

整個過載保護的策略有以下三個特點:第一,業務無關的,使用請求等待時間而不是響應時間來制定用户和業務優先級,這些都與業務本身無關。第二,獨立控制和聯合控制結合,准入優先級取決於獨立的服務,但又可以聯合下游服務的情況,優化服務過載時的表現。第三,高效且公平。請求鏈條的優先級是一致的,並且會定時改變hash函數調整用户優先級。過載情況下,不會總是影響固定的用户。

你可能感興趣的騰訊工程師作品

| 由淺入深讀透vue源碼:diff算法

| 優雅應對故障:QQ音樂怎麼做高可用架構體系?

| QQ瀏覽器是如何提升搜索相關性的?

從Linux零拷貝深入瞭解Linux-I/O

技術盲盒:前端後端AI與算法運維工程師文化

公眾號後台回覆“微信”,領本文作者推薦參考資料。

閲讀原文