Redis
- 通过setnx函数来进行原子性设置值(如果存在,设置失败)
- 通过Watch、multi、exec、unwatch的命令组合来实现原子性和数据的准确性
- 使用的spring boot中的 RedisTemplate
代码
package com.wyj.destributed.lock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
public class RedisDistributedLock implements DistributedLock {
private static Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);
private static final int DEFAULT_EXPIRE_MS = 60000;
private RedisTemplate redisTemplate;
private String lockKey;
private int expireMS;
private ReentrantLock lock;
private String lockedSuccessValue;
public RedisDistributedLock(RedisTemplate redisTemplate, String lockKey) {
this(redisTemplate, lockKey, DEFAULT_EXPIRE_MS);
}
public RedisDistributedLock(RedisTemplate redisTemplate, String lockKey, int expireMS) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey;
this.expireMS = expireMS;
lock = new ReentrantLock();
}
@Override
public void lock() {
lock.lock();
try {
for (; ; ) {
if (tryAcquire()) return;
}
} finally {
lock.unlock();
}
}
private boolean tryAcquire() {
long expires = System.currentTimeMillis() + expireMS;
String lockValue = generateLockKey(expires);
if (setNx(lockValue)) {
handleLockSuccessful(lockValue);
return true;
}
try {
return (boolean) redisTemplate.execute(new SessionCallback<Boolean>() {
@Override
public Boolean execute(RedisOperations operations) throws DataAccessException {
operations.watch(lockKey);
String currentValue = (String) operations.boundValueOps(lockKey).get();
long currentExpires = getCurrentExpires(currentValue);
if (currentValue != null && currentExpires < System.currentTimeMillis()) {
operations.multi();
operations.boundValueOps(lockKey).getAndSet(lockValue);
List<String> exec = operations.exec();
if (exec != null && exec.size() > 0 && exec.get(0).equals(currentValue)) {
handleLockSuccessful(lockValue);
return true;
}
} else {
operations.unwatch();
}
return false;
}
});
} catch (Exception e) {
return false;
}
}
@Override
public void unlock() {
try {
if (lockedSuccessValue != null) {
redisTemplate.execute(new SessionCallback<Boolean>() {
@Override
public Boolean execute(RedisOperations operations) throws DataAccessException {
operations.watch(lockKey);
String currentValue = (String) operations.boundValueOps(lockKey).get();
if (lockedSuccessValue.equals(currentValue)) {
operations.multi();
operations.delete(lockKey);
operations.exec();
} else {
operations.unwatch();
}
return null;
}
});
}
} catch (Exception e) {
logger.error("{} unlock error!", lockKey, e);
}
}
private void handleLockSuccessful(String lockValue) {
lockedSuccessValue = lockValue;
}
private String generateLockKey(long expires) {
return expires + ":" + UUID.randomUUID().toString();
}
private static long getCurrentExpires(String currentValue) {
if (currentValue == null) {
return 0;
}
int i = currentValue.indexOf(":");
return Long.parseLong(currentValue.substring(0, i));
}
private boolean setNx(final String value) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, value);
}
}