日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

rateLimiter令牌桶限流算法

發(fā)布時間:2024/2/28 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 rateLimiter令牌桶限流算法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

RateLimiter是guava提供的基于令牌桶算法的實現(xiàn)類,可以非常簡單的完成限流特技,并且根據(jù)系統(tǒng)的實際情況來調(diào)整生成token的速率。

通??蓱糜趽屬徬蘖鞣乐箾_垮系統(tǒng);限制某接口、服務單位時間內(nèi)的訪問量,譬如一些第三方服務會對用戶訪問量進行限制;限制網(wǎng)速,單位時間內(nèi)只允許上傳下載多少字節(jié)等。

下面來看一些簡單的實踐,需要先引入guava的maven依賴。

一 有很多任務,但希望每秒不超過N個

[java]?view plaincopyprint?
  • import?com.google.common.util.concurrent.RateLimiter;??
  • ??
  • import?java.util.ArrayList;??
  • import?java.util.List;??
  • import?java.util.concurrent.ExecutorService;??
  • import?java.util.concurrent.Executors;??
  • ??
  • /**?
  • ?*?Created?by?wuwf?on?17/7/11.?
  • ?*?有很多個任務,但希望每秒不超過X個,可用此類?
  • ?*/??
  • public?class?Demo1?{??
  • ??
  • ????public?static?void?main(String[]?args)?{??
  • ????????//0.5代表一秒最多多少個??
  • ????????RateLimiter?rateLimiter?=?RateLimiter.create(0.5);??
  • ????????List<Runnable>?tasks?=?new?ArrayList<Runnable>();??
  • ????????for?(int?i?=?0;?i?<?10;?i++)?{??
  • ????????????tasks.add(new?UserRequest(i));??
  • ????????}??
  • ????????ExecutorService?threadPool?=?Executors.newCachedThreadPool();??
  • ????????for?(Runnable?runnable?:?tasks)?{??
  • ????????????System.out.println("等待時間:"?+?rateLimiter.acquire());??
  • ????????????threadPool.execute(runnable);??
  • ????????}??
  • ????}??
  • ??
  • ????private?static?class?UserRequest?implements?Runnable?{??
  • ????????private?int?id;??
  • ??
  • ????????public?UserRequest(int?id)?{??
  • ????????????this.id?=?id;??
  • ????????}??
  • ??
  • ????????public?void?run()?{??
  • ????????????System.out.println(id);??
  • ????????}??
  • ????}??
  • ??
  • }??
  • 該例子是多個線程依次執(zhí)行,限制每2秒最多執(zhí)行一個。運行看結(jié)果
    我們限制了2秒放行一個,可以看到第一個是直接執(zhí)行了,后面的每2秒會放行一個。
    rateLimiter.acquire()該方法會阻塞線程,直到令牌桶中能取到令牌為止才繼續(xù)向下執(zhí)行,并返回等待的時間。

    二 搶購場景限流

    譬如我們預估數(shù)據(jù)庫能承受并發(fā)10,超過了可能會造成故障,我們就可以對該請求接口進行限流。
    [java]?view plaincopyprint?
  • package?com.tianyalei.controller;??
  • ??
  • import?com.google.common.util.concurrent.RateLimiter;??
  • import?com.tianyalei.model.GoodInfo;??
  • import?com.tianyalei.service.GoodInfoService;??
  • import?org.springframework.web.bind.annotation.RequestMapping;??
  • import?org.springframework.web.bind.annotation.RestController;??
  • ??
  • import?javax.annotation.Resource;??
  • ??
  • /**?
  • ?*?Created?by?wuwf?on?17/7/11.?
  • ?*/??
  • @RestController??
  • public?class?IndexController?{??
  • ????@Resource(name?=?"db")??
  • ????private?GoodInfoService?goodInfoService;??
  • ??
  • ????RateLimiter?rateLimiter?=?RateLimiter.create(10);??
  • ??
  • ????@RequestMapping("/miaosha")??
  • ????public?Object?miaosha(int?count,?String?code)?{??
  • ????????System.out.println("等待時間"?+?rateLimiter.acquire());??
  • ????????if?(goodInfoService.update(code,?count)?>?0)?{??
  • ????????????return?"購買成功";??
  • ????????}??
  • ????????return?"購買失敗";??
  • ????}??
  • ??
  • ??
  • ??
  • ????@RequestMapping("/add")??
  • ????public?Object?add()?{??
  • ????????for?(int?i?=?0;?i?<?100;?i++)?{??
  • ????????????GoodInfo?goodInfo?=?new?GoodInfo();??
  • ????????????goodInfo.setCode("iphone"?+?i);??
  • ????????????goodInfo.setAmount(100);??
  • ????????????goodInfoService.add(goodInfo);??
  • ????????}??
  • ??
  • ????????return?"添加成功";??
  • ????}??
  • }??
  • 這個是接著之前的文章(秒殺系統(tǒng)db,http://blog.csdn.net/tianyaleixiaowu/article/details/74389273)加了個Controller 代碼很簡單,就是請求過來時,調(diào)用RateLimiter.acquire,如果每秒超過了10個請求,就阻塞等待。我們使用jmeter進行模擬100個并發(fā)。 創(chuàng)建一個線程數(shù)為100,啟動間隔時間為0的線程組,代表100個并發(fā)請求。


    啟動jmeter請求,看控制臺結(jié)果



    初始化10個的容量,所以前10個請求無需等待直接成功,后面的開始被1秒10次限流了,基本上每0.1秒放行一個。

    三 搶購場景降級

    上面的例子雖然限制了單位時間內(nèi)對DB的操作,但是對用戶是不友好的,因為他需要等待,不能迅速的得到響應。當你有1萬個并發(fā)請求,一秒只能處理10個,那剩余的用戶都會陷入漫長的等待。所以我們需要對應用降級,一旦判斷出某些請求是得不到令牌的,就迅速返回失敗,避免無謂的等待。 由于RateLimiter是屬于單位時間內(nèi)生成多少個令牌的方式,譬如0.1秒生成1個,那搶購就要看運氣了,你剛好是在剛生成1個時進來了,那么你就能搶到,在這0.1秒內(nèi)其他的請求就算白瞎了,只能寄希望于下一個0.1秒,而從用戶體驗上來說,不能讓他在那一直阻塞等待,所以就需要迅速判斷,該用戶在某段時間內(nèi),還有沒有機會得到令牌,這里就需要使用tryAcquire(long timeout, TimeUnit unit)方法,指定一個超時時間,一旦判斷出在timeout時間內(nèi)還無法取得令牌,就返回false。注意,這里并不是真正的等待了timeout時間,而是被判斷為即便過了timeout時間,也無法取得令牌。這個是不需要等待的。
    看實現(xiàn):
    [java]?view plaincopyprint?
  • /**?
  • ?????*?tryAcquire(long?timeout,?TimeUnit?unit)?
  • ?????*?從RateLimiter?獲取許可如果該許可可以在不超過timeout的時間內(nèi)獲取得到的話,?
  • ?????*?或者如果無法在timeout?過期之前獲取得到許可的話,那么立即返回false(無需等待)?
  • ?????*/??
  • ????@RequestMapping("/buy")??
  • ????public?Object?miao(int?count,?String?code)?{??
  • ????????//判斷能否在1秒內(nèi)得到令牌,如果不能則立即返回false,不會阻塞程序??
  • ????????if?(!rateLimiter.tryAcquire(1000,?TimeUnit.MILLISECONDS))?{??
  • ????????????System.out.println("短期無法獲取令牌,真不幸,排隊也瞎排");??
  • ????????????return?"失敗";??
  • ????????}??
  • ????????if?(goodInfoService.update(code,?count)?>?0)?{??
  • ????????????System.out.println("購買成功");??
  • ????????????return?"成功";??
  • ????????}??
  • ????????System.out.println("數(shù)據(jù)不足,失敗");??
  • ????????return?"失敗";??
  • ????}??
  • 在不看執(zhí)行結(jié)果的情況下,我們可以先分析一下,一秒出10個令牌,0.1秒出一個,100個請求進來,假如100個是同時到達,那么最終只能成交10個,90個都會因為超時而失敗。事實上,并不會完全同時到達,必然會出現(xiàn)在0.1秒后到達的,就會被歸入下一個周期。這是一個挺復雜的數(shù)學問題,每一個請求都會被計算未來可能獲取到令牌的概率。 還好,RateLimiter有自己的方法去做判斷。 我們運行看結(jié)果

    多執(zhí)行幾次,發(fā)現(xiàn)每次這個順序都不太一樣。 經(jīng)過我多次試驗,當設置線程組的間隔時間為0時,最終購買成功的數(shù)量總是22.其他的78個都是失敗。但基本都是開始和結(jié)束時連續(xù)成功,中間的大段失敗。 我修改一下jmeter線程組這100個請求的產(chǎn)生時間為1秒時,結(jié)果如下

    除了前面幾個和最后幾個請求連續(xù)成功,中間的就比較穩(wěn)定了,都是隔8個9個就會成功一次。
    當我修改為2秒內(nèi)產(chǎn)生100個請求時,結(jié)果就更平均了

    基本上就是前10個成功,后面的就開始按照固定的速率而成功了。 這種場景更符合實際的應用場景,按照固定的單位時間進行分割,每個單位時間產(chǎn)生一個令牌,可供購買。 看到這里是不是有點明白搶小米的情況了,很多時候并不是你網(wǎng)速快,手速快就能搶到,你需要看后臺系統(tǒng)的分配情況。所以你能否搶到,最好是開很多個賬號,而不是一直用一個賬號在猛點,因為你點也白點,后臺已經(jīng)把你的資格排除在外了。 當然了,真正的搶購不是這么簡單,瞬間的流量洪峰會沖垮服務器的負載,當100萬人搶1萬個小米時,連接口都請求不進來,更別提接口里的令牌分配了。 此時就需要做上一層的限流,我們可以選擇在上一層做分布式,開多個服務,先做一次限流,淘汰掉絕大多數(shù)運氣不好的用戶,甚至可以隨機丟棄某些規(guī)則的用戶,迅速攔截90%的請求,讓你去網(wǎng)頁看單機排隊動畫,還剩10萬。10萬也太大,足以沖垮數(shù)據(jù)層,那就進隊列MQ,用MQ削峰后,然后才放進業(yè)務邏輯里,再進行RateLimiter的限流,此時又能攔截掉90%的不幸者,還剩1萬,1萬去交給業(yè)務邏輯和數(shù)據(jù)層,用redis和DB來處理庫存。恭喜,你就是那個漏網(wǎng)之魚。 重點在于迅速攔截掉99%的不幸者,避免讓他們?nèi)ソ佑|到數(shù)據(jù)層。而且不能等待時間太長,最好是請求的瞬間就能確定你是永遠看單機動畫最好。
    /***************************************************************************************************/ 補充: 只在本地時效果不怎么明顯,我把這個小工程部署到線上服務器壓測了一下。 首先試了一下去掉了RateLimiter,只用db的Service處理數(shù)據(jù)的情況,發(fā)現(xiàn)mysql的服務占CPU約20%,總體請求失敗率較高。多是Tomcat超時。 使用RateLimiter阻塞后,數(shù)據(jù)庫CPU基本沒動靜,壓力幾乎沒有,Tomcat超時還有一些,因為還是并發(fā)數(shù)大,處理不了。 使用RateLimiter非阻塞,超時和請求失敗極少,總體QPS上升了不少。 測試不太正規(guī),就大概跑了跑。

    總結(jié)

    以上是生活随笔為你收集整理的rateLimiter令牌桶限流算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。