分布式锁及其常见实现方式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式锁及其常见实现方式相关的知识,希望对你有一定的参考价值。
参考技术A 在分布式系统中,为了保证对数据的修改有最终一致性,通常使用分布式锁或者分布式事务。比如常见的多个系统同时修改商品,既依赖于现有数据也要修改数据,如果没有限制,高并发情况下很可能最终数据是错误的。与单机锁不同,分布式锁更加复杂,需要考虑网络延迟、服务阻塞等,通常具有如下特点:
利用数据库主键唯一的特性,可以基于唯一主键保证多次操作只有一次成功。在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。释放锁时,直接删除数据库记录即可。
此方案存在的问题是强依赖数据库,容易形成热点,数据库锁表导致的超时会影响性能,或者数据库宕机会导致服务不可用。并且,数据库本身没有失效机制,如果任务崩溃会导致数据库中的锁不能被释放。数据库插入操作本身没有阻塞机制,故无法实现分布式锁的阻塞等待,任务线程可能需要重复尝试插入。由于唯一主键的存在,持有锁的线程也无法重复获得锁,其他线程竞争锁的过程中也无法根据优先级进行分配。
在数据库中为表增加一个版本号字段,每次操作时判断版本号,只有版本号一致才能进行对应的修改,修改后版本号加 1,通过 CAS 的方式进行修改。
此实现会增加数据库操作的次数,高并发情况下可能性能不好。
for update是一种行级锁,又叫排它锁,一旦用户对某个行施加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行。我们可以认为获得排他锁的线程即获得分布式锁,任务执行完成后通过 commit 来释放锁。for update 语句会在执行成功后立即返回,在执行失败时一直处于阻塞状态,直到成功。
但是 mysql 会对查询进行优化,即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的,如果 MySQL 认为全表扫效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。
setnx 的含义就是 SET if Not Exists,主要有两个参数 setnx(key, value)。该方法是原子的,如果 key 不存在,则设置当前 key 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0。setnx 命令不能设置 key 的超时时间,只能通过 expire() 来设置。
锁的实现步骤:
这个方案如果在第一步 setnx 执行成功后,在 expire() 命令执行成功前,发生了宕机的现象,那么就依然会出现死锁的问题。
这个方案是对上一个方案的优化版本。
getset() 命令主要有两个参数 getset(key,newValue)。该方法是原子的,对 key 设置 newValue 这个值,并且返回 key 原来的旧值。假设 key 原来是不存在的,那么首次执行的返回值是 null。
锁的实现步骤:
这个方案在任务处理超时或发生宕机时,无需担心锁超时问题,下次请求可以判断出实际上锁已经超时了。
zookeeper 由多个节点构成(单数),采用 zab 一致性协议。因此可以将 zk 看成一个单点结构,对其修改数据其内部自动将所有节点数据进行修改而后才提供查询服务。
zookeeper 数据是目录树的形式,每个目录称为 znode, znode 中可存储数据(一般不超过 1M),还可以在其中增加子节点。
子节点有三种类型。
zookeeper 提供了 Watch 机制,client 可以监控每个节点的变化,当产生变化会给 client 产生一个事件。
可以利用临时节点与 watch 机制实现分布式锁。每个锁占用一个普通节点 /lock,当需要获取锁时在 /lock 目录下创建一个临时节点,创建成功则表示获取锁成功,失败则 watch/lock 节点,有删除操作后再去争锁。临时节点好处在于当进程挂掉后锁的节点自动删除不会发生死锁。
缺点在于所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大。
一个可行的优化方案是上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同。只有序号最小的可以拥有锁,如果这个节点序号不是最小的则 watch 序号比本身小的前一个节点 (公平锁)。watch 事件到来后,再次判断是否序号最小。取锁成功则执行代码,最后释放锁(删除该节点)。
性能上可能没有缓存服务那么高,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁临时节点来实现锁功能。zookeeper 中创建和删除节点只能通过 Leader 服务器来执行,然后将数据同步到所有的 Follower 机器上。
分布式锁比较复杂,也比较容易发生死锁。目前主流的实现方式包括:
分布式锁及其常见实现方式 - 程序之心
redis应用系列一:分布式锁正确实现姿势
以上是关于分布式锁及其常见实现方式的主要内容,如果未能解决你的问题,请参考以下文章