<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為什么面試官喜歡考我限流的知識

          共 5611字,需瀏覽 12分鐘

           ·

          2020-06-17 23:20


          本文公眾號來源:安琪拉的博客作者:安琪拉本文已收錄至我的GitHub

          在系統(tǒng)設(shè)計(jì)中,限流是保障系統(tǒng)高可用的一種常規(guī)手段,同樣的手段還有熔斷、服務(wù)降級等等。

          妲己:聽說最近你們系統(tǒng)又對接了幾條業(yè)務(wù)線,而且早上9.10點(diǎn)鐘流量非常大,你怎么保證系統(tǒng)不被搞掛?

          安琪拉:你算是問對了,最近對接了幾家大機(jī)構(gòu),同時(shí)由于疫情的影響,線上渠道的流量也比平常多了很多,我這邊系統(tǒng)做了很多系統(tǒng)優(yōu)化,大致可以歸為以下幾類:

          • 限流:對應(yīng)用入口流量做控制,瞬時(shí)流量向后遷移,對下游請求流量做自適應(yīng)限流,根據(jù)接口響應(yīng)時(shí)間動(dòng)態(tài)調(diào)整流量。
          • 延遲排隊(duì):如果請求量大,按業(yè)務(wù)線優(yōu)先級排隊(duì),優(yōu)先保障線上渠道實(shí)時(shí)的請求。
          • 路由:這個(gè)是因?yàn)闃I(yè)務(wù)的特殊性,所有的請求都依賴下游第三方的服務(wù),可以將多家下游服務(wù)供應(yīng)商做個(gè)動(dòng)態(tài)路由表,將請求優(yōu)先路由給接口成功率高、耗時(shí)低的服務(wù)供應(yīng)商;
          • 備份:這基本是所有分布式組件都會做的,能做多機(jī)的不做單機(jī),例如:Redis 做三主三備(集群)、MySQL分庫分表、MQ 與 Redis 互為備份(安琪拉遇到過MQ事故)等等;
          • 降級:這個(gè)是最后的迫不得已的措施,如果遇到全線崩潰,使用降級手段保障系統(tǒng)核心功能可用,或讓模塊達(dá)到最小可用。
          • 日志:完整的監(jiān)控和鏈路日志,日志功能很多,也分很多種,一方面是方便排查問題,另一方面可用來做任務(wù)重試、任務(wù)回滾、數(shù)據(jù)恢復(fù)、狀態(tài)持久化等。

          妲己:能給我講講限流的基礎(chǔ)概念嗎?

          安琪拉:限流,顧名思義,就是限制流量,一般分為限制入口流量和限制出口流量,入口流量是人家來請求我的系統(tǒng),我在入口處加了一道閥門,出口流量是我調(diào)外部系統(tǒng),我在出口加一道閥門。

          妲己:那一般怎么來實(shí)現(xiàn)限流呢?

          安琪拉:如果是單機(jī),可以通過Semphore 限制統(tǒng)一時(shí)間請求接口的量,也可以用 Google Guava 包提供的限流包,如果是分布式環(huán)境,可以使用 Redis 實(shí)現(xiàn),也有阿里 SentinalSpring Cloud Gateway 可以實(shí)現(xiàn)限流。我們先來看單機(jī)版本的限流,代碼如下:

          c6ce2f1ee96933cc43b9c7ba5e6f5a9a.webp

          這個(gè)代碼很好理解,一共就只有三個(gè)坑位,使用 Semaphore 定義,"妲己", "亞瑟", "魯班", "甄姬" 輪番上總共二十次廁所,Semaphore 就是鎖的機(jī)制,進(jìn)了廁所,在門上加鎖,看下控制臺輸出:

          158449c6e59d2fff3efcc440383a0f36.webp


          每次只有三個(gè)人能同時(shí)上廁所。

          妲己:我似乎有點(diǎn)明白了,廁所就是資源,我們上廁所就好比請求,大家一起上就產(chǎn)生了流量高峰,那分布式環(huán)境怎么解決呢?

          安琪拉:分布式環(huán)境的思想和單機(jī)的思想是一樣的,也是控制資源的訪問頻率,一般主流的設(shè)計(jì)思想有二種:

          1. 漏洞算法

            a1c90af8d456367aa0c011cf355ca0e6.webp


            把請求比作水,在請求入口和響應(yīng)請求的服務(wù)之間加一個(gè)漏桶,桶中的水以恒定的速度流出,這樣保證了服務(wù)接收到的流量速度是穩(wěn)定的,如果桶里的水滿了,再進(jìn)來的水就直接溢出(請求直接拒絕)。

            漏桶是網(wǎng)絡(luò)環(huán)境中流量整形(Traffic Shaping)或速率限制(Rate Limiting)時(shí)經(jīng)常使用的一種算法,它的主要目的是控制數(shù)據(jù)進(jìn)入到網(wǎng)絡(luò)的速率,平滑網(wǎng)絡(luò)上的突發(fā)流量。

          2. 令牌桶算法

            6bbf78700f77bd38ea6eac721399fbb8.webp


            令牌桶算法有點(diǎn)類似于生產(chǎn)者消費(fèi)者模式,專門有一個(gè)生產(chǎn)者往令牌桶中以恒定速率放入令牌,而請求處理器(消費(fèi)者)在處理請求時(shí)必須先從桶中獲得令牌,如果沒有拿到令牌,有二種策略:一種是直接返回拒絕請求,一種是等待一段時(shí)間,再次嘗試獲取令牌。

            令牌桶算法用來控制發(fā)送到網(wǎng)絡(luò)上的數(shù)據(jù)的數(shù)目,并允許突發(fā)數(shù)據(jù)的發(fā)送

            Google的Guava包中的RateLimiter類就是令牌桶算法的解決方案。

          對比一下這二種算法,其實(shí)無非是一個(gè)在出口處是以恒定的速率出水,一個(gè)是以恒定速率放令牌,安琪拉看來區(qū)分度不大,只是令牌桶算法更加靈活,往往實(shí)際工作中,可以實(shí)現(xiàn)動(dòng)態(tài)調(diào)整令牌的放入速度、以及令牌桶的總大小。

          妲己:為什么我看完覺得二種算法差不多?

          安琪拉:令牌桶相比漏桶有個(gè)優(yōu)勢,能夠滿足突發(fā)流量的請求。打個(gè)比方:如果線上環(huán)境資源很空閑,因?yàn)槁┒此鞒龅乃俣群愣?,請求因?yàn)樗俣仁芟薏粫皶r(shí)得到響應(yīng)。比如現(xiàn)在漏洞出水速度是 3個(gè)/秒,現(xiàn)在線上來了5個(gè)請求,全部進(jìn)漏桶,漏桶里面現(xiàn)在一共只有5個(gè)請求,但是也只能一秒處理 3 個(gè)(出水速度限制)。但是如果是令牌桶算法,放入令牌的速度是 3個(gè)/秒,假設(shè)令牌桶中已經(jīng)有二個(gè)令牌了,這時(shí)來了5個(gè)請求,都能拿到令牌完成請求,因此令牌桶算法是面向請求的(請求主動(dòng)拿令牌,按需分配),而漏洞則是面向令牌,我以恒定的速度出水,才不管你有多少請求。

          妲己:我明白了,那你給我講講 Google Guava怎么實(shí)現(xiàn)令牌桶算法的?

          安琪拉:明白了思想之后,很容易理解實(shí)現(xiàn),我們來看一下源代碼:

          f907dae2d2ebd25c272ac3d1c2e3e2bf.webp


          API 很簡單,只需要指定限流的速度,例如第一個(gè), 速度是每秒鐘2個(gè),如果是分鐘級限流,你也可以設(shè)置為 0.2,代表1秒鐘生成0.2 個(gè)令牌,1分鐘限流為 12個(gè)。第二個(gè)例子是每秒鐘5000,這個(gè)例子演示了如何通過限流器限制網(wǎng)絡(luò)處理流量為每秒鐘 5kb。5000個(gè)byte。

          Guava 還有很多方法,如下:

          返回值和方法修飾符 方法和描述
          double acquire()
          從RateLimiter獲取一個(gè)許可,方法會被阻塞直到獲取到請求
          double acquire(int permits)
          從RateLimiter獲取指定許可數(shù),方法會被阻塞直到獲取到請求
          static RateLimiter create(double permitsPerSecond)
          根據(jù)每秒放到令牌桶數(shù)量創(chuàng)建RateLimiter,這里的令牌桶數(shù)量是指每秒生成令牌數(shù)(通常是指QPS,每秒多少查詢)
          static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)
          根據(jù)每秒放到令牌桶數(shù)量和預(yù)熱期來創(chuàng)建RateLimiter,意思是不會一下生成全部的令牌,把令牌桶塞滿,而是會漸進(jìn)式的增加令牌,這里的每秒放到令牌桶數(shù)量是指每秒生成令牌數(shù)(通常是指QPS,每秒多少個(gè)請求量),在這段預(yù)熱時(shí)間內(nèi),RateLimiter每秒分配的許可數(shù)會平穩(wěn)地增長直到預(yù)熱期結(jié)束時(shí)達(dá)到最大。
          double getRate()
          返回RateLimiter 配置中的穩(wěn)定速率,該速率單位是每秒生成多少令牌數(shù)
          void setRate(double permitsPerSecond)
          更新RateLimite的穩(wěn)定速率,參數(shù)permitsPerSecond 由構(gòu)造RateLimiter的工廠方法提供。
          boolean tryAcquire()
          從RateLimiter 獲取許可,如果該許可可以在無延遲下的情況下立即獲取得到的話
          boolean tryAcquire(int permits)
          從RateLimiter 獲取許可數(shù),如果該許可數(shù)可以在無延遲下的情況下立即獲取得到的話
          boolean tryAcquire(int permits, long timeout, TimeUnit unit)
          從RateLimiter 獲取指定許可數(shù)如果該許可數(shù)可以在不超過timeout的時(shí)間內(nèi)獲取得到的話,或者如果無法在timeout 過期之前獲取得到許可數(shù)的話,那么立即返回false (無需等待)

          妲己:那我們來看一下分布式限流吧。

          安琪拉:分布式其實(shí)就是把本地令牌桶放到一個(gè)所有主機(jī)都可以訪問的地方。

          妲己:一般放哪里比較合適。

          安琪拉:分布式中間件,例如 Redis,分布式緩存,生成令牌和獲取令牌都可以用redis指令實(shí)現(xiàn),而且速度還快。

          妲己:那你快給我講講怎么實(shí)現(xiàn)。

          安琪拉:那我說下我的實(shí)現(xiàn)步驟和背景吧。在令牌桶算法中,有一個(gè)單獨(dú)的生產(chǎn)者以恒定的速率向令牌桶中放入令牌,如果通過redis實(shí)現(xiàn),一個(gè)生產(chǎn)者線程不斷往redis添加令牌(寫),其他請求線程每次請求讀redis獲取令牌,這樣會有很大的性能損耗,好的解決辦法是延遲放令牌的操作,獲取令牌的時(shí)候才放入令牌,將二個(gè)操作合并。

          妲己:那獲取令牌的時(shí)候怎么計(jì)算應(yīng)該放桶中放入多少令牌呢?

          安琪拉:嗯,這是個(gè)好問題,filledTokens 為這一次需要放入令牌的數(shù)量,計(jì)算邏輯為:

          此刻應(yīng)填充令牌數(shù) =  min((令牌桶剩余令牌數(shù) + 當(dāng)前時(shí)間與上一次令牌生成時(shí)間間隔 * 令牌生成速度), 令牌總?cè)萘?

          根據(jù)上面的思路,寫一個(gè)分布式限流的Redis腳本,redis提供lua支持,腳本如下:

          6c4a63c66ebd77ac194c8bbc0248f087.webp



          妲己:這個(gè)寫好之后怎么測試 ?

          安琪拉:關(guān)注Wx 公眾號:安琪拉的博客,  我教你!Redis 提供了客戶端加載工具可以方便lua 腳本的調(diào)試,如下所示:

          5ce02f1a54cab7ed092a3aac9b7e8aed.webp


          如下圖所示:可以輸入help 查看完整命令,常用n和print,分別為下一行和打印當(dāng)前局部變量


          ab9b9a4532447c54f84dcf011b54a795.webp[(img-KHHPnrUg-1586806007625)(/Users/zw/Library/Application Support/typora-user-images/image-20200414025001967.png)]


          另外也可以直接通過script load 命令加載redis lua腳本,得到sha1 之后直接運(yùn)行(這個(gè)是模型真實(shí)程序運(yùn)行模式,可以暫時(shí)跳過)。


          // 1. 在redis服務(wù)端load 腳本 拿到sharedis-cli script load "$(cat ratelimit.lua)"//sha1: ebbcd2ed99990afaca6d2ba61a0f2d5bdd907e59// 2. 通過腳本 sha1 值運(yùn)行腳本redis-cli evalsha ebbcd2ed99990afaca6d2ba61a0f2d5bdd907e59 2 remain.${0}.tokens last_fill_time 0.2 12 `gdate +%s%3N` 1
          41c47c1abf7209e348bd721f0bd7b6fc.webp在這里插入圖片描述


          妲己lua 腳本的執(zhí)行會不會有性能上的損耗,比較redis是單線程的?

          安琪拉:redis 使用 epoll 實(shí)現(xiàn)I/O多路復(fù)用的事件驅(qū)動(dòng)模型,對于每一個(gè)讀取和寫入操作都盡量要快速,所以我們需要對編寫的lua 腳本做個(gè)壓測,redis 提供了壓測指令 redis-benchmark, 測試10萬 腳本的執(zhí)行,命令如下:


          redis-benchmark -n 100000 evalsha ebbcd2ed99990afaca6d2ba61a0f2d5bdd907e59 2 remain.${1}.tokens last_fill_time 0.2 12 `gdate +%s%3N` 1


          實(shí)際效果如下:a8a3005f98b1b51fec385db04cf8b5a6.webp

          99.9%都在 2ms以內(nèi)完成,每秒鐘執(zhí)行4萬5千多次,因此損耗可以接受。

          妲己:怎么把分布式限流lua 放到Spring boot工程中呢?

          安琪拉:下面我們就開始工程化之路,

          首先

          1. 手寫一個(gè)lua 腳本(上面的腳本直接拷貝),在Spring 工程目錄中放好,如下圖;

            e2334fd9c4a0b83cedf79d36b567142a.webp


          2. 程序啟動(dòng)時(shí)加載lua 腳本, 根據(jù)lua的 SHA1值判斷腳本是否已經(jīng)加載到redis( redis 不能存太多的script),程序如下:

          b97d5ab38e5563238b95e34ed8fdef55.webp

          這里程序啟動(dòng),加載腳本,檢查腳本在redis中是否存在,腳本如果沒有重新編輯更新,sha1是一致的,不會重復(fù)加載,另外注意一點(diǎn),如果是集群模式,Jedis 3.*版本以前不支持lua腳本,建議使用Lettuce。

          1. 關(guān)于Lettuce 和 Jedis 客戶端的對比,大家可以網(wǎng)上看一下,Spring Boot最新默認(rèn)客戶端已經(jīng)改成Lettuce了。

            EvalSha is not supported in cluster environment

          2. 配置限流器

          0beb0ee218758de8a9aa05c4e9c37957.webp

          這里有一點(diǎn)需要注意一下,key 都帶了 {1} 的前綴,這個(gè)用于所有key 在集群模式都hash 命中同一個(gè)slot (槽),因?yàn)閘ua 腳本不能跨集群節(jié)點(diǎn)執(zhí)行。


          看一下效果,舒服...:0cd86f7e39c89bd288305ebf9c09ea47.webp


          各類知識點(diǎn)總結(jié)

          下面的文章都有對應(yīng)的原創(chuàng)精美PDF,在持續(xù)更新中,可以來找我催更~

          掃碼或者微信搜Java3y 免費(fèi)領(lǐng)取原創(chuàng)思維導(dǎo)圖、精美PDF。在公眾號回復(fù)「888」領(lǐng)取,PDF內(nèi)容純手打有任何不懂歡迎來問我。


          
           

          原創(chuàng)電子書
          d99641387ea9325bc02276ffa4df1172.webp

          原創(chuàng)思維導(dǎo)圖

          2bf9624c8d2f3d85f84cd9a16826cf72.webp


          afcc957ce4efc2c6675b0cf60f4e2e94.webp

          1ac166f6d3cc2821cf56d4edc6f6fd42.webp

          1ac166f6d3cc2821cf56d4edc6f6fd42.webp

          瀏覽 73
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日皮视频在线观看免费 | 国产在线拍揄自揄拍无码视频 | 高清国产mv在线观看 | 免费视频无码 | 免费在线观看亚洲天堂 |