架構師成長之路之限流漫談

2020-12-15 酷扯兒

本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫

1. 我們為什麼需要限流

在上一篇架構師成長之路之服務治理漫談裡面,我們已經談到了高可用治理的部分。為了「反脆弱」,在微服務複雜拓撲的情況下,限流是保障服務彈性和拓撲健壯的重中之重。

想一想,如果業務推出了一個秒殺活動,而你沒有任何的限流措施;當你搭建了一個帳號平臺,而完全沒有對十幾個業務方設定流量配額……這些很有可能在特定場合下給你的產品帶來大量的業務損失和口碑影響。

我們通常重點關注產品業務層面正向和逆向功能的完成,而對於逆向技術保障,這一點則是企業發展過程中很容易忽視的,所以一旦業務快速增長,這將給你的產品帶來很大的隱患。

當然,也不是所有的系統都需要限流,這取決於架構師對於當前業務發展的預判。

2. 我們常見的限流手段

我們來列舉業內比較常見的一些限流手段。

2.1 信號量計數

信號量競爭是用來控制並發的一個常見手段。比如 C 和 Java 中都有 Semaphore 的實現可以讓你方便地上手。鼎鼎大名的彈性框架 Hystrix 也默認選擇了信號量來作為隔離和控制並發的辦法。它的優點即在於簡單可靠,但是只能在單機環境中使用。

2.2 線程池隔離

隔離艙技術中也大量使用了線程池隔離的方式來實現,通過限制使用的線程數來對流量進行限制,一般會用阻塞隊列配合線程池來實現。如果線程池和隊列都被打滿,可以設計對應拒絕策略。需要謹慎調整其參數和線程池隔離的個數,以避免線程過多導致上下文切換帶來的高昂成本。也是基於這個考慮,Hystrix 默認採用了信號量計數的方式來控制並發。同樣,其也只能在單機環境中使用。

2.3 固定窗口計數

我們可以以第一次請求訪問的時候開始進行計數,而不嚴格按照自然時間來計數。比如可以利用 Redis 的 INCR 和 EXPIRE 組合進行計數,如下偽代碼所示:

count = redis.incrby(key)if count == 1redis.expire(key,3600)if count >= threshold println("exceed...")

這種實現方式簡單粗暴,可以解決絕大部分分布式限流的問題。但是其存在的問題是:

該計數方式並不是準確計數,由於時間窗口一旦過期,則之前積累的數據就失效,這樣可能導致比如本來希望限制「一分鐘內訪問不能超過 100 次」,但實際上做不到精準的限制,會存在誤判放過本應拒絕的流量。每次請求都將訪問一次 Redis,可能存在大流量並發時候將緩存打崩最終拖垮業務應用的問題。這個在高並發場景中是非常嚴重的問題。當然,你可以選擇按照業務進行適當的緩存集群切割來緩解這種問題,但是這仍然是治標不治本。當然,如果你選擇單機限流的實現方式,則無需使用 Redis,進一步,單機限流情況下該問題不存在。2.4 自然窗口計數

有的場景會需要以自然窗口為維度進行限制,實現方式即進行分桶計數。每個 slot 一般以時間戳為 key salt,以單 slot 時間長度內的計數值為 Value,我們可以根據實際的需求對單 slot 的時間長度進行限制,比如如果你需要限制一天發送簡訊數不超限,則以 1 個自然天為 1 個 slot,如果希望限制 QPS,則以 1s 為 1 個 slot。然後起定時任務獲取 slot,進一步取出實際的分桶計算結果,進行判斷是否達到閾值,如果超過閾值則執行對應的限制操作。

該策略如果應用在分布式限流環境下,則會碰到若干個問題。這個後面章節中會提到。另外,該策略本質上其實是也是一種特殊的固定窗口計數策略,那麼固定窗口所存在的弊端,自然窗口計數也會存在。那麼我們不禁會問,如果希望規避固定窗口的一大問題——「無法準確計數」的話,要怎麼做呢?這時,「滑動窗口計數」方式應運而生。

2.5 滑動窗口計數

滑動窗口的出現,可以很好地解決精準計數的問題。隨著時間窗口不斷地滑動,動態地進行計數判斷。可以規避自然窗口和固定窗口計數所存在的計數不準確的問題。以下有兩種常見的滑動窗口計數的實現類別。

