在上一篇文章nginx限流配置中,我們介紹了如何使用nginx限流,這篇文章介紹另外一種限流方式---RateLimiter,
v限流背景
在早期的計算機領域,限流技術(time limiting)被用作控制網路介面收發通信資料的速率, 可以用來優化性能,減少延遲和提高帶寬等, 現在在互聯網領域,也借鑒了這個概念, 用來為服務控制請求的速率, 如果雙十一的限流, 12306的搶票等, 即使在細粒度的軟體架構中,也有類似的概念,
系統使用下游資源時,需要考慮下游對資源受限、處理能力,在下游資源無法或者短時間內無法提升處理性能的情況下,可以使用限流器或者類似保護機制,避免下游服務崩潰造成整體服務的不可用,
v限流演算法
常見限流演算法有兩種:漏桶演算法和令牌桶演算法,
2.1 漏桶演算法
漏桶演算法(Leaky Bucket)是網路世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)時經常使用的一種演算法,它的主要目的是控制資料注入到網路的速率,平滑網路上的突發流量,漏桶演算法提供了一種機制,通過它,突發流量可以被整形以便為網路提供一個穩定的流量,
漏桶可以看作是一個帶有常量服務時間的單服務器佇列,如果漏桶(包快取)溢位,那么資料包會被丟棄, 在網路中,漏桶演算法可以控制埠的流量輸出速率,平滑網路上的突發流量,實作流量整形,從而為網路提供一個穩定的流量,
如下圖所示,把請求比作是水,水來了都先放進桶里,并以限定的速度出水,當水來得過猛而出水不夠快時就會導致水直接溢位,即拒絕服務,

圖片來源于網路,侵刪,
可以看出,漏桶演算法可以很好的控制流量的訪問速度,一旦超過該速度就拒絕服務,
2.2 令牌桶演算法
令牌桶演算法是網路流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一種演算法,典型情況下,令牌桶演算法用來控制發送到網路上的資料的數目,并允許突發資料的發送,
令牌桶演算法的原理是系統會以一個恒定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務,從原理上看,令牌桶演算法和漏桶演算法是相反的,一個“進水”,一個是“漏水”,
對于很多應用場景來說,除了要求能夠限制資料的平均傳輸速率外,還要求允許某種程度的突發傳輸,這時候漏桶演算法可能就不合適了,令牌桶演算法更為適合,
如下圖所示,令牌桶演算法的原理是系統會以一個恒定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務,

