分布式锁对比


使用原因

在分布式环境中,需要一种跨JVM的互斥机制来控制共享资源的访问

例如,为避免用户操作重复导致交易执行多次,使用分布式锁可以将第一次以外的请求在所有服务节点上立马取消掉。如果使用事务在数据库层面进行限制也能实现的但会增大数据库的压力。

例如,在分布式任务系统中为避免统一任务重复执行,某个节点执行任务之后可以使用分布式锁避免其他节点在同一时刻得到任务

实现方式

  1. 数据库(利用主键唯一规则、MySQL行锁)
  2. 基于Redis的NX(存在不新增)、EX(过期时间)参数
  3. Zookeeper临时有序节点

1. 数据库

在数据库中创建一个表,表中包含方法名等字段,并在方法名字段伤创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁

DROP TABLE IF EXISTS `method_lock`;
CREATE TABLE `method_lock` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `method_name` varchar(64) NOT NULL COMMENT '锁定的方法名',
  `desc` varchar(255) NOT NULL COMMENT '备注信息',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

执行某个方法后,插入一条记录

INSERT INTO method_lock (method_name, desc) VALUES ('methodName', '测试的methodName');

因为我们对method_name做了唯一性约束,如果有多个请求同事提交到数据,数据库会保证只有一个操作可以成功,那么我们就可以认为这个操作成功的那个线程获得了该方法的锁,接而执行方体内容。

成功插入则获得锁,执行完成后删除对应的行数据释放锁

delete from method_lock where method_name ='methodName';
  • 优点

    易于理解实现

  • 缺点

  1. 没有锁失效自动删除机制,可能出现插入数据成功,服务器宕机,对应的数据没有被删除,当服务器恢复之后一直获取不到锁,所以,需要在表中新增一列,用于记录失效时间,并且有定时任务清除失效数据。
  2. 吞吐量低
  3. 单点故障问题
  4. 轮询获取锁状态方式比较低效

2. Redis

NX是Redis提供的一个原子操作,如果指定的key存在,那么NX失败,如果不存在会进行set操作并返回成功。我们可以利用这个实现一个分布式锁,主要思路就是,set成功表示获取锁,set失败表示获取失败,失败后需要重试。加上Ex参数可以让该key在超时之后自动删除。

原理:SETNX
格式:setnx key value 将key的值设为value,当且仅当key不存在。若给定的key已经存在,则SETNX不做任何动作
SETNX 是SET IF NOT Exist(如果不存在则set)
set key value nv ex 10s 释放锁 delete key

public void lock(String key, String request, int timeout) throws InterruptedException {
    Jedis jedis = jedisPool.getResource();

    while (timeout >= 0) {
        String result = jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, DEFAULT_EXPIRE_TIME);
        if (LOCK_MSG.equals(result)) {
            jedis.close();
            return;
        }
        Thread.sleep(DEFAULT_SLEEP_TIME);
        timeout -= DEFAULT_SLEEP_TIME;
    }
}
  • 优点
    1. 吞吐量高
    2. 有锁失效自动删除机制,保证不会阻塞所有的流程
  • 缺点
    1. 单点故障问题
    2. 锁超时问题:如果A拿到锁设置了超时时长,但是业务还没有执行完成且锁已经被释放,此时其他进程会拿到锁从而执行业务?Redission定时延长超时时长避免过期。为什么不设置永不超时?为了防范业务方没有写解锁方法或者发生异常无法进行解锁。
    3. 轮询方式过于低效
Redis做分布式锁有哪些情况,如何解决
  1. 加锁,没有释放锁。需要释放锁的操作,比如delete key
  2. 加锁后,程序还没有执行释放锁,程序挂了,需要用到key的过期机制

Zookeeper

  1. 当客户端对某个方法加锁时,在Zookeeper上的与该方法对应的指定节点的目录下,生成一个临时有序节点
  2. 判断节点是否是当前目录下最小的节点,如果是则获取成功;如果不是,则获取失败,并获取上一个临时有序节点,对该节点进行监听,当节点删除时通知唯一的客户端
  • 优点

1.解决锁超市问题,因为zookeeper的写入都是顺序的,在一个节点创建之后,其他请求再次创建便会失败,同时可以对这个节点进行watch,如果节点删除会通知其他节点抢占锁; 2.能通过watch机制高效通知其他节点获取锁,避免惊群效应;

惊群效应就是在一个节点删除的时候,大量对这个节点的删除动作有订阅watch的线程会进行回调

3.有锁失效自动删除机制,保证不会阻塞所有流程

  • 缺点
    1. 性能不如Redis
    2. 强依赖zookeeper,如果原来系统不用zookeeper那需要自己维护一套zk

zk与redis做分布式锁的区别

Reids:

  1. Redis只保证最终一致性,副本间的数据复制是异步进行(Set是写,Get是读,Reids集群一般是读写分离架构,存在主从同步延迟情况),主从切换之后可能有部分数据没有复制过去可能会「丢失锁」 情况,故强一致性要求的业务不推荐使用Reids,推荐使用zk。
  2. Redis集群各方法的响应时间均为最低。随着并发量和业务数量的提升其响应时间会有明显上升(公网集群影响因素偏大),但是极限qps可以达到最大且基本无异常

Zookeeper:

  1. 使用ZooKeeper集群,锁原理是使用ZooKeeper的临时顺序节点,临时顺序节点的生命周期在Client与集群的Session结束时结速。因此如果某个Client节点存在网络问题,与ZooKeeper集群断开连接,Session超时同样会导致锁被错误的释放(导致被其他线程错误地持有),因此ZooKeepert也无法保证完全一致。
  2. ZK具有较好的稳定性;响应时间抖动很小,没有出现异常。但是随着并发量和业务数量的提升其响应时间和qps会明显下降。

总结:

  1. Zookeeper每次进行锁操作前都要创建若干节点,完成后要释放节点,会浪费很多时间;
  2. 而Redis只是简单的数据操作,没有这个问题。



Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • 2379. Minimum Recolors to Get K Consecutive Black Blocks
  • 2471. Minimum Number of Operations to Sort a Binary Tree by Level
  • 1387. Sort Integers by The Power Value
  • 2090. K Radius Subarray Averages
  • 2545. Sort the Students by Their Kth Score