加锁没有用lua脚本 感兴趣的可以试一下
set然后expire这种操作不是原子性的,所以高并发下存在问题
使用lua脚本原子加锁
最新的redis-template中也支持原子操作了setIfAbsent()
好像是redis2.6.12之后加入的(redisTemplate也是操作的jedis)
springboot2.1.*/spring-data-redis2.1.9+
private static final Long SUCCESS = 1L;
// NX -- 仅在不存在的情况下才设置密钥。 XX -- 仅设置已存在的密钥。
private static final String NX = "NX";
// 过期时间单位:EX = 秒; PX = 毫秒
private static final String EX = "EX";
private static final String LOCK_OK = "OK";
private static RedisSerializer<String> argsSerializer = new StringRedisSerializer();
private static RedisSerializer resultSerializer = new StringRedisSerializer();
private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
/**
* 是否存在
* @param key
* @param value
* @param expireSeconds
* @return true:加锁成功;false:已存在锁
*/
public boolean lock(String key, String value, long expireSeconds) {
return (boolean)redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
if (nativeConnection instanceof JedisCluster) {
JedisCluster jedisCluster = (JedisCluster) nativeConnection;
String result = jedisCluster.set(key, value, NX, EX, expireSeconds);
return LOCK_OK.equals(result);
}
if (nativeConnection instanceof Jedis) {
Jedis jedis = (Jedis) nativeConnection;
String result = jedis.set(key, value, NX, EX, expireSeconds);
return LOCK_OK.equals(result);
}
return false;
}
});
}
/**
* 解锁
*
* @param
* @param
* @return
*/
public Boolean unlock(String key, String value) {
RedisScript<String> redisScript = new DefaultRedisScript<>(UNLOCK_SCRIPT, String.class);
// argsSerializer,resultSerializer 需要传递,不然某种情况下执行不了
Object result = redisTemplate.execute(redisScript, argsSerializer, resultSerializer, Collections.singletonList(key), value);
if (SUCCESS.equals(result)) {
return true;
}
return false;
}
加锁 lua,解锁lua
-- 加锁
if redis.call('setNx',KEYS[1],ARGV[1]) then
if redis.call('get',KEYS[1])==ARGV[1] then
return redis.call('expire',KEYS[1],ARGV[2])
else
return 0
end
end
-- 解锁
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end