|
@ -0,0 +1,315 @@
|
|
|
package com.yihu.base.cache.lock;
|
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.dao.DataAccessException;
|
|
|
import org.springframework.data.redis.connection.RedisConnection;
|
|
|
import org.springframework.data.redis.core.RedisCallback;
|
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.util.Assert;
|
|
|
import org.springframework.util.StringUtils;
|
|
|
import redis.clients.jedis.Jedis;
|
|
|
import redis.clients.jedis.JedisCluster;
|
|
|
import redis.clients.jedis.JedisCommands;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
|
import java.util.Random;
|
|
|
import java.util.UUID;
|
|
|
|
|
|
/**
|
|
|
* 缓存锁
|
|
|
*/
|
|
|
public class CacheLock {
|
|
|
|
|
|
private static Logger logger = LoggerFactory.getLogger(CacheLock.class);
|
|
|
|
|
|
private RedisTemplate redisTemplate;
|
|
|
|
|
|
/**
|
|
|
* 将key 的值设为value ,当且仅当key 不存在,等效于 SETNX。
|
|
|
*/
|
|
|
public static final String NX = "NX";
|
|
|
|
|
|
/**
|
|
|
* seconds — 以秒为单位设置 key 的过期时间,等效于EXPIRE key seconds
|
|
|
*/
|
|
|
public static final String EX = "EX";
|
|
|
|
|
|
/**
|
|
|
* 调用set后的返回值
|
|
|
*/
|
|
|
public static final String OK = "OK";
|
|
|
|
|
|
/**
|
|
|
* 默认请求锁的超时时间(ms 毫秒)
|
|
|
*/
|
|
|
private static final long TIME_OUT = 100;
|
|
|
|
|
|
/**
|
|
|
* 默认锁的有效时间(s)
|
|
|
*/
|
|
|
public static final int EXPIRE = 60;
|
|
|
|
|
|
/**
|
|
|
* 解锁的lua脚本
|
|
|
*/
|
|
|
public 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();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 锁标志对应的key
|
|
|
*/
|
|
|
private String lockKey;
|
|
|
|
|
|
/**
|
|
|
* 记录到日志的锁标志对应的key
|
|
|
*/
|
|
|
private String lockKeyLog = "";
|
|
|
|
|
|
/**
|
|
|
* 锁对应的值
|
|
|
*/
|
|
|
private String lockValue;
|
|
|
|
|
|
/**
|
|
|
* 锁的有效时间(s)
|
|
|
*/
|
|
|
private int expireTime = EXPIRE;
|
|
|
|
|
|
/**
|
|
|
* 请求锁的超时时间(ms)
|
|
|
*/
|
|
|
private long timeOut = TIME_OUT;
|
|
|
|
|
|
/**
|
|
|
* 锁标记
|
|
|
*/
|
|
|
private volatile boolean locked = false;
|
|
|
|
|
|
final Random random = new Random();
|
|
|
|
|
|
/**
|
|
|
* 使用默认的锁过期时间和请求锁的超时时间
|
|
|
*
|
|
|
* @param redisTemplate
|
|
|
* @param lockKey 锁的key(Redis的Key)
|
|
|
*/
|
|
|
public CacheLock(RedisTemplate redisTemplate, String lockKey) {
|
|
|
this.redisTemplate = redisTemplate;
|
|
|
this.lockKey = lockKey + "_lock";
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 使用默认的请求锁的超时时间,指定锁的过期时间
|
|
|
*
|
|
|
* @param redisTemplate
|
|
|
* @param lockKey 锁的key(Redis的Key)
|
|
|
* @param expireTime 锁的过期时间(单位:秒)
|
|
|
*/
|
|
|
public CacheLock(RedisTemplate redisTemplate, String lockKey, int expireTime) {
|
|
|
this(redisTemplate, lockKey);
|
|
|
this.expireTime = expireTime;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 使用默认的锁的过期时间,指定请求锁的超时时间
|
|
|
*
|
|
|
* @param redisTemplate
|
|
|
* @param lockKey 锁的key(Redis的Key)
|
|
|
* @param timeOut 请求锁的超时时间(单位:毫秒)
|
|
|
*/
|
|
|
public CacheLock(RedisTemplate redisTemplate, String lockKey, long timeOut) {
|
|
|
this(redisTemplate, lockKey);
|
|
|
this.timeOut = timeOut;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 锁的过期时间和请求锁的超时时间都是用指定的值
|
|
|
*
|
|
|
* @param redisTemplate
|
|
|
* @param lockKey 锁的key(Redis的Key)
|
|
|
* @param expireTime 锁的过期时间(单位:秒)
|
|
|
* @param timeOut 请求锁的超时时间(单位:毫秒)
|
|
|
*/
|
|
|
public CacheLock(RedisTemplate redisTemplate, String lockKey, int expireTime, long timeOut) {
|
|
|
this(redisTemplate, lockKey, expireTime);
|
|
|
this.timeOut = timeOut;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 尝试获取锁 超时返回
|
|
|
*
|
|
|
* @return
|
|
|
*/
|
|
|
public boolean tryLock() {
|
|
|
// 生成随机key
|
|
|
lockValue = UUID.randomUUID().toString();
|
|
|
// 请求锁超时时间,纳秒
|
|
|
long timeout = timeOut * 1000000;
|
|
|
// 系统当前时间,纳秒
|
|
|
long nowTime = System.nanoTime();
|
|
|
while ((System.nanoTime() - nowTime) < timeout) {
|
|
|
if (OK.equalsIgnoreCase(this.set(lockKey, lockValue, expireTime))) {
|
|
|
locked = true;
|
|
|
// 上锁成功结束请求
|
|
|
return locked;
|
|
|
}
|
|
|
|
|
|
// 每次请求等待一段时间
|
|
|
seleep(10, 50000);
|
|
|
}
|
|
|
return locked;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 尝试获取锁 立即返回
|
|
|
*
|
|
|
* @return 是否成功获得锁
|
|
|
*/
|
|
|
public boolean lock() {
|
|
|
lockValue = UUID.randomUUID().toString();
|
|
|
//不存在则添加 且设置过期时间(单位ms)
|
|
|
String result = set(lockKey, lockValue, expireTime);
|
|
|
locked = OK.equalsIgnoreCase(result);
|
|
|
return locked;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 以阻塞方式的获取锁
|
|
|
*
|
|
|
* @return 是否成功获得锁
|
|
|
*/
|
|
|
public boolean lockBlock() {
|
|
|
lockValue = UUID.randomUUID().toString();
|
|
|
while (true) {
|
|
|
//不存在则添加 且设置过期时间(单位ms)
|
|
|
String result = set(lockKey, lockValue, expireTime);
|
|
|
if (OK.equalsIgnoreCase(result)) {
|
|
|
locked = true;
|
|
|
return locked;
|
|
|
}
|
|
|
|
|
|
// 每次请求等待一段时间
|
|
|
seleep(10, 50000);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 解锁
|
|
|
* 不使用固定的字符串作为键的值,而是设置一个不可猜测(non-guessable)的长随机字符串,作为口令串(token)。
|
|
|
* 不使用 DEL 命令来释放锁,而是发送一个 Lua 脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。
|
|
|
* 这两个改动可以防止持有过期锁的客户端误删现有锁的情况出现。
|
|
|
*/
|
|
|
public Boolean unlock() {
|
|
|
// 只有加锁成功并且锁还有效才去释放锁
|
|
|
if (locked) {
|
|
|
return (Boolean) redisTemplate.execute(new RedisCallback<Boolean>() {
|
|
|
@Override
|
|
|
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
|
|
|
Object nativeConnection = connection.getNativeConnection();
|
|
|
Long result = 0L;
|
|
|
|
|
|
List<String> keys = new ArrayList<>();
|
|
|
keys.add(lockKey);
|
|
|
List<String> values = new ArrayList<>();
|
|
|
values.add(lockValue);
|
|
|
|
|
|
// 集群模式
|
|
|
if (nativeConnection instanceof JedisCluster) {
|
|
|
result = (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, values);
|
|
|
}
|
|
|
|
|
|
// 单机模式
|
|
|
if (nativeConnection instanceof Jedis) {
|
|
|
result = (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, values);
|
|
|
}
|
|
|
|
|
|
if (result == 0 && !StringUtils.isEmpty(lockKeyLog)) {
|
|
|
logger.info("Redis分布式锁,解锁{}失败!解锁时间:{}", lockKeyLog, System.currentTimeMillis());
|
|
|
}
|
|
|
|
|
|
locked = result == 0;
|
|
|
return result == 1;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @param key 锁的Key
|
|
|
* @param value 锁里面的值
|
|
|
* @param seconds 过去时间(秒)
|
|
|
* @return
|
|
|
*/
|
|
|
private String set(final String key, final String value, final long seconds) {
|
|
|
Assert.isTrue(!StringUtils.isEmpty(key), "key不能为空");
|
|
|
return (String) redisTemplate.execute(new RedisCallback<String>() {
|
|
|
@Override
|
|
|
public String doInRedis(RedisConnection connection) throws DataAccessException {
|
|
|
Object nativeConnection = connection.getNativeConnection();
|
|
|
String result = null;
|
|
|
if (nativeConnection instanceof JedisCommands) {
|
|
|
result = ((JedisCommands) nativeConnection).set(key, value, NX, EX, seconds);
|
|
|
}
|
|
|
|
|
|
if (!StringUtils.isEmpty(lockKeyLog) && !StringUtils.isEmpty(result)) {
|
|
|
logger.info("获取锁{}的时间:{}", lockKeyLog, System.currentTimeMillis());
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @param millis 毫秒
|
|
|
* @param nanos 纳秒
|
|
|
* @Title: seleep
|
|
|
* @Description: 线程等待时间
|
|
|
* @author yuhao.wang
|
|
|
*/
|
|
|
private void seleep(long millis, int nanos) {
|
|
|
try {
|
|
|
Thread.sleep(millis, random.nextInt(nanos));
|
|
|
} catch (InterruptedException e) {
|
|
|
logger.info("获取分布式锁休眠被中断:", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public String getLockKeyLog() {
|
|
|
return lockKeyLog;
|
|
|
}
|
|
|
|
|
|
public void setLockKeyLog(String lockKeyLog) {
|
|
|
this.lockKeyLog = lockKeyLog;
|
|
|
}
|
|
|
|
|
|
public int getExpireTime() {
|
|
|
return expireTime;
|
|
|
}
|
|
|
|
|
|
public void setExpireTime(int expireTime) {
|
|
|
this.expireTime = expireTime;
|
|
|
}
|
|
|
|
|
|
public long getTimeOut() {
|
|
|
return timeOut;
|
|
|
}
|
|
|
|
|
|
public void setTimeOut(long timeOut) {
|
|
|
this.timeOut = timeOut;
|
|
|
}
|
|
|
}
|