单Redis实例实现分布式锁
2018-04-03 14:41
357 查看
分布式锁原则
1、相互排斥,即任一时刻,只能有一个客户端持有锁;2、无死锁,持有锁的客户端宕机或网络延迟下仍可获取锁;3、有始有终,一个客户端加了锁,只能自己释放锁,当然也不能被其他客户端解锁;4、容错性,只要大部分redis节点还存活,那么客户端就应该可以正常加锁和释放锁;加锁操作
<!--jedis 2.9.0--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
private static final String LOCKED_SUCCESS = "OK"; private static final String NX = "NX"; private static final String EXPIRE_TIME = "PX"; /** * 获取锁 * * @param jedis redis客户端 * @param lockKey 锁的key * @param uniqueId 请求标识 * @param expireTime 过期时间 * @return 是否获取锁 */ public static boolean tryDistributedLock(Jedis jedis, String lockKey, String uniqueId, long expireTime) { String result = jedis.set(lockKey, uniqueId, NX, EXPIRE_TIME, expireTime); return LOCKED_SUCCESS.equals(result); }在低版本的redis中是没有这个set方法的,至于为什么这个简单的set方法能够保证前面提到的分布式锁原则呢?看下这个set的源码参数
/** * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1 * GB). * @param key 唯一标识key * @param value 存储的值value * @param nxxx 可选项:NX、XX 其中NX表示当key不存在时才set值,XX表示当key存在时才set值 * @param expx 过期时间单位,可选项:EX|PX 其中EX为seconds,PX为milliseconds * @param time 过期时间,单位取上一个参数 * @return Status code reply */ public String set(final String key, final String value, final String nxxx, final String expx, final long time) { checkIsInMultiOrPipeline(); client.set(key, value, nxxx, expx, time); return client.getStatusCodeReply(); }如上的tryDistributedLock就可以实现简单的redis分布式锁了(此set方法的原子性)1、set方法中nxx参数为NX,表示当key不存在时才会set值,保证了互斥性;2、set值的同时设置过期时间(过期后del此key),客户端宕机或网络延迟时不会一直持有锁,避免了死锁发生;3、set方法中的value,比如UUID之类的,用来表示当前请求客户端的唯一性标识;4、因为是redis单例,暂时没有考虑容错性;
常见的错误分布式加锁实现
public static void getLock(Jedis jedis, String lockKey, String uniqueId, int expireTime) { //setnx方法表示当key不存在时才set值,存在不做任何操作 Long result = jedis.setnx(lockKey, uniqueId); //设置过期时间,但是如果此时服务器宕机,将无法释放锁(这两个setnx和expire并不具备原子性) if (result == 1) jedis.expire(lockKey, expireTime); }
解锁操作
private static final Long RELEASE_SUCCESS = 1L; /** * 释放锁 * * @param jedis redis客户端 * @param lockKey 锁的key * @param uniqueId 请求标识 * @return 是否释放 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String uniqueId) { String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(luaScript, Collections.singletonList(lockKey), Collections.singletonList(uniqueId)); return RELEASE_SUCCESS.equals(result); }不难看出使用Lua脚本告诉redis,如果这个key存在,且其存储的值和指定的value一致才可以删除这个key,从而释放锁,这样也保证了分布式锁的几个原则. 常见的错误释放锁会直接del这个key,没有考虑当前锁的拥有者,不符合分布式锁原则的有始有终原则; 或者如下
public static void releaseLock(Jedis jedis,String lockKey,String uniqueId){ if(uniqueId.equals(jedis.get(lockKey))){ jedis.del(lockKey); } }如上代码存在这样的场景: Client A去加锁lockKey,然后释放锁,在执行del(lockKey)之前,这时lockKey锁expire到期失效了,此时Client B尝试加锁lockKey成功,Client A接着执行释放锁操作(del),便释放了Client B的锁.
如上是单实例下的分布式锁实现,如需要在Redis HA的高可用环境中使用分布式锁,请移步使用redis官方提供的的java组件Redisson.
相关文章推荐
- Dubbo框架应用之(四)--Dubbo基于Zookeeper实现分布式实例
- Dubbo框架应用之(四)--Dubbo基于Zookeeper实现分布式实例
- Dubbo框架应用之(四)--Dubbo基于Zookeeper实现分布式实例
- 消息服务框架(MSF)应用实例之分布式事务三阶段提交协议的实现
- dubbo实例 Dubbo+Zookeeper+Spring整合应用篇-Dubbo基于Zookeeper实现分布式服务
- 分布式任务分发框架Gearman教程和PHP实现实例
- dubbo基于Zookeeper实现分布式实例
- 分布式任务分发框架Gearman教程和PHP实现实例
- Dubbo学习总结(4)——Dubbo基于Zookeeper实现分布式实例
- Dubbo学习总结(4)——Dubbo基于Zookeeper实现分布式实例
- Dubbo学习总结(4)——Dubbo基于Zookeeper实现分布式实例
- 分布式任务分发框架Gearman教程和PHP实现实例
- Dubbo框架应用之(四)--Dubbo基于Zookeeper实现分布式实例
- 分布式任务分发框架Gearman教程和PHP实现实例
- Dubbo学习总结(4)——Dubbo基于Zookeeper实现分布式实例
- 分布式任务分发框架Gearman教程和PHP实现实例
- Redis Template实现分布式锁的实例代码
- Dubbo框架应用之(四)--Dubbo基于Zookeeper实现分布式实例
- Dubbo框架应用之(四)--Dubbo基于Zookeeper实现分布式实例
- Python利用multiprocessing实现最简单的分布式作业调度系统实例