方案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)
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; } }