令牌桶限流算法
令牌桶限流算法
令牌桶算法是一个桶,匀速向桶里放令牌,控制桶最大容量(令牌最大数)和放入令牌速率(生成令牌/秒)。所有的请求在处理之前都需要拿到一个可用的令牌才会被处理,如果桶里面没有令牌的话,则拒绝服务;
- 接口限制 t 秒内最大访问次数为 n,则每隔 t/n 秒会放一个 token 到桶中;
- 桶中最多可以存放 b 个 token,如果 token 到达时令牌桶已经满了,那么这个 token 会被丢弃;
- 接口请求会先从令牌桶中取 token,拿到 token 则处理接口请求,拿不到 token 则执行限流;
- 当一个n个字节的数据包到达时,就从令牌桶中删除n个令牌(不同大小的数据包,消耗的令牌数量不一样),并且数据包被发送到网络;
Rate limiting Spring Boot with bucket4j
每分钟10个限流,每分钟10个速度放入令牌token
1 2 3 4 5 6 7 8 9 10 | Refill refill = Refill.intervally( 10 , Duration.ofMinutes( 1 )); Bandwidth limit = Bandwidth.classic( 10 , refill); Bucket bucket = Bucket4j.builder() .addLimit(limit) .build(); for ( int i = 1 ; i <= 10 ; i++) { assertTrue(bucket.tryConsume( 1 )); } assertFalse(bucket.tryConsume( 1 )); |
Use Spring MVC Interceptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public class RateLimitInterceptor implements HandlerInterceptor { @Autowired private PricingPlanService pricingPlanService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String apiKey = request.getHeader( "X-api-key" ); if (apiKey == null || apiKey.isEmpty()) { response.sendError(HttpStatus.BAD_REQUEST.value(), "Missing Header: X-api-key" ); return false ; } String url = request.getRequestURI(); Bucket tokenBucket = pricingPlanService.resolveBucket(apiKey+ "-" +url); ConsumptionProbe probe = tokenBucket.tryConsumeAndReturnRemaining( 1 ); if (probe.isConsumed()) { response.addHeader( "X-Rate-Limit-Remaining" , String.valueOf(probe.getRemainingTokens())); return true ; } else { long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000; response.addHeader( "X-Rate-Limit-Retry-After-Seconds" , String.valueOf(waitForRefill)); response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "You have exhausted your API Request Quota" ); return false ; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class PricingPlanService { private final Map<String, Bucket> cache = new ConcurrentHashMap<>(); public Bucket resolveBucket(String apiKey) { return cache.computeIfAbsent(apiKey, this ::newBucket); } private Bucket newBucket(String apiKey) { PricingPlan pricingPlan = PricingPlan.resolvePlanFromApiKey(apiKey); return Bucket4j.builder() .addLimit(pricingPlan.getLimit()) .build(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public enum PricingPlan implements BandwidthFactory { FREE(){ @Override public Bandwidth getLimit() { return Bandwidth.classic( 20 , Refill.intervally( 20 , Duration.ofHours( 1 ))); } }, BASIC(){ @Override public Bandwidth getLimit() { return Bandwidth.classic( 40 , Refill.intervally( 40 , Duration.ofHours( 1 ))); } }, PROFESSIONAL(){ @Override public Bandwidth getLimit() { return Bandwidth.classic( 100 , Refill.intervally( 100 , Duration.ofHours( 1 ))); } }; } |
1 2 3 4 5 6 7 8 9 10 11 12 | @SpringBootConfiguration public class AppConfig implements WebMvcConfigurer { @Autowired private RateLimitInterceptor interceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor) .addPathPatterns( "/api/v1/area/**" ); } } |
1 2 3 4 5 6 | @PostMapping ( "/api/v1/area/rectangle3" ) public ResponseEntity<AreaV1> rectangle3( @RequestHeader (value = "X-api-key" ) String apiKey, @RequestBody RectangleDimensionsV1 dimensions) { return ResponseEntity.ok() .body( new AreaV1( "rectangle" , dimensions.getLength() * dimensions.getWidth())); } |
发起测试请求
1 2 3 | curl - v -X POST http: //localhost :8071 /api/v1/area/rectangle3 \ -H "Content-Type: application/json" -H "X-api-key:FX001-99999" \ -d '{ "length": 10, "width": 12 }' |
每小时20个token,剩余19个token可用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