【JAVA秒会技术之分布式锁】玩转Redis分布式锁
2017-10-26 16:31
253 查看
玩转Redis分布式锁
不要一看到“分布式”三个字就就得有多难,多高大上,其实简单的很。先从官方的角度解释一下,什么叫“分布式锁”:
听懂了吗?没听懂举个实际中很容易碰到的例子:
Quartz定时任务应该用过吧,那么问题来了,基于分布式,假如一个应用部署在3台服务器上,同一时间点,定时任务会同时执行3次,怎么办?
有人说很简单,写入数据库之前查一下就行了。别忘了,你怎么能保证,在你查的瞬间,其他服务器的线程执行到哪里了呢?再回顾一下这个问题的关键信息:同一时间,不同服务器的不同线程,同时并行执行同一任务。此时,你就需要用到“分布式锁”了。用Redis做分布式锁只是其中的一种实现方式,相对来说,比较简单。
在此之前,先介绍几个Redis的几个小命令:
前面的铺垫说完了,直接上代码:
不要一看到“分布式”三个字就就得有多难,多高大上,其实简单的很。先从官方的角度解释一下,什么叫“分布式锁”:
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。 |
Quartz定时任务应该用过吧,那么问题来了,基于分布式,假如一个应用部署在3台服务器上,同一时间点,定时任务会同时执行3次,怎么办?
有人说很简单,写入数据库之前查一下就行了。别忘了,你怎么能保证,在你查的瞬间,其他服务器的线程执行到哪里了呢?再回顾一下这个问题的关键信息:同一时间,不同服务器的不同线程,同时并行执行同一任务。此时,你就需要用到“分布式锁”了。用Redis做分布式锁只是其中的一种实现方式,相对来说,比较简单。
在此之前,先介绍几个Redis的几个小命令:
①【setnx命令】是 SET if Not eXists的简写。将 key的值设为 value,当且仅当 key不存在。若给定的 key已经存在,则 SETNX不做任何动作。返回整数: 1-设置成功 ;0-设置失败。 ②【ttl命令】用于获取键到期的剩余时间(秒)。返回-1代表key没有设置超时时间。 ③【watch命令】可用于提供CAS(check-and-set)功能。假设我们通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Null 。 ④【multi命令】标志着一个事务块的开始。 ⑤【transaction.exec()】事务开始执行。 ⑥【unwatch命令】结束监听。 |
package com.***.cache; import java.util.List; import java.util.UUID; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisException; public class JedisManager { private JedisPool jedisPool; public JedisPool getJedisPool() { return jedisPool; } public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool; } public Jedis getJedis() { Jedis jedis = null; try { jedis = getJedisPool().getResource(); } catch (Exception e) { throw new JedisConnectionException(e); } return jedis; } public void returnResource(Jedis jedis, boolean isBroken) { if (jedis == null) return; jedis.close(); } /** * 获取Redis分布式锁 * <p>Title: lockWithTimeout</p> * @author Liyan * @date 2017年10月26日 下午3:27:53 * @param locaName 锁的key * @param acquireTimeout 获取锁的超时时间,毫秒 * @param timeout 锁本身的超时时间,毫秒 * @return 锁标识 */ public String getLock(String locaName, long acquireTimeout, long timeout) { Jedis jedis = null; String retIdentifier = null; try { //获取连接 jedis = jedisPool.getResource(); //随机生成一个value String identifier = UUID.randomUUID().toString(); //锁名,即key值 String lockKey = "lock:" + locaName; //超时时间,上锁后超过此时间则自动释放锁 int lockExpire = (int)(timeout / 1000); //获取锁的超时时间,超过这个时间则放弃获取锁 long end = System.currentTimeMillis() + acquireTimeout; while (System.currentTimeMillis() < end) { //【SETNX命令】是 SET if Not eXists的简写。 //将 key的值设为 value,当且仅当 key不存在。若给定的 key已经存在,则 SETNX不做任何动作。 //返回整数: 1-设置成功 ;0-设置失败. Long setnx = jedis.setnx(lockKey, identifier); if (setnx == 1) { //设置锁的有效时间 jedis.expire(lockKey, lockExpire); //返回value值,用于释放锁时间确认 retIdentifier = identifier; return retIdentifier; } //【TTL命令】用于获取键到期的剩余时间(秒)。 //返回-1代表key没有设置超时时间,为key设置一个超时时间 if (jedis.ttl(lockKey) == -1) { jedis.expire(lockKey, lockExpire); } try { //防止分布式线程并发 Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } catch (JedisException e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return retIdentifier; } /** * 释放锁 * <p>Title: releaseLock</p> * @author Liyan * @date 2017年10月26日 下午3:40:25 * @param lockName 锁的key * @param identifier 释放锁的标识 * @return boolean 是否释放成功 */ public boolean releaseLock(String lockName, String identifier) { Jedis jedis = null; String lockKey = "lock:" + lockName; boolean retFlag = false; try { jedis = jedisPool.getResource(); while (true) { //对lockKey开始监控 //【watch命令】可用于提供CAS(check-and-set)功能。 //假设我们通过WATCH命令在事务执行之前监控了多个Keys, //倘若在WATCH之后有任何Key的值发生了变化, //EXEC命令执行的事务都将被放弃,同时返回Null jedis.watch(lockKey); //通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁 if (identifier.equals(jedis.get(lockKey))) { //开启一个事务 //【multi命令】标志着一个事务块的开始。 Transaction transaction = jedis.multi(); //删除lockKey transaction.del(lockKey); //执行事务,进行删除 List<Object> results = transaction.exec(); if (results == null) { continue; } //标记成功结果 retFlag = true; } //结束监听 jedis.unwatch(); break; } } catch (JedisException e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return retFlag; } }
相关文章推荐
- java(优化16) 15天玩转redis之第八篇
- Redis 分布式锁的正确实现方式( Java 版 )[转]
- 使用 Redis 实现分布式系统轻量级协调技术
- 如何给web项目添加redis服务 JAVA几种缓存技术 ehcache和redis哪个更好
- 分布式缓存技术redis学习系列(一)——redis简介以及linux上的安装
- [置顶] Java实现基于Redis的分布式锁
- Redis 分布式锁的正确实现方式( Java 版 )
- jeesz分布式企业框架 javaWeb分布式架构 springmvc+mybatis+shiro dubbo zookeeper redis kafka app服务
- 详解Java如何实现基于Redis的分布式锁
- java redis 分布式(切片)非分布式(非切片)连接
- Redis 分布式锁的正确实现方式( Java 版 )
- 分布式锁1 Java常用技术方案
- 分布式缓存技术redis学习系列(五)——spring-data-redis与JedisPool的区别、使用ShardedJedisPool与spring集成的实现及一致性哈希分析
- Java互联网大数据技术视频资料 Nginx,ActiveMQ,Redis,Solr,kafka等
- 分布式缓存技术redis学习系列(三)——redis高级应用(主从、事务与锁、持久化)
- 分布式缓存技术redis学习系列(二)——详细讲解redis数据结构(内存模型)以及常用命令
- Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式)介绍
- Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式)介绍
- Java分布式应用技术架构介绍