我们想通过同时设置redis的一个key值,用来表示占用到锁。可以用setnx命令实现。但如果此时redis服务发生异常,锁永远无法释放则会产生异常。为了避免这个现象,可以给key设置过期时间,但是两个命令分开执行时,两次命令中间的这段时间也可能产生异常。所以需要设置key-value和expire作为一个原子操作。redis的set命令本身支持这种操作。
SET key value [EX seconds|PX milliseconds|EXAT timestamp|PXAT
milliseconds-timestamp|KEEPTTL] [NX|XX] [GET]
- EX seconds – Set the specified expire time, in seconds.
- PX milliseconds – Set the specified expire time, in milliseconds.
- EXAT timestamp-seconds – Set the specified Unix time at which the key will expire, in seconds.
- PXAT timestamp-milliseconds – Set the specified Unix time at which the key will expire, in milliseconds.
- NX – Only set the key if it does not already exist.
- XX – Only set the key if it already exist.
- KEEPTTL – Retain the time to live associated with the key.
- GET – Return the old value stored at key, or nil when key did not exist.
于是将这个操作通过如下方式封装,留出key的值用来表示锁,过期时间表示锁过期时间即可。这中方式实现的分布式锁相比于redisson实现的分布式锁,可以更细粒度的控制每个具体锁的过期时间,缺点是没有自动续锁。
package com.dahua.psi.cache.redis.helper;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import java.nio.charset.Charset;
/**
* redis锁帮助类,与redisson的分布式锁相比,可任意修改锁超时时间
*
* @author susq
* @date 2020年11月11日
*/
@Component
public class RedisClusterHelper implements InitializingBean {
public boolean setExNx(String key, long expire) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
byte[] exp = String.valueOf(expire).getBytes(Charset.forName("UTF-8"));
Object obj = connection.execute("set", keySerializer.serialize(key), valueSerializer.serialize("1"), nx, ex, exp);
return obj != null;
});
}
@Override
public void afterPropertiesSet() throws Exception {
keySerializer = redisTemplate.getKeySerializer();
valueSerializer = redisTemplate.getValueSerializer();
}
@Autowired
private RedisTemplate<String, String> redisTemplate;
RedisSerializer keySerializer;
RedisSerializer valueSerializer;
private byte[] nx = "NX".getBytes(Charset.forName("UTF-8"));
private byte[] ex = "EX".getBytes(Charset.forName("UTF-8"));
/**
* 获取一个redis锁
* @param key 锁的key
* @param expire 过期时间,单位秒
* @return 是否获取成功
*/
public boolean setExNx(String key, long expire) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
byte[] exp = String.valueOf(expire).getBytes(Charset.forName("UTF-8"));
Object obj = connection.execute("set", keySerializer.serialize(key), valueSerializer.serialize("1"), nx, ex, exp);
return obj != null;
});
}
@Override
public void afterPropertiesSet() throws Exception {
keySerializer = redisTemplate.getKeySerializer();
valueSerializer = redisTemplate.getValueSerializer();
}
}