接口限流

在大数据量高并发访问时,经常会出现服务或接口面对暴涨的请求而不可用的情况,甚至引发连锁反映导致整个系统崩溃。此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速率,就进行等待、排队、降级、拒绝服务等。在限流时,常见的两种算法是漏桶和令牌桶算法算法

令牌桶算法

令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 当桶满时,新添加的令牌被丢弃或拒绝。

1: guava实现

1: 引入pom文件

1
2
3
4
5
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>

2: 创建接口拦截注解

1
2
3
4
5
6
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GuavaRateLimit {

}

3: 添加拦截器,针对所有带此注解的接口进行限流

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
31
32
33
@Aspect
@Component
public class RateLimitAspect {

private final static Logger logger = LoggerFactory.getLogger(RateLimitAspect.class);

/**
* guava限流器,每秒限制流入10个请求
*/
private static final RateLimiter RATE_LIMITER = RateLimiter.create(10/2);

/**
* 拦截所有带@RateLimit的接口,进行限流
*/
@Around("@annotation(com.example.ratelimit.annotation.GuavaRateLimit)")
public Object rateLimiter(ProceedingJoinPoint joinPoint) {
Object obj = null;
try {
//尝试获取令牌,获取不到说明当前请求数量已经达到限流阈值
//tryAcquire()是非阻塞, acquire()是阻塞的
if (RATE_LIMITER.tryAcquire()) {
obj = joinPoint.proceed();
} else {
logger.info("系统繁忙");
return "系统繁忙";
}
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}

}

4: 添加限流测试接口

1
2
3
4
5
6
7
8
9
10
11
/**
* 限流测试接口
*
* @return 请求成功 返回 guava-limit-test:success ,请求失败 返回 系统繁忙
*/
@GuavaRateLimit
@PostMapping(value = "/guava")
public String guava() {
logger.info("guava-limit-test:success");
return "guava-limit-test:success";
}

2: redission限流器实现

1: 引入pom文件

1
2
3
4
5
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
</dependency>

2: 创建接口拦截注解

1
2
3
4
5
6
@Target({ElementType.PARAMETER, ElementType.METHOD})//作用目标方法和参数
@Retention(RetentionPolicy.RUNTIME)//这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用
@Documented//说明该注解将被包含在javadoc中
public @interface RedisRateLimit {

}

3: 初始化RRateLimiter限流器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private String name = "rateLimiter";  //存入redis中的令牌桶名称

private long rate = 10; // 速率

private long rateInterval = 1; // 速率时间间隔

@Bean
public RRateLimiter create() {
RRateLimiter rateLimiter = redissonClient.getRateLimiter(name);
//如果发现该令牌桶已存在,则先删除之前的令牌桶
if (rateLimiter.isExists()) {
rateLimiter.delete();
}
rateLimiter.trySetRate(RateType.PER_CLIENT, rate, rateInterval, RateIntervalUnit.SECONDS);
return rateLimiter;
}

4: 添加拦截器,针对所有带此注解的接口进行限流

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
31
@Aspect
@Component
public class RateLimitAspect {

private final static Logger logger = LoggerFactory.getLogger(RateLimitAspect.class);

@Resource
RRateLimiter rRateLimiter;

/**
* 拦截所有带@RateLimit的接口,进行限流
*/
@Around("@annotation(com.example.ratelimit.annotation.RedisRateLimit)")
public Object rRateLimiter(ProceedingJoinPoint joinPoint) {
Object obj = null;
try {
//尝试获取令牌,获取不到说明当前请求数量已经达到限流阈值
//tryAcquire()是非阻塞, acquire()是阻塞的
if (rRateLimiter.tryAcquire()) {
obj = joinPoint.proceed();
} else {
logger.info("系统繁忙");
return "系统繁忙";
}
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}

}

5: 添加限流测试接口

1
2
3
4
5
6
7
8
9
10
11
/**
* 限流测试接口
*
* @return 请求成功 返回 redisLimitTest:success ,请求失败 返回 系统繁忙
*/
@RedisRateLimit
@PostMapping(value = "/redis")
public String redis() {
logger.info("redis-limit-test:success");
return "redis-limit-test:success";
}

项目源码地址 https://gitee.com/loveUUxad/ratelimit.git

文章目录
  1. 1. 令牌桶算法
    1. 1.1. 1: guava实现
      1. 1.1.1. 1: 引入pom文件
      2. 1.1.2. 2: 创建接口拦截注解
      3. 1.1.3. 3: 添加拦截器,针对所有带此注解的接口进行限流
      4. 1.1.4. 4: 添加限流测试接口
    2. 1.2. 2: redission限流器实现
      1. 1.2.1. 1: 引入pom文件
      2. 1.2.2. 2: 创建接口拦截注解
      3. 1.2.3. 3: 初始化RRateLimiter限流器
      4. 1.2.4. 4: 添加拦截器,针对所有带此注解的接口进行限流
      5. 1.2.5. 5: 添加限流测试接口
|