SpringBoot项目中接口限流实现方案

环境:springboot2.3.9 + Guava30.1.1-jre

限流算法

一般有漏桶算法和令牌桶算法及计数器三种方式。

计数器

用计数器实现限流有点简单粗暴,一般我们会限 制一秒钟的能够通过的请求数,比如限流QPS为100,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。

具体的实现可以是这样的:对于每次服务调用,可以通过 AtomicLong#incrementAndGet()方法来给计数器加1并返回最新值,通过这个最新值和阈值进行比较。

这种实现方式,有一个弊端:如果我在单位时间1s内的前10ms,已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象”。

漏桶算法

漏桶算法主要是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。

漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。 在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。

如图所示,把请求比作是水,水来了都先放进桶里,并以限定的速度出水,当水来得过猛而出水不够快时就会导致水直接溢出,即拒绝服务。

SpringBoot项目中接口限流实现方案

image

可以看出,漏桶算法可以很好地控制流量的访问速度,一旦超过该速度就拒绝服务。

令牌桶算法

令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。从原理上看,令牌桶算法和漏桶算法是相反的,一个“进水”,一个是“漏水”。

SpringBoot项目中接口限流实现方案

image

了解完后3种限流算法后,接下来我们看看在项目中如何应用。

  • 使用Google Guava库RateLimiter

RateLimiter使用的是一种叫令牌桶的流控算法,RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行;且RateLimiter不支持集群环境,集群环境需要借助Redis等第三方工具实现。

依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>30.1.1-jre</version>
</dependency>

实现目标:每秒只允许3个请求通过。

@RestController
@RequestMapping("/products")
public class ProductController {
	
	private final RateLimiter rateLimiter = RateLimiter.create(5.0) ;
	
	@GetMapping("/{id}")
	public ResponseEntity<R> queryProducts(@PathVariable("id")  String id) throws Exception {
		if (rateLimiter.tryAcquire(1)) {
			TimeUnit.MILLISECONDS.sleep(200) ;
			return new ResponseEntity<R>(R.success("查询商品【" + id + "】成功"), HttpStatus.OK) ;
		}
		return new ResponseEntity<R>(R.failure("你访问的太快了"), HttpStatus.INTERNAL_SERVER_ERROR) ;
	}
}

通过Jmeter测试,版本5.4.1

线程配置,100个并发循环2次

SpringBoot项目中接口限流实现方案

 

接口配置

SpringBoot项目中接口限流实现方案

 

测试结果

SpringBoot项目中接口限流实现方案

 

RateLimiter相关方法说明:

SpringBoot项目中接口限流实现方案

参考https://ifeve!com/guava-ratelimiter

  • 使用百度的ratelimiter-spring-boot-starter


ratelimiter-spring-boot-starter为服务端限流的SDK,提供单节点维度的限流功能,通过限流算法,在流量过大时保证服务端按照一定速率平滑处理请求。

基于Spring Boot框架开发,目的是为Spring Cloud项目增加限流功能,同样在Spring Boot项目中也能正常使用。 本Starter的目前的应用场景为在Spring Cloud/Spring Boot的Web项目中引入该限流Starter,配置限流规则开启限流功能。 非Spring Web项目的特性正在规划中。

限流维度为:节点级、方法维度、服务维度限流。

  • 节点级别含义为限流SDK引入目标服务代码,限流规则针对目标服务部署的每个实例单独生效。
  • 方法维度含义为可以为目标服务的每个方法单独配置限流规则,该规则针对当前方法生效,与其他方法互不影响,目前方法仅支持HttpMethod+uri。
  • 服务维度含义为可针对每个服务实例配置全局规则,流入该服务实例的每个请求都将先进行服务限流判断。 服务级和方法级同时存在,将先后进过服务级、方法级两种限流器,任意一个限流器拒绝都将拒绝请求。

目前方法级只提供http方法的规则配置与生效,后续有计划支持Rpc方法的限流。

依赖

