基于setNX + Lua脚本的实现
- Lua简介
- 从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行求值。
- Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。
- Lua脚本配置流程
- 1、在resource目录下面新增一个后缀名为.lua结尾的文件
- 2、编写lua脚本
- 3、传入lua脚本的key和arg
- 4、调用redisTemplate.execute方法执行脚本
jedis 加锁
public static boolean lock(String key,String lockValue,int expire){
if(null == key){
return false;
}
try {
Jedis jedis = getJedisPool().getResource();
String res = jedis.set(key,lockValue,"NX","EX",expire);
jedis.close();
return res!=null && res.equals("OK");
} catch (Exception e) {
return false;
}
}
lua脚本加锁
add.lua
local lockKey = KEYS[1]
local lockValue = KEYS[2]
-- setnx info
local result_1 = redis.call('SETNX', lockKey, lockValue)
if result_1 == true
then
local result_2= redis.call('SETEX', lockKey,3600, lockValue)
return result_1
else
return result_1
end
/**
* 获取lua结果
* @param key
* @param value
* @return
*/
public Boolean luaExpress(String key,String value) {
DefaultRedisScript<Boolean> lockScript = new DefaultRedisScript<Boolean>();
lockScript.setScriptSource(
new ResourceScriptSource(new ClassPathResource("add.lua")));
lockScript.setResultType(Boolean.class);
// 封装参数
List<Object> keyList = new ArrayList<Object>();
keyList.add(key);
keyList.add(value);
Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList);
return result;
}
释放锁
unlock.lua
if redis.call('get', KEYS[1]) == ARGV[1]
then
return redis.call('del', KEYS[1])
else
return 0
end
private static final Long lockReleaseOK = 1L;
static String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";// lua脚本,用来释放分布式锁
public static boolean releaseLock(String key ,String lockValue){
if(key == null || lockValue == null) {
return false;
}
try {
Jedis jedis = getJedisPool().getResource();
Object res =jedis.eval(luaScript,Collections.singletonList(key),Collections.singletonList(lockValue));
jedis.close();
return res!=null && res.equals(lockReleaseOK);
} catch (Exception e) {
return false;
}
}
采用spring redisTemplate 实现分布式锁
spring-data-redis的版本尽量高版本,2.0以下的connection.set是没有返回值的。
@Component
public class RedisLock {
@Resource
private RedisTemplate<String, Object> redisTemplate;
//加锁
public Boolean setNX(final String key, final String requestId, final long expirationTime, final TimeUnit timeUnit) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.set(key.getBytes(), (value == null ? "" : value).getBytes(),
Expiration.from(expirationTime, timeUnit),
RedisStringCommands.SetOption.ifAbsent()));
}
//释放锁
public Boolean releaseLock(String key, String requestId) {
return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Boolean result = connection.eval(script.getBytes(), ReturnType.BOOLEAN, 1, key.getBytes(), requestId.getBytes());
return result;
});
}
}