基于setnx加锁,基于lua实现锁释放。
实现了根据 key 及唯一标识 requestId进行加锁,并设置了超时时间;释放锁时根据key及requestId进行释放。
SET_IF_ABSENT 没有key才设置,expire 设置超时时间,作为原子性操作
RedisCallback<Boolean> callback = (connection) -> {
return connection.set(lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")), Expiration.seconds(timeUnit.toSeconds(expire)), RedisStringCommands.SetOption.SET_IF_ABSENT);
};
return (Boolean)redisTemplate.execute(callback)
直接上代码,可以直接用的工具类
@Slf4j
public class RedisDistributedLock {
private RedisTemplate redisTemplate;
public RedisDistributedLock(RedisTemplate redisTemplate){
this.redisTemplate = redisTemplate;
}
public static final String UNLOCK_LUA;
/**
* 释放锁脚本,原子操作
*/
static {
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append(" return redis.call(\"del\",KEYS[1]) ");
sb.append("else ");
sb.append(" return 0 ");
sb.append("end ");
UNLOCK_LUA = sb.toString();
}
/**
* 获取分布式锁,原子操作
* @param lockKey
* @param requestId 唯一ID, 可以使用UUID.randomUUID().toString();
* @param expire
* @param timeUnit
* @return
*/
public boolean tryLock(String lockKey, String requestId, long expire, TimeUnit timeUnit) {
try{
RedisCallback<Boolean> callback = (connection) -> {
return connection.set(lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")), Expiration.seconds(timeUnit.toSeconds(expire)), RedisStringCommands.SetOption.SET_IF_ABSENT);
};
return (Boolean)redisTemplate.execute(callback);
} catch (Exception e) {
log.error("redis lock error.", e);
}
return false;
}
/**
* 释放锁
* @param lockKey
* @param requestId 唯一ID
* @return
*/
public boolean releaseLock(String lockKey, String requestId) {
RedisCallback<Boolean> callback = (connection) -> {
return connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN ,1, lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")));
};
return (Boolean)redisTemplate.execute(callback);
}
/**
* 获取Redis锁的value值
* @param lockKey
* @return
*/
public String get(String lockKey) {
try {
RedisCallback<String> callback = (connection) -> {
return new String(connection.get(lockKey.getBytes()), Charset.forName("UTF-8"));
};
return (String)redisTemplate.execute(callback);
} catch (Exception e) {
log.error("get redis occurred an exception", e);
}
return null;
}
}
此工具简易轻量,基本满足加锁的一些场景。
VS redission分布式锁
redisson实现了加锁尝试时间
看门狗延长锁持有时间。
redission支持多样锁如可重入锁、读写锁、信号量等。
直接用setNx的问题,无超时时间程序意外挂掉,导致key不释放,好像新版本有setnx+过期时间的方法。
释放锁直接用delete 可能删除非当前线程的锁,增加唯一value校验,两步操作非原子性。