2.5.1 基於共享分布式內存

可以採用 Redis ZSet,存儲結構如下圖所示。Key 為功能 ID,Value 為 UUID,Score 也記為同一時間戳。整個過程簡單概括為「添加記錄、設置失效時間、計數、刪除過期記錄」四部分。使用 ZADD、EXPIRE、ZCOUNT 和 zremrangeScore 來實現,並同時注意開啟 Pipeline 來儘可能提升性能。

偽代碼如下:

// 開啟pipepipeline = redis.pielined()// 增加一條請求pipeline.zadd(key, getUUID(), now)// 重新設置失效時間pipeline.expire(key, 3600)// 統計在滑動窗口內,有多少次的請求count = pipeline.zcount(key, expireTimeStamp, now)// 刪除過期記錄pipeline.zremrangeByScore(key, 0, expireTimeStamp - 1)pipeline.sync()if count >= thresholdprintln("exceed")

但是該方法,有一個比較突出的問題。就是這是一個重操作,將引發高 QPS 下 Redis 的性能瓶頸,也將消耗較多的資源和時間。一般我們可以付出秒級的時延,對其做多階段異步化的處理。比如將計數、刪除過期數據和新增記錄分為三部分去進行異步處理。此處就不進一步展開了。

2.5.2 基於本地內存

第一個方案中,分布式滑動窗口的難度在於,不得不進行內存共享來達到窗口計數準確的目的。如果考慮分發時進行 Key Based Routing 是不是能解決這個問題?在付出非冪等、複雜度抬升等一定代價的情況下,引入基於本地內存的分布式限流實現方式。

實現方式有如下兩種:

如果可以接受準實時計算的話,可以採用 Storm,使用 filedsGroup,指定 Key 到對應的 Bolt 去處理;如果需要實時計算的話,那麼就採用 RPC 框架的 LB 策略為指定 Key 的一致性 Hash。然後路由到對應的服務實例去處理。以上兩個實現方式,當到達 Bolt 或者服務實例後,即可基於本地內存進行處理,處理方式也有三種。

採用 Esper,用 DSL 語句即可簡單實現滑動窗口。Storm 1.0 之後提供了滑動窗口的實現。如果希望自實現滑動窗口(不推薦),實現思路也比較簡單即:循環隊列+自然窗口滑動計數。循環隊列來解決無限後延的時間裡,計數空間重複利用的問題。而此處,我們看到了一個熟悉的名詞——「自然窗口計數」。沒錯,底層仍然採用自然窗口計數,但是區別在於,我們會對自然窗口切分更細的粒度,每次批量超前獲取多個分桶,來進行加和計算。這樣就可以實現滑動窗口的效果,你可以認為,當分桶被細化到 10s、5s 甚至越來越細的時候,計數將趨近於更加準確。

2.6 令牌桶和漏桶算法計數

令牌桶的示意圖如下:

而漏桶的示意圖如下:

這個在業內也是鼎鼎大名。基本談起限流算法,這兩個算法必然會被提起,令牌桶可以有流量應對突發流量,漏桶則強調對流量的整型。二者的模型是相反的。令牌桶和漏桶算法在單機限流中較為常見,而在分布式限流中罕見蹤跡。

對於令牌桶來說,你可以採用定時任務去做投遞令牌的動作,也可以採用算法的方式去進行簡單的計算。Guava Ratelimiter 採用的是後者。

令牌桶的優勢之一,在於可以有部分餘量用以應對突發流量。但是在實際生產環境中,這不一定是安全的。如果我們的服務沒有做好應對更高突發流量的準備,那麼很有可能會引發服務雪崩。所以考慮到這一點,Guava 採用了令牌桶 + 漏桶結合的策略來進行限流。對於默認業務,採用標準令牌桶方式進行「可超支」限速,而對於無法突然應對高峰流量的業務,會採用緩慢提升投放令牌速率(即逐步縮短業務請求等待時間)的方式來進行熱啟動控制,具體見 Guava Ratelimiter 源碼注釋描述,此處不贅述,其效果如下圖所示:

3. 微服務限流幾個考慮的點

以上的限流手段,有的能應用在單機環境,有的能應用在分布式環境。而在高並發的分布式環境中,我們需要考慮清楚如下幾個問題如何解決。

3.1 機器時鐘不一致或者時鐘回退問題

