分布式系列之分布式锁几种实现机制

发布时间:2022-08-19 14:12

在分布式系统中,分布式锁用来解决分布式系统中多线程、多进程在不同机器上共享资源访问的问题。本文简要介绍分布式锁的四种实现机制,包括数据库、Redis缓存、Zookeeper和Etcd,以加深了解。


1、分布式锁介绍

在单体应用中,通过锁机制实现多线程对共享资源的访问的,在分布式系统中,由于多线程、多进程是分布在不同的机器上,单机部署的并发锁控制机制已经不能满足分布式要求。分布式锁就是解决分布式系统中共享资源访问的问题,与单体应用不同的是,资源控制的最小粒度也从线程升级到了进程。

1.1 分布式锁的设计原则

为了满足分布式系统中资源的并发访问控制,分布式锁在设计上应满足以下原则:

  1. 在分布式系统环境下,一个方法在同一个时间只能被一个机器的同一个线程执行
  2. 高可用架构保证获取锁与释放锁过程中可靠性
  3. 获取锁与释放锁的高性能保证
  4. 具备可重入特性,可以理解为由相同的任务并发使用,不必担心数据错误
  5. 具备锁失效机制,防止死锁。锁申请等待超时限制,通常我们无法判断为什么一个线程迟迟拿不到锁,可能是饥饿,可能是死锁,给定一个等待时间,让线程自动放弃
  6. 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
  7. 中断响应,等待锁的线程,程序可以根据需要取消对锁的请求,如果一个线程正在等待锁,那么它依然可以收到一个通知,被告知无需等待,可以停止等待,可以处理死锁
  8. 公平锁,会按照时间的先后顺序,保证先到者先得,后到者后得,不会产生饥饿现象,只要排队,最终都能得到资源。如果使用synchronized关键字进行锁控制,那么产生的锁就是非公平的
1.2 分布式锁实现方法

常见的分布式锁实现方法有几种:基于数据库通过唯一索引实现、基于缓存Redis实现、基于一致性算法Zookeeper或Etcd实现

  1. 基于数据库实现:主要是利用数据库的唯一索引来实现,同一时刻只能允许一个竞争者获取锁,加锁时候在数据库中插入一条锁记录,利用业务id进行防重。当第一个竞争者加锁成功后,后续竞争者再来加锁就会报唯一键值冲突
  2. 基于缓存实现:基于缓存的加锁基本是在内存进行操作,理论上效率最高。使用Redis实现都是基于SETNX key value这个命令,如果key不存在才会执行成功
  3. 基于一致性算法实现:常见的有基于zookeeper实现和基于Etcd实现,zookeeper是利用临时顺序节点不能重复创建来实现排它性;Etcd则是利用存储的key值带有Revision属性,每次进行全局事务操作,对应的Revision值都会加1,保证了全局唯一

下面将分别介绍以上几种实现方法。

2、基于数据库实现

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

CREATE TABLE `distributed_lock` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `unique_method` varchar(255) NOT NULL COMMENT '锁定的方法名',
  `holder_id` varchar(255) NOT NULL COMMENT '锁持有者id',
  `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_method_index` (`unique_method`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

其中id字段为自增id,unique_method字段就是防重的唯一方法名,也就是加锁的对象。在表中创建了唯一索引,保证unique_method的唯一性。

1)加锁即插入一条记录

insert into distributed_lock(unique_method, holder_id) values (‘unique_method’, ‘holder_id’);

如果当前sql执行成功代表加锁成功,如果抛出唯一索引异常(DuplicatedKeyException)则代表加锁失败,当前锁已经被其他竞争者获取。

2)解锁很简单,直接删除此条记录即可

delete from methodLock where unique_method=‘unique_method’ and holder_id=‘holder_id’;

3)数据库实现简单,操作简单,用操作数据库的方式即可实现锁,但是存在以下问题:

  • 因为是基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,所以数据库需要实现高可用架构部署以及支持高TPS的并发访问效率;
  • 不具备可重入特性,因为同一个线程在释放锁之前,行数据一直存在,无法再次成功插入数据。解决方法是在表中新增一列,用于记录当前获取到锁的机器和线程信息。在再次获取锁的时候,先查询表中的机器和线程信息是否和当前机器和线程信息相同,若相同则直接获取锁;
  • 没有锁失效机制,因为有可能出现成功插入数据后,服务器宕机了,对应的数据没有被删除,此时distributed_lock表中的这条记录就会一直存在,其他竞争者无法加锁。为了解决这个问题,需要在锁中新增一列,用于记录失效的时间,并且需要有定时任务清除这些失效的数据;
  • 不具备阻塞锁的特性,获取不到锁直接返回失败,所以需要优化获取逻辑,循环多次去获取,增加失败重试的机制

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号