Skip to content

Latest commit

 

History

History
92 lines (67 loc) · 4.63 KB

分布式-锁.md

File metadata and controls

92 lines (67 loc) · 4.63 KB

为什么用

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

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

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

实现方式

数据库

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

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. 轮询获取锁状态方式太过低效

基于Redis

NX是Redis提供的一个原子操作,如果指定key存在,那么NX失败,如果不存在会进行set操作并返回成功。我们可以利用这个来实现一个分布式的锁,主要思路就是,set成功表示获取锁,set失败表示获取失败,失败后需要重试。再加上EX参数可以让该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. 轮询获取锁状态方式太过低效

基于ZooKeeper

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

优点:

  1. 解决锁超时问题。因为Zookeeper的写入都是顺序的,在一个节点创建之后,其他请求再次创建便会失败,同时可以对这个节点进行Watch,如果节点删除会通知其他节点抢占锁
  2. 能通过watch机制高效通知其他节点获取锁,避免惊群效应
  3. 有锁失效自动删除机制,保证不会阻塞所有流程

缺点:

  1. 性能不如Redis
  2. 强依赖zk,如果原来系统不用zk那就需要维护一套zk

欢迎光临我的博客,发现更多技术资源~