限流算法一般有三种方法:漏桶算法、令牌桶算法和计数器。计数器用计数器来实现限流有点简单粗暴。一般我们会限制一秒内可以通过的请求数。比如限流QPS为100。算法的思路是从第一个请求开始计时,在接下来的1s内,每有一个请求到来,计数加1。如果累计达到100个,以后的所有请求都将被拒绝。1s结束后,将计数恢复为0,重新开始计数。具体实现可以是:对于每次服务调用,可以使用AtomicLong#incrementAndGet()方法将计数器加1并返回最新值,并将最新值与阈值进行比较。这种实现方式有一个缺点:如果我在单位时间的前10ms内通过了100个请求,那么接下来的990ms我只能拒绝请求。我们称这种现象为“尖峰现象”。漏桶算法漏桶算法主要控制数据注入网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,可以将突发流量形状为网络提供稳定的流量。漏桶可以看作是服务时间恒定的单个服务器队列,如果漏桶(数据包缓冲区)溢出,则数据包将被丢弃。在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供稳定的流量。如图,将请求比作水。当水来的时候,先放入桶中,限速放水,当水来得太快,水来得不够快时,水会直接溢出,即拒绝服务。从图中可以看出,漏桶算法可以很好地控制流量的访问速度,一旦超速,就会拒绝服务。令牌桶算法令牌桶算法的原理是系统会以恒定的速度往桶中放入令牌,如果请求需要处理,需要先从桶中取出一个令牌,当没有令牌时在桶中如果可取,拒绝服务。从原理上讲,令牌桶算法和漏桶算法是相反的,一个是“进水”,一个是“漏水”。image了解了最后三种限流算法后,我们来看看如何在项目中应用。使用GoogleGuava库RateLimiterRateLimiter使用了一种称为令牌桶的流控算法,RateLimiter会按照一定的频率将令牌扔到桶中,线程只有拿到令牌后才能执行;而RateLimiter不支持集群环境,集群环境需要借助Redis等第三方工具实现。依赖org.springframework.bootspring-boot-starter-webcom.google.guavaguava30.1.1-jre达到目的:每秒只允许通过3个请求。@RestController@RequestMapping("/products")publicclassProductController{privatefinalRateLimiterrateLimiter=RateLimiter.create(5.0);@GetMapping("/{id}")publicResponseEntityqueryProducts(@PathVariable("id")Stringid)throwsException{if(rateLimiter.tryAcquire(1)){TimeUnit.MILLISECONDS.sleep(200);returnnewResponseEntity(R.success("查询商品["+id+"]成功"),HttpStatus.OK);}returnnewResponseEntity(R.failure("您的访问速度太快了"),HttpStatus.INTERNAL_SERVER_ERROR);}}通过Jmeter测试,5.4.1版本线程配置,100个并发循环2次接口配置测试结果RateLimiter相关方法说明:参考https://ifeve!com/guava-ratelimiter使用百度的ratelimiter-spring-boot-starterratelimiter-spring-boot-starter作为服务端限流的SDK,提供单节点限流功能,通过限流算法,当流量过大时,保证服务端以一定的速率流畅的处理请求。基于SpringBoot框架开发,目的是为SpringCloud项目增加一个限流功能,在SpringBoot项目中也可以正常使用。该Starter目前的应用场景是在SpringCloud/SpringBootWeb项目中引入限流Starter,配置限流规则,开启限流功能。计划用于非SpringWeb项目的功能。限流维度为:节点级别、方法维度、服务维度限流。节点级别的意思是限流SDK引入目标服务代码,限流规则对目标服务部署的每个实例独立生效。方法维度是指目标服务的每个方法都可以单独配置一个限流规则。该规则对当前方法生效,与其他方法互不影响。目前该方法仅支持HttpMethod+uri。服务维度的含义是可以为每个服务实例配置全局规则,每个流入服务实例的请求都会先通过服务流量限制进行判断。服务层和方法层同时存在,会依次进入服务层和方法层两个限流器。如果任何一个限流器拒绝,该请求将被拒绝。目前方法层面只提供http方法的规则配置和验证,未来有计划支持Rpc方法的限流。依赖com.baidubce.formularatelimiter-spring-boot-starter2.1.1.1应用配置spring:application:name:ratelimiter---formula:ratelimiter:enabled:trueratelimiters:#限流生效的位置,配置具体的uri-effectiveLocation:/products/q/**#限流类型:1表示http,2表示rpc(暂不支持)effectiveType:1#规则是否生效enabled:truehttpMethod:GET#限流器类型,1表示令牌桶limiterType:1#请求源,当前版本不区分请求源,需要区分请求源正在开发中#source:#Current-limitingQPSvaluethreshold:5注意:这里的spring.application.name必须配置,否则启动会报错;formula.ratelimiter.ratelimiters.source不知道怎么配置,官方文档也找不到。接口@GetMapping("/q/{id}")publicResponseEntityqueryProduct(@PathVariable("id")Stringid)throwsException{TimeUnit.MILLISECONDS.sleep(200);returnnewResponseEntity(R.success("queryCommodity【"+id+"】Success"),HttpStatus.OK);}测试请求失败,返回状态码429(TooManyRequest)百度限流工具,核心过滤器:RateLimiterEffectiveFilter.javawaitForPermit方法waitForPermission方法HttpUtil#isBlockException方法看到这里,如果要修改返回的信息,只能重写它的代码。重写这个类:在我们的项目src新建一个com.baidu.formula.ratelimiter.spring.boot.autoconfigure.util.HttpUtil类,修改isBlockException方法publicstaticbooleanisBlockException(HttpServletResponseresponse,Exceptione)throwsIOException{if(einstanceofBlockException2set)9{response.4;//toomanyrequestresponse.setContentType("application/json;charset=utf-8");response.setCharacterEncoding("UTF-8");response.getWriter().print("{\"code\":-1,\"message\":\"你的请求太快了\"}");response.flushBuffer();returntrue;}else{returnfalse;}}测试:完成!!!