<dependency>
		<groupId>com.baidubce.formula</groupId>
		<artifactId>ratelimiter-spring-boot-starter</artifactId>
		<version>2.1.1.1</version>
</dependency>

应用配置

spring:
  application:
    name: ratelimiter
---
formula:
  ratelimiter:
    enabled: true
    ratelimiters:
    # 限流生效的位置,配置具体的uri
    - effectiveLocation: /products/q/**
      # 限流类型:1表示http,2表示rpc(暂未支持)
      effectiveType: 1
      # 该规则是否生效
      enabled: true
      httpMethod: GET
      # 限流器类型,1表示令牌桶
      limiterType: 1
      # 请求来源,当前版本不区分请求来源,区分请求来源的需求正在开发
      # source:
      # 限流的QPS值
      threshold: 5

注意:这里的spring.application.name必须配置,不然启动报错;
formula.ratelimiter.ratelimiters.source这个没有搞懂怎么配置的,官方文档没找到。

接口

@GetMapping("/q/{id}")
public ResponseEntity<R> queryProduct(@PathVariable("id")  String id) throws Exception {
		TimeUnit.MILLISECONDS.sleep(200) ;
		return new ResponseEntity<R>(R.success("查询商品【" + id + "】成功"), HttpStatus.OK) ;
}

测试

SpringBoot项目中接口限流实现方案

 

对于失败的请求,返回状态码429(Too Many Request)

baidu的这个限流工具,核心过滤器:

RateLimiterEffectiveFilter.java

SpringBoot项目中接口限流实现方案

 

waitForPermit方法

SpringBoot项目中接口限流实现方案

 

waitForPermission方法

SpringBoot项目中接口限流实现方案

 

HttpUtil#isBlockException方法

SpringBoot项目中接口限流实现方案

 

看到这里你想修改返回信息只能是重写它的代码了。

重写该类:

在我们项目src新建
com.baidu.formula.ratelimiter.spring.boot.autoconfigure.util.HttpUtil类修改isBlockException方法

public static boolean isBlockException(HttpServletResponse response, Exception e) throws IOException {
        if (e instanceof BlockException) {
            response.setStatus(429); // too many request
            response.setContentType("application/json; charset=utf-8");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().print("{\"code\": -1, \"message\": \"你的请求太快了\"}") ;
            response.flushBuffer();
            return true;
        } else {
            return false;
        }
}

测试:

SpringBoot项目中接口限流实现方案

 

完毕!!!

给个关注+转发呗谢谢

SpringBoot项目中接口限流实现方案

 

SpringBoot项目中接口限流实现方案

 

SpringBoot项目中接口限流实现方案

 

SpringBoot项目中接口限流实现方案

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Spring Boot ,我们可以使用 AOP(面向切面编程)和拦截器的方式来实现接口限流。以下是一个简单的实现方式: 1. 引入 Guava 库,它包含了令牌桶算法和漏斗算法的实现。 2. 定义一个注解 `@RateLimit`,用来标记需要进行限流接口。 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimit { int value() default 100; // 默认每秒限制100个请求 } ``` 3. 定义一个切面 `RateLimitAspect`,在接口被调用时进行限流检查。 ```java @Aspect @Component public class RateLimitAspect { private final RateLimiter rateLimiter = RateLimiter.create(100.0); // 每秒100个请求 @Around("@annotation(rateLimit)") public Object limit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable { if (rateLimiter.tryAcquire(rateLimit.value(), TimeUnit.MILLISECONDS)) { return joinPoint.proceed(); } else { throw new RuntimeException("接口限流,请稍后再试!"); } } } ``` 4. 在接口方法上添加 `@RateLimit` 注解,指定每秒允许的请求个数。 ```java @RestController public class DemoController { @GetMapping("/demo") @RateLimit(10) // 每秒限制10个请求 public String demo() { return "Hello World!"; } } ``` 以上代码实现了一个简单的接口限流功能,可以根据实际需求进行调整。需要注意的是,这种方式只适用于单机部署的场景,如果是分布式部署的系统,需要考虑使用分布式限流方案
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值