方案1
利用setNX若存在则不插入,不存在则插入成功,同时value为时间戳,没拿到锁则判断时间是否过期,get拿到时间戳,过期则用,getset方式赋值,在比较时间戳是否过期,过期则拿到锁;
@Component
public class RedisLock {
@Autowired
StringRedisTemplate stringRedisTemplate;
public boolean addLock(String key, long expireTime) throws InterruptedException {
boolean lock = true;
long now = System.currentTimeMillis();
//setNx
while (!stringRedisTemplate.opsForValue().setIfAbsent(key, expireTime + "")) {
String lastTime = stringRedisTemplate.opsForValue().get(key);
if (StringUtils.isNotEmpty(lastTime) && Long.parseLong(lastTime) < now) {
// getset
String recentTime = stringRedisTemplate.opsForValue().getAndSet(key, expireTime + "");
//必须判断上一次时间和当前取出来的时间一致才行
if (StringUtils.isNotEmpty(recentTime) && recentTime.equals(lastTime)) {
lock = true;
break;
}
}
System.out.println("等待...");
Thread.sleep(1000);
}
return lock;
}
public void unLock(String key, long nowTime) {
//加锁解锁同一个人
if (nowTime == Long.parseLong(stringRedisTemplate.opsForValue().get(key))) {
stringRedisTemplate.delete(key);
}
}
}
方案2:
加锁:set(key,value,‘NX’,‘EX’,expireTime)
解锁:eval(lua语句,key1,value)
-
lua脚本
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
@Component public class RedisFlusterLock { @Autowired StringRedisTemplate stringRedisTemplate; private static final String LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then " + " return redis.call(\"del\",KEYS[1]) " + "else " + " return 0 " + "end "; public void tryLock(String key,String value, Long expireTime) { while (1 == 1) { String setResult = stringRedisTemplate.execute((RedisCallback<String>) connection -> { Object nativeConnection = connection.getNativeConnection(); String result = null; if (nativeConnection instanceof JedisCluster) { result = ((JedisCluster) nativeConnection).set(key, value, "NX", "EX", expireTime); } if (nativeConnection instanceof Jedis) { result = ((Jedis) nativeConnection).set(key, value, "NX", "EX", expireTime); } return result; }); if ("ok".equalsIgnoreCase(setResult)) { break; } else { try { System.out.println("等待..."); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public boolean unLock(String key, String value) { if (stringRedisTemplate.opsForValue().get(key).equals(value)) { Boolean delResult = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> { Object nativeConnection = connection.getNativeConnection(); long result = 0L; List<String> keys = new ArrayList<>(); keys.add(key); List<String> values = new ArrayList<>(); values.add(value); if (nativeConnection instanceof JedisCluster) { result = (Long) ((JedisCluster) nativeConnection).eval(LUA, keys, values); } if (nativeConnection instanceof Jedis) { result = (Long) ((Jedis) nativeConnection).eval(LUA, keys, values); } return result == 1L; }); return delResult; } else { return false; } } }
其他参考
基于Redis命令:
SET key valueNX EX max-lock-time
适用于redis单机和redis集群模式
- SET命令是原子性操作,NX指令保证只要当key不存在时才会设置value
- 设置的value要有唯一性,来确保锁不会被误删(value=系统时间戳+UUID)
- 当上述命令执行返回OK时,客户端获取锁成功,否则失败
- 客户端可以通过redis释放脚本来释放锁
- 如果锁到达了最大生存时间将会自动释放
只有当前key的value和传入的value相同才会执行DEL命令
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* redis锁工具类
*/
public class RedisLock {
public static final String OK = "OK";
public static final String UNLOCK_LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1] " + "then "
+ " return redis.call(\"del\",KEYS[1]) " + "else " + " return 0 " + "end ";
/**
* 单机和集群redis分布式锁
*
* 参考:https://redis.io/commands/set
*
* 版本:Redis Version >= 2.6.12
*
* 命令:SET key-name uuid NX EX max-lock-time
*
* @param keyName redis key name
* @param stringRedisTemplate stringRedisTemplate
* @param expireSeconds 锁定的最大时长
* @return 锁定结果
*/
public static LockRes tryLock(String keyName, StringRedisTemplate stringRedisTemplate, Integer expireSeconds) {
// 将value设置为当前时间戳+随机数
String lockValue = System.currentTimeMillis() + UUID.randomUUID().toString();
String redisLockResult = stringRedisTemplate.execute((RedisCallback<String>) connection -> {
Object nativeConnection = connection.getNativeConnection();
String result = null;
// 集群
if (nativeConnection instanceof JedisCluster) {
result = ((JedisCluster) nativeConnection).set(keyName, lockValue, "NX", "EX", expireSeconds);
}
// 单机
if (nativeConnection instanceof Jedis) {
result = ((Jedis) nativeConnection).set(keyName, lockValue, "NX", "EX", expireSeconds);
}
return result;
});
if (OK.equalsIgnoreCase(redisLockResult)) {
return new LockRes(true, keyName, lockValue);
} else {
return new LockRes(false, keyName, null);
}
}
public static Boolean unlock(LockRes lockRes, StringRedisTemplate stringRedisTemplate) {
if (lockRes.isFlag()) {
return stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
Object nativeConnection = connection.getNativeConnection();
Long result = 0L;
List<String> keys = new ArrayList<>();
keys.add(lockRes.getKey());
List<String> values = new ArrayList<>();
values.add(lockRes.getValue());
// 集群
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);
}
return result == 1L;
});
} else {
return true;
}
}
}
public class LockRes {
// 是否拿到锁,false:没拿到,true:拿到
private boolean flag;
// 缓存的键
private String key;
// 缓存的值
private String value;
public LockRes(boolean flag, String key, String value) {
super();
this.flag = flag;
this.key = key;
this.value = value;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
版权声明:本文为ximaiyao1984原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。