一旦出現這種問題,則可能導致收集的數據相互汙染而導致判斷出錯。所以一方面,在運維層面需要確保機器時鐘能夠按期同步。另一方面,需要有準實時檢測的手段,及時發現時鐘偏差太大或者時鐘回退的機器,基於一定策略篩選出不合格的數據來源,將其刨除出計算範圍並發出警告。

3.2 在 SDK 還是 Server 端做限流邏輯

你需要考慮你的限流策略迭代的頻繁程度,推動業務方改造的成本,語言/技術棧異構情況,是否有需要進行立多系統聯合限流的場景,以此來進行決策。如果採用 SDK 方式,你需要做好碰到這幾個棘手問題的心理準備。

而如果採用 Server 方式,你則需要更多考慮高並發下數據堆積,機器資源消耗,以及對業務方性能的影響問題。一般業內採用的是富 SDK 的方式來做,但是對於上述的 SDK 會面臨的幾個問題沒有很好的解決方案。而 ServiceMesh 領軍人物 Istio 採用了 Mixer 來實現 Server 端限流的方式,但是碰到了很嚴重的性能問題。所以這是一個很困難的選擇。

回顧下架構師成長之路之服務治理漫談一篇中所講到的服務治理發展路徑,是不是有點驚人的相似?是不是也許限流的未來,不在 SDK 也不在 Server,而在於 ServiceMesh?我不確定,但我覺得這是一個很好的探索方向。

3.3 限流是不是會讓你的系統變得不可控

這是一個很有意思的問題,限流本身是為了「反脆弱」而存在的,但是如果你的分布式複雜拓撲中遍布限流功能,那麼以後你每個服務的擴容,新的功能上線,拓撲結構的變更,都有可能會導致局部服務流量的驟增,進一步引發限流導致業務有損問題。這就是「反脆弱」的本身也有可能會導致「脆弱」的出現。所以,當你進行大規模限流能力擴張覆蓋的時候,需要謹慎審視你的限流能力和成熟度是否能夠支撐起如此大規模的應用。

3.4 拓撲的關聯性能給限流帶來什麼

我們置身於複雜服務拓撲和各種調用鏈路中,這一方面確實給限流帶來了很大的麻煩,但另一方面,我們是不是可以思考一下,這些複雜度,本身是不是可以帶給我們什麼樣的利好?比如:底層服務扛不住,那麼是不是可以在更上層的調用方入口進行限流?如此是不是可以給予用戶更友好提示的同時,也可避免鏈路上服務各自限流後帶來的系統級聯處理壓力?微服務的本質是自治沒錯,但是我們是不是可以更好地對各個服務的限流自治能力進行編排,以達到效率、體驗、資源利用的優化?

相信大家都會有自己的答案。這件事情本身的難度是在於決策的準確性,但如果能很好地進行落地實現,則意味著我們的限流從自動化已經逐步轉向了智能化。這也將是更高一層次的挑戰和機遇。

3.5 準確性和實時性的權衡

在高並發限流場景下,準確性和實時性理論上不可兼得。在特定的場景中,你需要作出你的選擇,比如前文介紹的基於 Redis ZSet 實現的滑動窗口實時計算方式可以滿足實時性和準確性,但其會帶來很明顯的性能問題。所以我們需要作出我們的權衡,比如犧牲準確性將滑動窗口退化為固定窗口來保障性能;或者犧牲實時性,對滑動窗口多階段去做異步化,分析和決策兩階段分離,來保障性能。這取決於你的判斷。

4. 總結

限流是高可用治理中核心的一環,實現方式也五花八門,每種方式也都有各自的問題,本文只是做了一個簡單的回顧。希望隨著 ServiceMesh、AIOps 等理論的興起,我們對於限流是什麼,能做什麼,怎麼實現,能夠釋放出更大的空間去想像。

