redis分布式锁实现
最近项目中使用到了redis实现的分布式锁,自定义的分布式锁支持自旋和可冲入等,是一个不错的实践,这里记录下
/**
* @description: redis分布式锁
* @author: cc.wang
* @createDate: 2022-04-22 12:13
* @version: 1.0
*/
public class RedisLock {
private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
private RedisTemplate<String, Object> redisTemplate;
private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
/**
* Lock key path.
*/
private String lockKey;
/**
* 锁超时时间,防止线程在入锁以后,无限的执行等待
*/
private int expireMsecs = 3000;
/**
* 锁等待时间,防止线程饥饿
*/
private int timeoutMsecs = 100;
private volatile boolean locked = false;
private String requestClientId = "";
protected 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();
}
/**
* Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.
*
* @param lockKey lock key (ex. account:1, ...)
*/
public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey + "_lock";
}
/**
* Detailed constructor with default lock expiration of 60000 msecs.
*/
public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey, int timeoutMsecs) {
this(redisTemplate, lockKey);
this.timeoutMsecs = timeoutMsecs;
}
/**
* Detailed constructor.
*/
public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
this(redisTemplate, lockKey, timeoutMsecs);
this.expireMsecs = expireMsecs;
}
/**
* @return lock key
*/
public String getLockKey() {
return this.lockKey;
}
private String get(final String key) {
try {
RedisCallback<String> callback = (connection) -> {
JedisCommands commands = (JedisCommands) connection.getNativeConnection();
return commands.get(key);
};
String result = redisTemplate.execute(callback);
return result;
} catch (Exception e) {
logger.error("获取锁内容异常", e);
}
return "";
}
private boolean setNX(final String key, final String value) {
try {
RedisCallback<String> callback = (connection) -> {
JedisCommands commands = (JedisCommands) connection.getNativeConnection();
return commands.set(lockKey, value, "NX", "PX", this.expireMsecs);
};
String result = redisTemplate.execute(callback);
logger.debug("locked result: {}", result);
return StringUtils.isNotBlank(result);
} catch (Exception e) {
logger.error("加锁失败", e);
}
return false;
}
/**
* @return true if lock is acquired, false acquire timeouted
* @throws InterruptedException in case of thread interruption
*/
public synchronized boolean lock() throws InterruptedException {
int timeout = timeoutMsecs;
if (StringUtils.isBlank(this.requestClientId)) {
requestClientId = UUID.randomUUID().toString();
logger.debug("redis lock requestClientId: {}", requestClientId);
}
//支持自旋
while (timeout >= 0) {
boolean isLocked = this.setNX(this.lockKey, this.requestClientId);
if (isLocked) {
this.locked = isLocked;
return true;
}
if (this.locked) {
//支持重入
String value = this.get(this.lockKey);
logger.debug("locked requestClientId: {}", value);
if (StringUtils.equalsIgnoreCase(this.requestClientId, value)) {
return true;
}
}
timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
if (timeout > 0) {
doSleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
}
return false;
}
/**
* 休眠一段时间
*
* @param milliseconds
* @throws InterruptedException
*/
private void doSleep(long milliseconds) throws InterruptedException {
Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
/**
* Acqurired lock release.
*/
public synchronized boolean unlock() {
if (!this.locked) {
//未取得锁时直接返回
return false;
}
try {
List<String> keys = Lists.newArrayList(this.lockKey);
List<String> args = Lists.newArrayList(this.requestClientId);
// 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
// spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
RedisCallback<Long> callback = (connection) -> {
Object nativeConnection = connection.getNativeConnection();
// 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
if (nativeConnection instanceof JedisCluster) { // 集群模式
return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
} else if (nativeConnection instanceof Jedis) { // 单机模式
return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
}
return 0L;
};
Long result = redisTemplate.execute(callback);
boolean reply = result != null && result > 0;
if (reply) {
this.locked = false;
}
return reply;
} catch (Exception e) {
logger.error("release lock occured an exception", e);
}
return false;
}
}
艾欧尼亚,昂扬不灭,为了更美好的明天而战(#^.^#)