前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分布式锁系列--03关于分布式锁的选型分析01

分布式锁系列--03关于分布式锁的选型分析01

作者头像
IT云清
发布2022-05-07 16:43:04
1630
发布2022-05-07 16:43:04
举报
文章被收录于专栏:IT云清IT云清

本文分析,在分布式系统中,使用redis实现分布式锁,会遇到什么问题。关于分布式锁概念redis分布式锁的具体实现,可参考前面的2篇文章。本文重点在于,对分布式锁技术选型的分析。

1.redis锁单节点实现

常规的,使用redis做分布式锁,主要实现如下:

1.1加锁
代码语言:javascript
复制
  /**
   * 加锁
   *
   * @param lockName 锁名,对应被争用的共享资源
   * @param randomValue 随机值,需要保持全局唯一,便于释放时校验锁的持有者
   * @param expireTime 过期时间,到期后自动释放,防止出现问题时死锁,资源无法释放
   * @return
   */
  public static boolean acquireLock(String lockName,String randomValue,int expireTime){
    Jedis jedis = jedisPool.getResource();
    try {
      while (true){
        String result = jedis
            .set(lockName, randomValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if(LOCK_SUCCESS.equals(result)){
          logger.info("【Redis lock】success to acquire lock for [ "+lockName+" ],expire time:"+expireTime+"ms");
          return true;
        }
      }
    }catch (Exception ex){
      ex.printStackTrace();
    }finally {
      if(null != jedis){
        jedis.close();
      }
    }
    logger.info("【Redis lock】failed to acquire lock for [ "+lockName+" ]");
    return false;
  }
1.2解锁
代码语言:javascript
复制
  /**
   * redis释放锁
   * watch和muti命令保证释放时的对等性,防止误解锁
   *
   * @param lockName 锁名,对应被争用的共享资源
   * @param randomValue 随机值,需要保持全局唯一,以检验锁的持有者
   * @return 是否释放成功
   */
  public static boolean releaseLock(String lockName,String randomValue){
    Jedis jedis = jedisPool.getResource();
    try{
      jedis.watch(lockName);//watch监控
      if(randomValue.equals(jedis.get(lockName))){
        Transaction multi = jedis.multi();//开启事务
        multi.del(lockName);//添加操作到事务
        List<Object> exec = multi.exec();//执行事务
        if(RELEASE_SUCCESS.equals(exec.size())){
          logger.info("【Redis lock】success to release lock for [ "+lockName+" ]");
          return true;
        }
      }
    }catch (Exception ex){
      logger.info("【Redis lock】failed to release lock for [ "+lockName+" ]");
      ex.printStackTrace();
    }finally {
      if(null != jedis){
        jedis.unwatch();
        jedis.close();
      }
    }
    return false;
  }

2.redis锁单节点分析

基于单点redis的锁实现,上述这种实现,基本达到了单节点的安全限度,解决了如下几个问题:

1.防止死锁

设置过期时间后,即使客户端挂了,加锁后未解锁,这个锁也是会到期释放的,不存在死锁的可能。

典型的死锁场景:

一个客户端获取锁成功,但是在释放锁之前崩溃了,此时该客户端实际上已经失去了对公共资源的操作权,但却没有办法请求解锁(删除 key-value 键值对),那么,它就会一直持有这个锁,而其它客户端永远无法获得锁。

2.对称性

加锁的时候,我们给锁设置了个随机值,保证了即使在如下情况,解锁也是只会释放自己加的锁,而不会误删。

典型的误删场景:

假如获取锁时SET的不是一个随机字符串,而是一个固定值,那么可能会发生下面的执行序列:

  1. 客户端1获取锁成功。
  2. 客户端1在某个操作上阻塞了很长时间。
  3. 过期时间到了,锁自动释放了。
  4. 客户端2获取到了对应同一个资源的锁。
  5. 客户端1从阻塞中恢复过来,释放掉了客户端2持有的锁。

此时,如果客户端2访问共享资源,就没有锁来提供资源保护了。

3.互斥性

一个时刻只能有一个客户端可以得到锁,这个由redis自身命令setnx即可得到保证。

4.解锁

释放锁,包含3个操作,“get”、“判断”、“del”,我们必须保证释放锁时,这三个操作是原子性的。有2种方式来保证这一批操作的原子性。(这里本质是想说,这三个操作期间,这个key不能被更改,基于watch机制的乐观锁)

  • 1.我们可以执行lua脚本,来保证删除操作的原子性。
  • 2.使用redis提供的watch机制,如前文的实现,来保证。

如果三个操作的原子性得不到保证,下面的场景,就会出问题:

  1. 客户端1获取锁成功。
  2. 客户端1访问共享资源。
  3. 客户端1为了释放锁,先执行’GET’操作获取随机字符串的值。
  4. 客户端1判断随机字符串的值,与预期的值相等。
  5. 客户端1由于某个原因阻塞住了很长时间。
  6. 过期时间到了,锁自动释放了。
  7. 客户端2获取到了对应同一个资源的锁。
  8. 客户端1从阻塞中恢复过来,执行DEL操纵,释放掉了客户端2持有的锁。
前面的4个问题,如果我们自己单节点实现时,可以考虑到,代码层面都是可以正确处理的。那么,还有一个问题,如何做到高可用呢?

5.高可用

单节点,存在挂机的风险,为了达到高可用,我们可以做redis集群。一个redis节点作为master,master挂一个slave,当master挂掉后,自动切到slave节点。

看上去,前面5个问题都得到了解决。

但是,在集群模式下,考虑一个场景:

  1. 客户端1从Master获取了锁。
  2. Master宕机了,存储锁的key还没有来得及同步到Slave上。
  3. Slave升级为Master。
  4. 客户端2从新的Master获取到了对应同一个资源的锁。

由于redis的主从复制是异步的,这可能导致,在failover的过程中,资源丧失了锁带来的安全性。

这是我们使用redis实现锁遇到的第一个问题。

6.参数的选择

在前文的算法中,我们给锁设置了有效期,这个值,究竟多少合适呢?

如果太短,锁可能在客户端还未完成对资源的操作之前就过期,从而失去了保护;

如果太长,一个客户端如果主动释放锁失败了,那么,需要等到过期时间才会被动释放,那么,在漫长的有效期内,其他客户端,都无法获得这个资源的锁。

这是我们使用redis实现锁遇到的第二个问题。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-02-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.redis锁单节点实现
    • 1.1加锁
      • 1.2解锁
      • 2.redis锁单节点分析
        • 1.防止死锁
          • 2.对称性
            • 3.互斥性
              • 4.解锁
                • 前面的4个问题,如果我们自己单节点实现时,可以考虑到,代码层面都是可以正确处理的。那么,还有一个问题,如何做到高可用呢?
                • 这是我们使用redis实现锁遇到的第一个问题。
                • 这是我们使用redis实现锁遇到的第二个问题。
            • 5.高可用
            • 6.参数的选择
            相关产品与服务
            云数据库 Redis
            腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档