概述
1、在没有集成spring-data-redis的项目中实现分布式锁,可以借助与jedis来需要增加jedisclient相关的配置和初始化代码,而已经集成 spring-data-redis的项目可以直接使用redisTemplate.execute 来实现。
2、加锁涉及到原子性,为了保证原子性 ,加锁需要使用执行lua脚本的方式,调用redisTemplate.execute执行lua脚本。
代码实现
public class RedisLockException extends Exception{
public RedisLockException(String message) {
super(message);
}
public RedisLockException(String message, Throwable cause) {
super(message, cause);
}
}
public class RedisCommand {
public static boolean lock(RedisTemplate<String, Object> redisTemplate, String key, String id, long expire) {
DefaultRedisScript<String> script = new DefaultRedisScript<>();
script.setScriptText("if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1],ARGV[1], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[2]); return nil; end; " +
"if (redis.call('hexists', KEYS[1], ARGV[1]) == 1) then redis.call('hincrby', KEYS[1], ARGV[1], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[2]); return nil; end; return redis.call('pttl', KEYS[1]);");
script.setResultType(String.class);
List<String> list = new ArrayList<>();
list.add(key);
String result = redisTemplate.execute(script, list, id, expire);
//result 为null时,加锁成功
return result == null;
}
public static void unlock(RedisTemplate<String, Object> redisTemplate, String key, String id) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText("if (redis.call('exists', KEYS[1]) == 0) then return 0; end; " +
"if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then return 0; end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1); " +
"if (counter > 0) then return 1; " +
"else " +
"redis.call('del', KEYS[1]); return 1; end;");
script.setResultType(Long.class);
List<String> list = new ArrayList<>();
list.add(key);
redisTemplate.execute(script, list, id);
}
public static boolean isLocked(RedisTemplate<String, Object> redisTemplate, String key, String id) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText("if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then return 0; end; " +
"return 1;");
script.setResultType(Long.class);
List<String> list = new ArrayList<>();
list.add(key);
Long result = redisTemplate.execute(script, list, id);
return result == 1L;
}
}
public interface RLock {
/**
* acquires the lock.
*
* @param expire redis key timeout
* @param timeUnit the time unit of the timeout argument
*/
void lock(long expire, TimeUnit timeUnit) throws InterruptedException, RedisLockException;
/**
* acquires the lock if lock is free
*
* @param expire redis key timeout
* @param timeUnit the time unit of the timeout argument
* @return
*/
boolean tryLock(long expire, TimeUnit timeUnit) throws RedisLockException;
/**
* try to Acquires the lock
*
* @param timeout the time to wait for the lock
* @param expire redis key timeout
* @param timeUnit the time unit of the timeout argument
* @return
*/
boolean tryLock(long timeout, long expire, TimeUnit timeUnit) throws InterruptedException, RedisLockException;
/**
* check if current thread is owner
*
* @return
*/
boolean isHeldByCurrentThread();
/**
* if any thread holds this lock
*
* @return
*/
boolean isLocked() throws RedisLockException;
void unlock() throws RedisLockException;
}
/**
* @author zzm
* @version V1.0
* @date 2017-09-26 14:18
**/
public class RedisLock implements RLock {
private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
private Thread thread;
private String key;
private UUID id = UUID.randomUUID();
private Random random = new Random();
private int unlockRetry;
private RedisTemplate<String, Object> redisTemplate;
//static final long spinForTimeoutThreshold = 1000L;
public static RedisLock create(String key, RedisTemplate<String, Object> redisTemplate) {
return create(key, redisTemplate, 1);
}
/**
* @param key 当前服务的别名
* @param unlockRetry 解锁重试次数
* @return
*/
public static RedisLock create(String key, RedisTemplate<String, Object> redisTemplate, int unlockRetry) {
return new RedisLock(key, redisTemplate, unlockRetry);
}
private RedisLock(String key, RedisTemplate<String, Object> redisTemplate, int unlockRetry) {
this.key = key;
this.redisTemplate = redisTemplate;
this.unlockRetry = unlockRetry;
}
/**
* 加锁
*
* @param expire redis key timeout
* @param timeUnit the time unit of the timeout argument
* @throws InterruptedException
*/
@Override
public void lock(long expire, TimeUnit timeUnit) throws InterruptedException {
if (expire <= 0L) throw new IllegalArgumentException("expire time least gt zero");
String field = getLockName(Thread.currentThread().getId());
boolean result;
for (; ; ) {
result = RedisCommand.lock(redisTemplate, key, field, timeUnit.toMillis(expire));
if (result) {
thread = Thread.currentThread();
return;
} else {
Thread.sleep(random.nextInt(10));
}
}
}
@Override
public boolean tryLock(long expire, TimeUnit timeUnit) {
String field = getLockName(Thread.currentThread().getId());
boolean result = RedisCommand.lock(redisTemplate, key, field, timeUnit.toMillis(expire));
if (result) {
thread = Thread.currentThread();
return true;
}
return false;
}
@Override
public boolean tryLock(long timeout, long expire, TimeUnit timeUnit) throws InterruptedException {
if (expire <= 0L) throw new IllegalArgumentException("expire time least gt zero");
if (timeout <= 0L) throw new IllegalArgumentException("timeout time least gt zero");
final long deadline = System.nanoTime() + timeUnit.toNanos(timeout);
String field = getLockName(Thread.currentThread().getId());
boolean result;
for (; ; ) {
result = RedisCommand.lock(redisTemplate, key, field, timeUnit.toMillis(expire));
if (result) {
thread = Thread.currentThread();
return true;
} else {
long remaining = deadline - System.nanoTime();
if (remaining <= 0L)
return false;
LockSupport.parkNanos(remaining);
}
}
}
@Override
public boolean isHeldByCurrentThread() {
return thread == Thread.currentThread();
}
@Override
public boolean isLocked() {
return RedisCommand.isLocked(redisTemplate, key, getLockName(Thread.currentThread().getId()));
}
@Override
public void unlock() {
if (thread != Thread.currentThread()) throw new IllegalMonitorStateException();
String field = getLockName(Thread.currentThread().getId());
for (int i = 0; i <= unlockRetry; i++) {
try {
RedisCommand.unlock(redisTemplate, key, field);
break;
} catch (Exception e) {
logger.error("当前线程解锁异常,线程ID:{},error:{}", Thread.currentThread().getId(), e.getMessage());
}
if (unlockRetry == i) logger.warn("当前线程解锁异常,线程ID:{}", Thread.currentThread().getId());
}
}
String getLockName(long threadId) {
return this.id + ":" + threadId;
}
}