网上到处都是分布式锁的代码,基本都是通过setNX 和 expire
这两个不是原子操作,肯定会有问题,不乏好多人通过用setNX的value当做过期时间来弥补等等。但是好像都不太好,或者多少有点问题。
从一个大神那里得来的代码
1 package com.abc.def.util; 2 3 import redis.clients.jedis.Jedis; 4 5 import java.util.Collections; 6 7 public class RedisDistributedLock { 8 9 10 private static final String LOCK_SUCCESS = "OK"; 11 private static final String SET_IF_NOT_EXIST = "NX"; 12 private static final String SET_WITH_EXPIRE_TIME = "PX"; 13 private static final Long RELEASE_SUCCESS = 1L; 14 15 /** 16 * 尝试获取分布式锁 17 * @param jedis Redis客户端 18 * @param lockKey 锁 19 * @param requestId 请求标识 20 * @param expireTime 超期时间 21 * @return 是否获取成功 22 */ 23 public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { 24 25 String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); 26 27 if (LOCK_SUCCESS.equals(result)) { 28 return true; 29 } 30 return false; 31 32 } 33 34 /** 35 * 释放分布式锁 36 * @param jedis Redis客户端 37 * @param lockKey 锁 38 * @param requestId 请求标识 39 * @return 是否释放成功 40 */ 41 public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { 42 43 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; 44 Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); 45 46 if (RELEASE_SUCCESS.equals(result)) { 47 return true; 48 } 49 return false; 50 51 } 52 53 }
获取锁,通过一个条指令来获取并且同时设置超时。
另外,解锁是通过获取锁的时候设置的key 这个key应该是一个随机值 推荐使用 UUID 来生成。 这样只能解锁自己的锁
另外解锁操作用的lua脚本来执行,把三条语句集合成一个原子操作。
使用示例代码:
1 while(true){ 2 String uuid = UUID.randomUUID().toString(); 4 boolean ret = lock.tryGetDistributedLock(redis,"NX_TEST", uuid, 5000); 5 if(ret != false){ 6 //TODO 实现业务逻辑 7 lock.releaseDistributedLock(redis,"NX_TEST", uuid); 8 break; 9 }else{ 10 Thread.sleep(500); 11 } 12 }
这里,设置的5000(5秒)超时时间,这个时间一定要大于业务逻辑的执行时间,否则就没办法锁住了。。
参考博客 https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/
PS: 哪位大神知道怎么从RedisTemplate 中获取 jedis ,RedisTemplate 并没有封装set 的相关函数。