圖片來源于網路,侵刪,
本文后面實體中講到的RateLimiter(Google的Guava包)正是使用的令牌桶演算法,
2.3 漏桶演算法和令牌桶演算法的區別
漏桶演算法的出水速度是恒定的,那么意味著如果瞬時大流量的話,將有大部分請求被丟棄掉(也就是所謂的溢位),漏桶演算法通常可以用于限制訪問外部介面的流量,保護其他人系統,比如我們請求銀行介面,通常要限制并發數,
令牌桶演算法生成令牌的速度是恒定的,而請求去拿令牌是沒有速度限制的,這意味,面對瞬時大流量,該演算法可以在短時間內請求拿到大量令牌,可以處理瞬時流量,而且拿令牌的程序并不是消耗很大的事情,令牌桶演算法通常可以用于限制被訪問的流量,保護自身系統,
需要注意的是,在某些情況下,漏桶演算法不能夠有效地使用網路資源,因為漏桶的漏出速率是固定的,所以即使網路中沒有發生擁塞,漏桶演算法也不能使某一個單獨的資料流達到埠速率,因此,漏桶演算法對于存在突發特性的流量來說缺乏效率,而令牌桶演算法則能夠滿足這些具有突發特性的流量,通常,漏桶演算法與令牌桶演算法結合起來為網路流量提供更高效的控制,
vRateLimiter
3.1 基礎介紹
RateLimiter 從概念上來講,速率限制器會在可配置的速率下分配許可證,如果必要的話,每個acquire() 會阻塞當前執行緒直到許可證可用后獲取該許可證,一旦獲取到許可證,不需要再釋放許可證,
RateLimiter使用的是一種叫令牌桶的流控演算法,RateLimiter會按照一定的頻率往桶里扔令牌,執行緒拿到令牌才能執行,比如你希望自己的應用程式QPS不要超過1000,那么RateLimiter設定1000的速率后,就會每秒往桶里扔1000個令牌,
3.2 方法摘要
| 修飾符和型別 | 方法和描述 |
|---|---|
| double | acquire() 從RateLimiter獲取一個許可,該方法會被阻塞直到獲取到請求 |
| double | acquire(int permits) 從RateLimiter獲取指定許可數,該方法會被阻塞直到獲取到請求 |
| static RateLimiter | create(double permitsPerSecond) 根據指定的穩定吞吐率創建RateLimiter,這里的吞吐率是指每秒多少許可數(通常是指QPS,每秒多少查詢) |
| static RateLimiter | create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) 根據指定的穩定吞吐率和預熱期來創建RateLimiter,這里的吞吐率是指每秒多少許可數(通常是指QPS,每秒多少個請求量),在這段預熱時間內,RateLimiter每秒分配的許可數會平穩地增長直到預熱期結束時達到其最大速率,(只要存在足夠請求數來使其飽和) |
| double | getRate() 回傳RateLimiter 配置中的穩定速率,該速率單位是每秒多少許可數 |
| void | setRate(double permitsPerSecond) 更新RateLimite的穩定速率,引數permitsPerSecond 由構造RateLimiter的工廠方法提供, |
| String | toString() 回傳物件的字符表現形式 |
| boolean | tryAcquire() 從RateLimiter 獲取許可,如果該許可可以在無延遲下的情況下立即獲取得到的話 |
| boolean | tryAcquire(int permits) 從RateLimiter 獲取許可數,如果該許可數可以在無延遲下的情況下立即獲取得到的話 |
| boolean | tryAcquire(int permits, long timeout, TimeUnit unit) 從RateLimiter 獲取指定許可數如果該許可數可以在不超過timeout的時間內獲取得到的話,或者如果無法在timeout 過期之前獲取得到許可數的話,那么立即回傳false (無需等待) |
| boolean | tryAcquire(long timeout, TimeUnit unit) 從RateLimiter 獲取許可如果該許可可以在不超過timeout的時間內獲取得到的話,或者如果無法在timeout 過期之前獲取得到許可的話,那么立即回傳false(無需等待) |
3.3 實驗
3.3.1 添加參考
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>20.0</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.10</version> </dependency>
3.3.2 添加注解
package learn.web.interceptor; import java.lang.annotation.*; /** * @author toutou * @date by 2020/12 * @des 限流注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Limiting { // 默認每秒放入桶中的token double limitNum() default 20; String name() default ""; }
3.3.3 aop切面
package learn.web.interceptor; import com.google.common.util.concurrent.RateLimiter; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.concurrent.ConcurrentHashMap; /** * @author toutou * @date by 2020/12 * @des */ @Aspect @Component @Slf4j public class RateLimitAspect { private ConcurrentHashMap<String, RateLimiter> RATE_LIMITER = new ConcurrentHashMap<>(); private RateLimiter rateLimiter; @Pointcut("@annotation(learn.web.interceptor.Limiting)") public void serviceLimit() { } @Around("serviceLimit()") public Object around(ProceedingJoinPoint point) throws Throwable { //獲取攔截的方法名 Signature sig = point.getSignature(); //獲取攔截的方法名 MethodSignature msig = (MethodSignature) sig; //回傳被織入增加處理目標物件 Object target = point.getTarget(); //為了獲取注解資訊 Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes()); //獲取注解資訊 Limiting annotation = currentMethod.getAnnotation(Limiting.class); double limitNum = annotation.limitNum(); //獲取注解每秒加入桶中的token String functionName = msig.getName(); // 注解所在方法名區分不同的限流策略 if(RATE_LIMITER.containsKey(functionName)){ rateLimiter=RATE_LIMITER.get(functionName); }else { RATE_LIMITER.put(functionName, RateLimiter.create(limitNum)); rateLimiter=RATE_LIMITER.get(functionName); } if(rateLimiter.tryAcquire()) { log.info("處理完成"); return point.proceed(); } else { throw new RuntimeException("服務器繁忙,請稍后再試,"); } } }
3.3.4 添加Controller測驗
package learn.web.controller; import learn.model.vo.Result; import learn.web.interceptor.Limiting; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @author toutou * @date by 2020/12 * @des */ @Slf4j @RestController public class IndexController { /** * 使用限流注解的介面1 * @return */ @GetMapping("/limit1") @Limiting(limitNum = 1, name = "limiting1") public Result Limit1() { return Result.setSuccessResult("limiting1"); } /** * 使用限流注解的介面2 * @return */ @GetMapping("/limit2") @Limiting(limitNum = 5, name = "limiting2") public Result Limit2() { return Result.setSuccessResult("limiting2"); } }
3.3.5 測驗效果
ab測驗截圖

瀏覽器截圖

其他參考/學習資料:
- RateLimiter (Guava: Google Core Libraries for Java HEAD-jre-SNAPSHOT API)
- Quick Guide to the Guava RateLimiter
- 高并發系統限流-漏桶演算法和令牌桶演算法
- ratelimiter/
- 介面限流演算法
- RateLimiter限流
v原始碼地址
https://github.com/toutouge/javademosecond/tree/master/hellolearn
作 者:請叫我頭頭哥
出 處:http://www.cnblogs.com/toutou/
關于作者:專注于基礎平臺的專案開發,如有問題或建議,請多多賜教!
著作權宣告:本文著作權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文鏈接,
特此宣告:所有評論和私信都會在第一時間回復,也歡迎園子的大大們指正錯誤,共同進步,或者直接私信我
聲援博主:如果您覺得文章對您有幫助,可以點擊文章右下角【推薦】一下,您的鼓勵是作者堅持原創和持續寫作的最大動力!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/237895.html
標籤:其他
上一篇:一個故事告訴你什么是訊息佇列!
