背景:
控制指定请求端的最大并发数;
使用redis+lua脚本来完成加锁,释放锁的原子性操作
/**
* 执行脚本
* @param keys
* @param args
* @return true 超过最大限制
*/
public Boolean eval(String script, List<String> keys, List<String> args) {
Long result = redisTemplate.execute(new RedisCallback<Long>() {
public Long doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
// 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
// 集群模式
if (nativeConnection instanceof JedisCluster) {
return (Long) ((JedisCluster) nativeConnection).eval(script, keys, args);
}
return 0L;
}
});
return result == 1L ? true : false;
}
eval方法:执行redis cluster方式的代码;
/**
* 最大并发lua脚本
* @param key 需要拦截的key
* @param value 参数:最大并发数
* @return
*/
public boolean evalIncr(String key, Integer value) {
List<String> keys = new ArrayList<>();
keys.add(key);
List<String> values = new ArrayList<>();
values.add(String.valueOf(value));
String srcipt = "local currCount = redis.call('incr', KEYS[1])\n" +
"redis.call('expire', KEYS[1], 300)\n" +
"if currCount > tonumber(ARGV[1]) then\n" +
" redis.call(\"decr\", KEYS[1])" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end\n";
return eval(srcipt, keys, values);
}
evalIncr方法:当有客户端请求时,对此端并发数做incr操作,如果达到了最大并发数则返回1,为了防止程序崩溃等意外事件,对key设置过期时间,防止死锁;
/**
* 原子decr
* @param key
*/
public void evalDecr(String key) {
List<String> keys = new ArrayList<>();
keys.add(key);
List<String> values = new ArrayList<>();
values.add("1");
//get不到key时,redis返回显示的nil,实际上是boolean类型的 false
String srcipt = "local currCount = redis.call('get', KEYS[1])\n" +
"if (type(currCount) == 'boolean' and currCount == false) then\n" +
" return 0\n" +
"elseif(tonumber(currCount) > 0) then\n" +
" redis.call(\"decr\", KEYS[1])" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end\n";
eval(srcipt, keys, values);
}
evalDecr方法:释放占用的并发数,获取到redis中当前客户端已用的并发数;
if - 判断获取的值是否为nil ,如果是不做任何操作(这种情况可能是key自动到了过期时间)
elseif - 判断当前已用并发数是否大于0,大于0在decr,防止出现负数,因为redis获取出的值为string类型,所以使用tonumber方法转为数字在比较
else - 其他情况不做任何操作
srcipt为:lua语言