Redisson实现分布式限流

通过使用Redisson的RRateLimiter实现对接口的访问限流,这种限流方式属于业务端限流,更加方便,更加灵活,可根据不同的业务进行调整。

Redisson的限流是基于令牌桶算法实现的,除了能够起到限流的作用外,还允许一定程度的流量突发。

使用

1.导入依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.5</version>
</dependency>

2.配置Redisson

RedissonConfig.class

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
34
35
36
37
38
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Redisson配置类
*/
@Configuration
@ConfigurationProperties(prefix = "spring.redis")//引用application配置文件
@Data
public class RedissonConfig {
//来自配置文件
private String host;
private Integer port;
private String password;
private Integer database;

@Bean
public RedissonClient redissonClient(){
// 1. 创建配置
Config config = new Config();
String redisAddress = "redis://"+host+":"+port;

//config.useClusterServers() //userClusterServers()是集群模式
config.useSingleServer()
.setAddress(redisAddress)
.setPassword(password)
.setDatabase(database);

// 2. 创建实例
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}

3.测试Redisson

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootTest
public class RedisTestClass {

@Resource
RedissonClient redissonClient;

@Test
public void testRedisson() {
RList<Object> list = redissonClient.getList("test-list");
list.add("123");
list.add("abcd");
list.add("甲乙丙丁");
System.out.println("rlist:"+list);
}
}

4.封装RedisLimiterManager

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
34
35
36
37
38
39
40
41
42
43
44
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class RedisLimiterManager {

@Resource
RedissonClient redissonClient;

/**
* 根据key进行限流
*
* @param key
* key可以是用户id,或者接口名称等,根据需求来自定义
* 比如:
* 1. 用户id,限制用户每秒访问多少次接口
* 2. 用户id+接口名称,限制用户每秒访问某个接口的次数
* 3. 接口名称,限制接口每秒最多被访问多少次
*/
public void doRateLimit(String key){
// 根据key创建限流器
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
// 设置限流器每秒放多少个请求
// 这里是允许1s内最多请求10次
// 参数:
// 1.在集群情况下
// RateType.OVERALL表示限流器是针对整个系统生效
// RateType.PER_CLIENT表示限流器是针对每个客户端生效
// 2.令牌桶容量
// 3.生成令牌间隔时间
// 4.生成令牌时间单位
rateLimiter.trySetRate(RateType.OVERALL, 3, 1, RateIntervalUnit.SECONDS);
// 尝试获取一个令牌,如果获取失败,则抛出异常
boolean flag = rateLimiter.tryAcquire(1);
if (!flag){
throw new RuntimeException("请求过于频繁");
}

}
}

5.使用

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
import com.me.bibackend.manager.RedisLimiterManager;
import org.junit.jupiter.api.Test;
import org.redisson.api.RList;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import javax.annotation.Resource;
import java.util.ArrayList;

@SpringBootTest
public class RedisTestClass {
@Resource
RedisLimiterManager redisLimiterManager;

@Test
public void testLimiter() throws InterruptedException {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
redisLimiterManager.doRateLimit("userId+InterfaceName");
//模拟访问接口
System.out.println("成功访问接口");
}).start();
}

//1s后再次访问
Thread.sleep(1100);
redisLimiterManager.doRateLimit("userId+InterfaceName");
//模拟访问接口
System.out.println("成功访问接口");

Thread.sleep(10000);
}
}

结果

可以看到目前的设定是1秒内对于同一个用户访问某接口只允许访问3次

在第一秒内的5次请求只有前3次成功,而1秒后又重新可以成功访问