Redis锁主要用到了Redis中的如下几个API,这里引用官方的API文档介绍说明一下:redis
SETNX
Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for "SET if Not eXists".分布式
Example:ide
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis> this
GETSET
Example:线程
redis> SET mykey "Hello"
"OK"
redis> GETSET mykey "World"
"Hello"
redis> GET mykey
"World"
redis> orm
expire
Integer reply, specifically:
1 if the timeout was set.
0 if key does not exist.接口
Example:ip
redis> SET mykey "Hello"
"OK"
redis> EXPIRE mykey 10
(integer) 1
redis> TTL mykey
(integer) 10
redis> SET mykey "Hello World"
"OK"
redis> TTL mykey
(integer) -1
redis> ci
官方文档介绍的也比较浅显易懂,这里不过多赘述。Redis可使用Redission,也可使用JedisCulster,而且以Api的方式提供给业务方
这里实现方式以下,互斥key业务方本身定制
接口层:
/** * Redis分布式锁 */ public interface DistributeLock { /** * 默认锁有效时间(单位毫秒) */ public static final long DEFAULT_LOCK_EXPIRE_TIME_MS = 60000; /** * 默认睡眠时间(单位毫秒) */ public static final long DEFAULT_SLEEP_TIME_MS = 100; /** * 尝试锁 * * @param lock 锁的键 * @param requestTimeoutMS 请求超时 ms * @return 若是锁成功,则返回true;不然返回false */ boolean tryLock(String lock, long requestTimeoutMS); /** * 尝试锁 * * @param lock 锁的键 * @param lockExpireTimeMS 锁有效期 ms * @param requestTimeoutMS 请求超时 ms */ boolean tryLock(String lock, long lockExpireTimeMS, long requestTimeoutMS); /** * 解锁 * * @param lock 锁的键 */ void unlock(String lock); }
具体实现层:
/** * Description:分布式锁 * Created by Jiyajie on 2017/12/15. */ @Service public class RedisDistributeLock implements DistributeLock { private static final Logger LOGGER = LoggerFactory.getLogger(RedisDistributeLock.class); /** * 每秒的毫秒数 */ private static final long MILLIS_PER_SECOND = 1000L; /** * 0值 */ private static final int INT_ZERO = 0; /** * 1值 */ private static final int INT_ONE = 1; /** * jedisCluster */ @Autowired private JedisCluster jedisCluster; @Override public boolean tryLock(String lock, long requestTimeoutMS) { return this.tryLock(lock, DEFAULT_LOCK_EXPIRE_TIME_MS, requestTimeoutMS); } @Override public boolean tryLock(String lock, long lockExpireTimeMS, long requestTimeoutMS) { Preconditions.checkArgument(StringUtils.isNotBlank(lock), "lock invalid"); Preconditions.checkArgument(lockExpireTimeMS > INT_ZERO, "lockExpireTimeMS invalid"); Preconditions.checkArgument(requestTimeoutMS > INT_ZERO, "requestTimeoutMS invalid"); while (requestTimeoutMS > INT_ZERO) { String expire = String.valueOf(System.currentTimeMillis() + lockExpireTimeMS + INT_ONE); Long result = jedisCluster.setnx(lock, expire); if (result > INT_ZERO) { //目前没有线程占用此锁 jedisCluster.expire(lock, Long.valueOf(lockExpireTimeMS / MILLIS_PER_SECOND).intValue()); return true; } String currentValue = jedisCluster.get(lock); if (currentValue == null) { //锁已经被其余线程删除立刻重试获取锁 continue; } else if (Long.parseLong(currentValue) < System.currentTimeMillis()) { //此处判断出锁已经超过了其有效的存活时间 String oldValue = jedisCluster.getSet(lock, expire); if (oldValue == null || oldValue.equals(currentValue)) { //1.若是拿到的旧值是空则说明在此线程作getSet以前已经有线程将锁删除,因为此线程getSet操做以后已经对锁设置了值,实际上至关于它已经占有了锁 //2.若是拿到的旧值不为空且等于前面查到的值,则说明在此线程进行getSet操做以前没有其余线程对锁设置了值,则此线程是第一个占有锁的 jedisCluster.expire(lock, Long.valueOf(lockExpireTimeMS / MILLIS_PER_SECOND).intValue()); return true; } } long sleepTime; if (requestTimeoutMS > DEFAULT_SLEEP_TIME_MS) { sleepTime = DEFAULT_SLEEP_TIME_MS; requestTimeoutMS -= DEFAULT_SLEEP_TIME_MS; } else { sleepTime = requestTimeoutMS; requestTimeoutMS = INT_ZERO; } try { TimeUnit.MILLISECONDS.sleep(sleepTime); } catch (InterruptedException e) { LOGGER.warn("分布式锁线程休眠异常{}", lock, e); } } return false; } @Override public void unlock(String lock) { String value = jedisCluster.get(lock); if (null != value && Long.parseLong(value) > System.currentTimeMillis()) { //若是锁还存在而且还在有效时间则进行删除 jedisCluster.del(lock); } } }
这里对实现步骤,以及实现原理进行分析:
1:参数校验,对传入的互斥key,锁超时时间,请求超时时间进行检验
2:在请求超时时间以内的请求,这里以while死循环的方式不断进行获取锁重试
3:设置锁过时时间,并尝试用setnx命令,redis以前不存在key的状况下,设置key,同时把过时时间expire做为value设置进去。若是获取成功,这里给锁加上真正的过时时间,获取锁成功~
4:在第三步没有成功的状况下,咱们直接再次获取锁。若是为空,则说明锁已通过期,或者已经被其余线程解锁,那么咱们马上结束本次循环,尝试从新获取~
5:若是第四步获取锁成功,咱们须要进行一下判断:1拿到锁的过时时间(key对应的value),并判断锁是否在过时时间以内,若是在的话,用具备原子性操做的命令getset,取出以前的过时时间oldValue值,这里会有两种状况:
//1.若是拿到的旧值是空则说明在此线程作getSet以前已经有线程将锁删除,因为此线程getSet操做以后已经对锁设置了值,实际上至关于它已经占有了锁
//2.若是拿到的旧值不为空且等于前面查到的值,则说明在此线程进行getSet操做以前没有其余线程对锁设置了值,则此线程是第一个占有锁的
两种状况都说明已经获取锁成功,结束循环
以上步骤都是创建在,请求超时时间以内的,这里每次循环获取,间隔100毫秒~,当获取时间超过请求超时时间的话:也是锁获取失败的一种状况
至此分布式锁的获取结束~
解锁操做很简单,这里也再也不赘述~