写在前面:暑期课云原生(云原神)期末大作业的bonus部分需要实现统一限流机制,组长大懒狗不想做交给我完成这个部分,期间踩了很多坑,故有了这篇文章。

一、导入依赖包:

pom.xml里导入以下依赖包,主要用了bucket4j:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.0</version>
</dependency>

<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>7.6.0</version>
</dependency>

<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-jcache</artifactId>
<version>7.6.0</version>
</dependency>

记住需要导入jcache包。

二、配置部分

在你的项目配置文件中配置redis需要的配置(这里我的配置文件是properties格式):

1
2
3
4
5
6
spring.redis.host=your_server_host_ip
# 端口一般都是6379
spring.redis.port=6379
spring.redis.password=your_redis_password
spring.redis.timeout=2000ms
spring.redis.database=0
  • 如果没有设置密码的话不用加上password的配置,但是不安全建议加上。
  • 如果你的redis是开在另一台服务器上的话,记得开放那台服务器的6379端口。

三、代码部分

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.BucketConfiguration;
import io.github.bucket4j.Refill;
import io.github.bucket4j.distributed.proxy.ProxyManager;
import io.github.bucket4j.grid.jcache.JCacheProxyManager;
import org.redisson.config.Config;
import org.redisson.jcache.configuration.RedissonConfiguration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.cache.CacheManager;
import javax.cache.Caching;
import java.time.Duration;

@Configuration
public class RedisConfig {

@Value("${spring.redis.host:your_server_host_ip}")
private String redisHost;

@Value("${spring.redis.port:6379}")
private int redisPort;

@Value("${spring.redis.password:your_redis_password}")
private String redisPassword;

@Bean
public Config config() {
Config config = new Config();
String address = "redis://" + redisHost + ":" + redisPort;
config.useSingleServer()
.setAddress(address)
.setPassword(redisPassword);
return config;
}

@Bean
public CacheManager cacheManager(Config config) {
try {
CacheManager cacheManager = Caching.getCachingProvider("org.redisson.jcache.JCachingProvider")
.getCacheManager();
cacheManager.createCache("cache", RedissonConfiguration.fromConfig(config));
return cacheManager;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}

@Bean
public ProxyManager<String> proxyManager(CacheManager cacheManager) {
try {
ProxyManager<String> manager = new JCacheProxyManager<>(cacheManager.getCache("cache"));
return manager;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}

@Bean
public Bucket bucket(ProxyManager<String> proxyManager, CacheManager cacheManager) {
try {
// 这里清除了旧的cache,确保每次启动都会是新的bucket
javax.cache.Cache<String, Object> cache = cacheManager.getCache("cache");
if (cache != null) cache.clear();

String bucketKey = "rate_limit:hello";
Bandwidth limit = Bandwidth.classic(40, Refill.greedy(40, Duration.ofMinutes(1)));
BucketConfiguration configuration = BucketConfiguration.builder()
.addLimit(limit)
.build();
Bucket bucket = proxyManager.builder().build(bucketKey, () -> configuration);
return bucket;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}

错误示范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Integer> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Integer> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}

@Bean
public Bucket bucket(RedisTemplate<String, Integer> redisTemplate) {
Bandwidth limit = Bandwidth.classic(20, Refill.greedy(20, Duration.ofMinutes(1)));
return Bucket.builder().addLimit(limit).build();
}
}

虽然没有爆红,但是实际上并没有用到redisTemplate,尽管仍然能起到限流的作用,但是使用的是bucket的本地限流功能,并不是使用了redis的统一限流。

四、检查redis是否存在bucket

使用命令redis-cli -h your_server_host_ip -p 6379 -a your_redis_password来连接你的redis(可能要先下载工具redis-tools)。
查询后的结果应该如下:
查询结果

如果到这里都没有问题的话就可以写脚本开始测试你的限流机制是否正确了。