设为首页 收藏本站
查看: 1782|回复: 0

[经验分享] 分布式锁实现:Redis-Java邵先生的博客

[复制链接]

尚未签到

发表于 2018-11-2 07:52:54 | 显示全部楼层 |阅读模式
DSC0000.jpg

前言
  单机环境下我们可以通过JAVA的Synchronized和Lock来实现进程内部的锁,但是随着分布式应用和集群环境的出现,系统资源的竞争从单进程多线程的竞争变成了多进程的竞争,这时候就需要分布式锁来保证。
  实现分布式锁现在主流的方式大致有以下三种
  1. 基于数据库的索引和行锁
  2. 基于Redis的单线程原子操作:setNX
  3. 基于Zookeeper的临时有序节点
  这篇文章我们用Redis来实现,会基于现有的各种锁实现来分析,最后分享Redission的锁源码分析来看下分布式锁的开源实现
  复制代码
设计实现
  加锁
  一、 通过setNx和getSet来实现
  这是现在网上大部分版本的实现方式,笔者之前项目里面用到分布式锁也是通过这样的方式实现
  public boolean lock(Jedis jedis, String lockName, Integer expire) {
  //返回是否设置成功
  //setNx加锁
  long now = System.currentTimeMillis();
  boolean result = jedis.setnx(lockName, String.valueOf(now + expire * 1000)) == 1;
  if (!result) {
  //防止死锁的容错
  String timestamp = jedis.get(lockName);
  if (timestamp != null && Long.parseLong(timestamp) < now) {
  //不通过del方法来删除锁。而是通过同步的getSet
  String oldValue = jedis.getSet(lockName, String.valueOf(now + expire));
  if (oldValue != null && oldValue.equals(timestamp)) {
  result = true;
  jedis.expire(lockName, expire);
  }
  }
  }
  if (result) {
  jedis.expire(lockName, expire);
  }
  return result;
  }
  代码分析:

  •   通过setNx命令老保证操作的原子性,获取到锁,并且把过期时间设置到value里面
  •   通过expire方法设置过期时间,如果设置过期时间失败的话,再通过value的时间戳来和当前时间戳比较,防止出现死锁
  •   通过getSet命令在发现锁过期未被释放的情况下,避免删除了在这个过程中有可能被其余的线程获取到了锁
  存在问题

  •   防止死锁的解决方案是通过系统当前时间决定的,不过线上服务器系统时间一般来说都是一致的,这个不算是严重的问题
  •   锁过期的时候可能会有多个线程执行getSet命令,在竞争的情况下,会修改value的时间戳,理论上来说会有误差
  •   锁无法具备客户端标识,在解锁的时候可能被其余的客户端删除同一个key
  •   虽然有小问题,不过大体上来说这种分布式锁的实现方案基本上是符合要求的,能够做到锁的互斥和避免死锁
  二、 通过Redis高版本的原子命令
  jedis的set命令可以自带复杂参数,通过这些参数可以实现原子的分布式锁命令
  jedis.set(lockName, &quot;&quot;, &quot;NX&quot;, &quot;PX&quot;, expireTime);
  复制代码
  代码分析

  •   redis的set命令可以携带复杂参数,第一个是锁的key,第二个是value,可以存放获取锁的客户端ID,通过这个校验是否当前客户端获取到了锁,第三个参数取值NX/XX,第四个参数 EX|PX,第五个就是时间
  •   NX:如果不存在就设置这个key XX:如果存在就设置这个key
  •   EX:单位为秒,PX:单位为毫秒
  •   这个命令实质上就是把我们之前的setNx和expire命令合并成一个原子操作命令,不需要我们考虑set失败或者expire失败的情况
  解锁
  一、 通过Redis的del命令
  public boolean unlock(Jedis jedis, String lockName) {
  jedis.del(lockName);
  return true;
  }
  代码分析
  通过redis的del命令可以直接删除锁,可能会出现误删其他线程已经存在的锁的情况
  二、 Redis的del检查
  public static void unlock2(Jedis jedis, String lockKey, String requestId) {
  // 判断加锁与解锁是不是同一个客户端
  if (requestId.equals(jedis.get(lockKey))) {
  // 若在此时,这把锁突然不是这个客户端的,则会误解锁
  jedis.del(lockKey);
  }
  }
  代码分析
  新增了requestId客户端ID的判断,但由于不是原子操作,在多个进程下面的并发竞争情况下,无法保证安全
  三、 Redis的LUA脚本
  public static boolean unlock3(Jedis jedis, String lockKey, String requestId) {
  String script = &quot;if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end&quot;;
  Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(&quot;&quot;));
  if (1L == (long) result) {
  return true;
  }
  return false;
  }
  代码分析
  通过Lua脚本来保证操作的原子性,其实就是把之前的先判断再删除合并成一个原子性的脚本命令,逻辑就是,先通过get判断value是不是相等,若相等就删除,否则就直接return
Redission的分布式锁
  Redission是redis官网推荐的一个redis客户端,除了基于redis的基础的CURD命令以外,重要的是就是Redission提供了方便好用的分布式锁API
  复制代码
  一、 基本用法
  RedissonClient redissonClient = RedissonTool.getInstance();
  RLock distribute_lock = redissonClient.getLock(&quot;distribute_lock&quot;);
  try {
  boolean result = distribute_lock.tryLock(3, 10, TimeUnit.SECONDS);
  } catch (InterruptedException e) {
  e.printStackTrace();
  } finally {
  if (distribute_lock.isLocked()) {
  distribute_lock.unlock();
  }
  }
  代码流程

  •   通过redissonClient获取RLock实例
  •   tryLock获取尝试获取锁,第一个是等待时间,第二个是锁的超时时间,第三个是时间单位
  •   执行完业务逻辑后,最终释放锁
  二、 具体实现
  我们通过tryLock来分析redission分布式的实现,lock方法跟tryLock差不多,只不过没有最长等待时间的设置,会自旋循环等待锁的释放,直到获取锁为止
  long time = unit.toMillis(waitTime);
  long current = System.currentTimeMillis();
  //获取当前线程ID,用于实现可重入锁
  final long threadId = Thread.currentThread().getId();
  //尝试获取锁
  Long ttl = tryAcquire(leaseTime, unit, threadId);
  // lock acquired
  if (ttl == null) {
  return true;
  }
  time -= (System.currentTimeMillis() - current);
  if (time

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-629580-1-1.html 上篇帖子: 系统学习redis之三——redis数据类型之string类型及操作 下篇帖子: 1.22 redis集群介绍21.23/21.24 redis集群搭建配置21.25 redis集群
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表