分布式锁选型背后的架构设计思维附源码

Posted 架构之美

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式锁选型背后的架构设计思维附源码相关的知识,希望对你有一定的参考价值。


1. 分布式锁本质


提到分布式锁,有很多实现,比如Redis分布式锁、ZooKeeper分布式锁、etcd分布式锁等。但是选择哪个更适合你的项目?在《 》一文深入分析过分布式锁的哲学本质,以及如何结合场景来选择合适的分布式锁。分析业务场景,得到业务本质,就是架构思维。思维最终是需要落地的,接下去分享一下对分布式锁的思考和实践。

锁的本质是对共享资源的处理,表现很多,有以下作用:
  1. 业务协调
  2. 业务幂等(需配合业务代码实现)
  3. 共享资源竞争


在单体应用时代表现为同步块lock。随着需求和业务量的增长,系统走向了分布式、微服务时代,多服务和多实例下的应用无法使用本地锁进行控制资源共享。此时就出现了分布式锁,分布式场景下对分布式锁的要求如下:
  1. 强一致性
  2. 服务高可用、系统稳健
  3. 锁自动续约、自动释放
  4. 业务可重入


2. 分布式锁存储选型及场景


目前常见分布式锁的实现有Redis、ZooKeeper、etcd等,各维度指标对比如下:
分布式锁选型背后的架构设计思维【附源码】
图1 分布式锁存储模型对比

一致性算法(CAP):在分布式场景下,CAP理论是很多架构设计的指导思想。CAP思想下有两个分支CP与AP;CP模型不管什么情况下,都要求各服务之间的数据一致;AP模型高可用下的数据最终一致性。虽然锁原本要求强一致性CP模型,但AP模型分布式锁的使用取决于业务场景对脏数据的最大容忍度,比如SNS场景,就可以使用AP模型分布式锁,从而在性能上有很大的优势。CP模型仍然保持原有的一致性要求,保证了业务资源串行竞争,更加适合于金融交易场景的强数据要求。Redis自身无一致性算法来保证多节点的数据一致性,所以是AP模型;ZooKeeper、etcd都有一致性算法,都是CP模式。

高可用:Redis是一个K-V存储,使用主从模式进行集群,Redis Cluster 底层也是主从模式的组合,性能高,保证了高可用。ZooKeeper是Tree的数据结构,节点要求N + 1,N必须大于2,通过ZAB选举保障主的可用。etcd是一个K-V存储,节点要求N + 1,N必须大于2,通过Raft选举保障主的高可用。


3. 分布式锁接口设计


根据需求,设计出锁接口,首先锁的基本方法如下:
/**
     * @方法名称 lock
     * @功能描述 <pre>获取锁</pre>
     * @param ttl 锁过期时间,单位毫秒
     * @return true-获取锁,false-为获得锁
     * @throws RuntimeException 操作锁失败,需要业务判断是否重试
     */
    boolean lock(int ttl) throws RuntimeException;


第二,分布式锁可以处理业务幂等,可用作为消息去重等场景,设计竞争锁方法如下:
 /**
     * @方法名称 acquire
     * @功能描述 <pre>竞争锁,并自动续租</pre>
     * @param ttl 锁过期时间,单位毫秒
     * @return true-获取锁,false-为获得锁
     * @throws RuntimeException 操作锁失败,需要业务判断是否重试
     */
    default boolean acquire(int ttl)
        throws RuntimeException {
        if (lock(ttl)) {
           logger.debug(MSG_LOCK, getName());
           startHeartBeatThread();

         
   return true;
        }
        return false;
    }

第三,作为锁的基本要求,业务的串行执行,设计等待锁方法如下:
   /**
     * @方法名称acquireOrWait
     * @功能描述 <pre>竞争锁或等待锁</pre>
     * @param ttl 锁过期时间,单位毫秒
     * @param waitTime 等待时间,单位毫秒

    
* @return true-获取锁,false-为获得锁
     * @throws InterruptedException
     * @throws RuntimeException 操作锁失败,需要业务判断是否重试
     */
    default boolean acquireOrWait(int ttl, int waitTime)
        throws InterruptedException,RuntimeException {
        while (!lock(ttl)) {
            waitTime =waitTime - ttl / 2;
           Thread.sleep(ttl / 2);
            if (waitTime<= 0) {
              logger.debug(MSG_LOCK_TIMEOUT, getName());
       
       return false;
            }
        }
        startHeartBeatThread();
        return true;
    }

第四,分布式场景,锁需要自动续租方法,保障锁内业务完整执行,如下:
/**
     * @方法名称startHeartBeatThread
     * @功能描述 <pre>续租心跳</pre>

  
   */
    void startHeartBeatThread(); 

第五,锁需要自动释放,为保证使用简单,所以重写Closeable接口:
/**
     * @方法名称 close
     * @功能描述 <pre>释放锁</pre>
     */

   
@Override
    void close();
    
   /**
     * @方法名称 release
     * @功能描述 <pre>释放锁</pre>
     */
    default void release() {
        close();
    }

4. AP模型的Redis分布式锁实现


分布式锁选型背后的架构设计思维【附源码】
分布式锁选型背后的架构设计思维【附源码】
图2 AP模型的Redis分布式锁实现


5. CP模型的etcd分布式锁实现


etcd有V2和V3两种接口:V2接口可以使用http直接访问,天然客户端物理解耦,但需要自动续租保证锁的完整性。V3接口默认grpc形式,是长链接机制,天然续租,但grpc有客户端依赖要求。可以根据场景要求,适度选择合适版本接口。
锁参数有:
  1. prevExits:检查是否存在,true:新增,false:更新;
  2. prevIndex:检查上一个的key,既操作返回的uuid;
  3. prevValue:检查上一个的值;

Linux curl锁操作:
  1. 取锁:curl http://ip:port/v2/keys/锁名 -XPUT -d ttl=10 -d prevExits=false -d value=锁值
  2. 续租:curl http://ip:port/v2/keys/锁名?prevValue=锁值 -XPUT -d ttl=3 -dprevExits=true -d refresh=true
  3. 释放锁:curl  http://ip:port/v2/keys/锁名?prevValue=锁值 -XDELETE

6. CP模型的etcd分布式锁实现源码


全部源代码请访问以下链接:
https://github.com/linhuaichuan/ecp-uid/tree/master/src/test/java/com/myzmds/ecp/core/standard/distributed/lock


作者简介:
林淮川 毕业于西安交通大学,现任大树金融架构师,技术委员会委员; 前大树金融供应链金融技术总监; 前天阳宏业交易事业部技术主管。 5年互联网金融行业业务经验。 多次主导金融服务平台的设计、策划、实施与交付。 拥有丰富的大型软件平台构架设计经验,以及供应链金融业务经验。


相关阅读


以上是关于分布式锁选型背后的架构设计思维附源码的主要内容,如果未能解决你的问题,请参考以下文章

京东技术:如何实现靠谱的分布式锁?(附SharkLock的设计选择)

Zookeeper详细使用解析!分布式架构中的协调服务框架最佳选型实践

Zookeeper 详细解析!分布式架构中的协调服务框架的最佳选型

分布式锁架构设计方案 -02

奈学教育《百万架构师》课程大纲

分布式锁的技术选型及思考