相關焦點

  • 架構師成長之路:分布式系統綜述
    作為一個資深架構師,一路走來,發現自己的技術水平很多時候其實是隨著項目的發展被迫成長的。其實,很多時候,自身水平達不到能順利完成架構項目的水平,但是,為了挑戰,為了技術成長,更是為了高薪資,只能咬牙堅持,熬夜學習,最終讓自己能順利設計和把控項目的架構。其中,最為艱難的,就是去設計、架構、規劃一整套,規模大的分布式系統。
  • 英特爾與Science聯袂推出「架構師成長計劃」
    你正準備成為架構師嗎?或者你想成為優秀的架構師嗎?祝賀你,你已在路上!英特爾推出架構師成長計劃:構建未來,智者更強 為此英特爾聯合國際學術期刊《科學》(Science/AAAS) 首度聯袂推出「架構師成長計劃」在線系列課程,共同為架構師修築成就優秀之路,助力架構師構建未來,讓智者更強。
  • Science和英特爾聯袂推出「架構師成長計劃」
    架構理論思維亟待提升,業務發展需參照實踐案例,若只聽一家之言,難得到全面的成長。為此,Science/AAAS聯合英特爾(Intel)推出「架構師成長計劃」,將最前沿的科學技術與最普世的產業案例相結合,修築通往優秀架構師之路,一起構建未來,讓智者更強。
  • 阿里P8大神書寫軟體架構師成長之路,總結這份419頁學習筆記
    「軟體架構師成長之路」系列教程歷時十年才陸續問世,之所以花費這麼長的時間,- -是因為涉及的知識與技巧非常多,二是體現了創作過程的「 工匠精神」一作者除 了傾儘自己的全力,本著務實嚴謹、精益求精的態度來創作,同時也時時思考:應該寫哪些?不應該寫哪些?系列教材內容廣度與深度如何?應該以怎樣的形式進行編排?如何貫穿前後知識點?如何體現綜合技能?
  • 2020年JAVA進階架構師 540篇優質文章整理
    【面經】慌了,面試居然被問到怎麼做高並發系統的限流?我掛樹上了:一道樹的面試題面試官:聊聊微信和淘寶掃碼登錄背後的實現原理?Java進階架構師都看到這裡了,還不關注下?!(3)「架構技術專題」架構核心指標之可擴展架構設計的三要素(4)「架構技術專題」9種高性能高可用高並發的技術架構(5)「架構技術專題」構建網站高可用架構(詳細分析篇)(6)「架構技術專題」超詳細網站伸縮性架構的設計(7)「架構技術專題」總結:共計7篇闡述架構技術之美--------------
  • 鋪平你的架構師之路!十年技術專家敬獻Java架構完美設計筆記
    寫在前面軟體架構師是每個程式設計師職業生涯中內功心法修煉的終極目標。當然要達到這個目標,一般並不簡單,你需要具備「十八般武藝」,而且還要融匯各家所長。那麼,該如何更好的理解架構呢?從形上看,架構是系統結構的骨架,支撐和連接各個部分;從神上看,架構是系統設計的靈魂,深刻體現了業務技術實現的本質。
  • 架構師成長計劃」牽手百度,聚焦未來數據中心基礎架構
    為此對於擔綱重要任務的架構師更是需要緊跟技術與市場趨勢,follow技術「網紅」,努力成為技術潮人。為此,Science與英特爾首度聯袂推出「架構師成長計劃」在線系列課程,共同為架構師修築成就優秀之路,助力架構師構建未來,讓智者更強。
  • java架構師指南:成為java架構師之後該怎麼走
    作為一名java架構師,首先要將自己的主要職責概括為三個「負責」,即為新系統的架構設計、舊系統的架構演進負責;為業務的技術支撐負責;為團隊新人的成長負責;結合多年經驗,小編將java架構師之路分為三個階段:
  • JAVA架構師成長路線,你準備好了嗎
    寫作是對自己學習的總結和記錄,如果您對 Java、分布式、微服務、中間件、Spring Boot、Spring Cloud等技術感興趣,可以關注我的動態,我們一起學習,一起成長!用知識改變命運,讓家人過上更好的生活,網際網路人一家親!
  • 如何從軟體開發人員成長為軟體架構師
    軟體架構師是一位軟體專家,負責對給定的數字產品做出有關系統設計,基礎結構和技術標準(包括語言,工具和平臺)的行政決策。軟體架構師設定願景並監督系統的構建。 此外,軟體架構師應該能夠共享技術遠景和技術指導,並根據軟體項目的要求進行計劃。另一方面,軟體架構師應該知道他們將用來構建系統的學科,例如,開發環境,DevOps的設計,甚至方法論和最佳實踐。
  • Mobvista蔡超做客QCon+案例研習社,分享優秀架構師成長攻略
    還在為如何成為優秀架構師而苦惱嗎?近日,匯量科技(Mobvista)集團副總裁兼首席工程架構師蔡超做客QCon+案例研習社線上發布會,為我們帶來了答案。基於多年實戰經驗,蔡超總結並分享了他在架構師成長之路上的8大竅門,為同行們帶來更多思考。
  • 華為18級大牛整理Java代碼架構優化實戰成就架構師夢想之路
    然而Java體系龐大、技術精深,如何寫出優質代碼,如何設計與優化系統架構,是高級開發者必須掌握的核心技術之一。(高質量代碼)、完美突破(架構師之路),這六項密技是完美編程的精髓,亦是完美編程的指導思想與靈魂。
  • 來自阿里P7十年架構師成長匠心之作「內含架構學習路線圖」
    關於Java架構師的學習路線,學習方法都很迷茫,由於也是在校學生,實戰機會也比較少,希望得到一些建議。其實在國內軟體發展並不像美國那樣長久地發展,而是在網際網路時代集中爆發,架構師已經和初始的含義有所變化,目前,在國內架構師 = 真正含義上的 架構師+首席工程師。
  • 深入阿里總結的最完整架構師成長路徑
    程式設計師的成長過程程序小白第一階段:剛畢業到公司,每天就是打雜,總做一些簡單又重複的事情,這就是程式設計師所說的「搬磚工」。搬磚工其實沒什麼,成長都是從這裡開始,當你多年以後,回過頭看,你會發現曾經搬的磚其實對自己很有用。
  • 京東技術官分享Java架構師成長筆記,帶你一步一腳印修成正果
    關於著作軟體架構師是每個程式設計師職業生涯中內功心法修煉的終極目標。要達到這個目標需要具備「十八般武藝,八十種技巧」,本書正是優秀軟體架構師的一本必讀書。本書總結了JavaEE軟體架構師應該具備的架構設計相關技能體系,希望可以成為程式設計師朋友們架構師成長之路上的鋪路石。從形上看,架構是系統結構的骨架,支撐和連接各個部分;從神上看,架構是系統設計的靈魂,深刻體現了業務技術實現的本質。
  • 什麼樣的架構師修煉之道文檔,才能幫助大家修煉成為最出色的架構師?
    前言 卓越的軟體架構師從何而來? 所有程式設計師都有成為架構師的潛力,只要掌握了架構師的思維方式和工作方法,你也能成長為架構師。 本文教你如何像架構師那樣思考問題、理解需求、設計架構、評估結果、編寫文檔。
  • 自從讀了字節技術總監的架構師成長指南,面試像開掛「百發百中」
    最近有幸在一位字節跳動技術總監手裡扒到了這份架構師成長指南,將部分知識章節發布到了在知乎上竟然獲得了5000+點讚!對標網際網路「年薪60W+」的架構師成長路線指南1.BATJ面試攻略教程合集(2020版)這份面試攻略教程是由六位十餘年一線網際網路Java行業資深架構師共同分享的面試教程(非常的珍貴,其中包括:簡歷優化/包裝,面試攻略...)
  • 人人都是架構師:架構是一種能力,不是頭銜
    架構是一種能力,它不是頭銜。 換句話說,我們需要具備架構能力,但不一定要成為架構師。就像鄧公,他被稱為改革開放的總設計師,但他不是設計師。既然這樣,那我們還需要架構師嗎?還需要架構部門嗎?我給出的答案是:不需要,因為每個人都應該是架構師。
  • 35 歲了,終於成為架構師了
    在被任命為架構師之後,不會手足無措把事情搞砸,而是能夠有條不紊開展工作,打好你的架構設計第一仗。更好的理解架構設計,成長為一個架構師,主要的困難在於:你是否在編程這件事上表現出足夠的優秀,從而獲得做架構的機會。你是否掌握架構設計的一般方法,能夠把握好做架構設計的機會。
  • 首次分享:阿里P8架構師的學習筆記與歷程
    今天小編把自己的一位朋友如何從職場菜鳥奮鬥至阿里P8架構師的故事分享給大家:小編還特意翻了翻去年和大佬的聊天記錄,現在重新再看,只能說太勵志了!如果你覺得大佬是一年就能夠練成的,那你就太天真了,水滴石穿,非一日之功