最近项目上需要做一个针对某用户下的某产品的限流需求,要求可配置,即时成效,也就是说多个用户下多个产品要有不同的并发控制。
由于之前并没有这方面的经验,经过一番查阅资料,发现令牌桶算法可以非常有效的控制流量,而且又有谷歌的guava提供的基于令牌桶算法的实现类RateLimiter(令牌桶算法这里不做描述,需要了解可以自行Google),但是由于不能满足项目上的需求,经过内部讨论并没有使用。
最终方案
最终采用方式也非常简单粗暴——计数器模式;原理非常简单,用户进入时,计数器+1,退出后-1,但是要做好线程同步,否者计数器很有可能变成负数。
这里的计时器采用Redis作为缓存,根据用户+产品作为key,缓存当前并发量,大致代码如下:
@Autowired
private RedisUtils redisUtils;
@Test
public void contextLoads() {
String key = "1000_0001";
Object object = new Object();
for (int i = 0; i < 7; i++) { // 请求数 7
new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
int current = Integer.valueOf(redisUtils.get(key)); //初始值 0
if (current < 5) { // 限流5
redisUtils.increment(key); // 缓存并发数 +1
} else {
System.out.println("并发超过5,请稍后再试"); // 熔断
return;
}
}
try {
Thread.sleep(3000); // 业务逻辑
redisUtils.decrement(key); // 释放并发数 -1
System.out.println("当前并发" + redisUtils.get(key));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
while (true) {
}
}
运行结果:
可以看到,想要的效果已经实现了,接下来看下Redis中的并发量:
多并发测试
将此方案带到业务代码中,并将最大并发量配置成5,使用Jmeter开启10个线程,测试一下结果
后话
经过大量测试后,发现此方式能有效的控制并发,但是并不意味着这是最好的处理方式,由于操作缓存需要用到线程同步,可能性能方面会大打折扣,而且若出现网络或其他故障,极有可能导致缓存中的并发量不能减掉。
本人还将持续更新此博客,记录后续可能遇到的问题或一些可优化的地方。
本博客内容均为原创,转载请注明出处。