Advertisement

Mysql学习总结(83)——常用的几种分布式锁:ZK分布式锁、Redis分布式锁、数据库分布式锁、基于JDK的分布式锁方案对比总结

阅读量:

一、基于数据库实现分布式锁

1.1、悲观锁

采用select-withwhere-for-update机制实现排他锁。需要注意的是,在设置where子句时,默认情况下name字段必须使用索引以避免锁定整个表。需要注意的是,在某些特定场景下(例如表规模较小),mysql优化器可能会选择不走该索引路径而导致潜在的锁定问题。

1.2、乐观锁

所谓乐观锁与前者的最大区别在于基于CAS的思想,并不具有互斥性也不会导致锁等待并消耗资源,在操作过程中被认为不存在并发冲突仅在update version失败后方能察觉到这种情况我们的抢购及秒杀活动正是采用这种方法以避免产品缺货的情况发生通过增加递增版本号字段来实现乐观锁的方式

二、基于jdk的实现方式

思路:创建一个独立的服务,并利用JDK提供的并发工具实现对唯一资源的控制。例如,在该服务中维护一个concurrentHashMap用于管理键值对。当其他服务尝试对该键进行锁定时(lock),可以通过该服务提供的接口或端口发送消息进行通知(notify)。这些消息会被服务端接收并解析(parse),从而将concurrentHashMap中的对应键值对标记为已获取(acquired)。这种方法可以通过Netty框架实现通信模块的设计(design)。当然你可以选择使用java的bio、nio或者整合dubbo、spring cloud feign来实现通信也没问题(no problem)

缺点:这种方法表面上看起来较为简单(simple),但在深入考虑可用性(availability)、可靠性(reliability)、效率(efficiency)以及扩展性(scalability)等因素时...

三、基于缓存(Redis等)实现分布式锁

3.1、官方叫做 RedLock 算法,是 redis 官方支持的分布式锁算法。

这个分布式锁有 3 个重要的考量点:

    1. 排他性(确保只有一个客户端能够获取到锁)
    1. 避免死锁
    1. 容错机制(即使大部分 Redis 节点创建了这把锁,也能正常运行)

3.2、下面是redis分布式锁的各种实现方式和缺点,按照时间的发展排序

  • 1、直接setnx
    通过调用setnx接口,在完成业务逻辑处理后触发del函数以释放锁头操作简便。
    缺点:其主要缺陷在于:当setnx操作顺利完成时仍未释放锁头会导致服务长时间未响应;该key将永久无法被获取。

    • 2、setnx设置一个过期时间
      为了改正第一个方法的缺陷,我们用setnx获取锁,然后用expire对其设置一个过期时间,如果服务挂了,过期时间一到自动释放
      缺点 :setnx和expire是两个方法,不能保证原子性,如果在setnx之后,还没来得及expire,服务挂了,还是会出现锁不释放的问题

    • 3、set nx px
      redis官方为了解决第二种方式存在的缺点,在2.8版本为set指令添加了扩展参数nx和ex,保证了setnx+expire的原子性,使用方法:
      set key value ex 5 nx
      缺点
      ①如果在过期时间内,事务还没有执行完,锁提前被自动释放,其他的线程还是可以拿到锁
      ②上面所说的那个缺点还会导致当前的线程释放其他线程占有的锁

  • 4、添加事务ID
    上述提到的第一个不足之处,并没有很好的解决办法,只能把过期时间尽量设置得更长一些,并且最好避免执行耗时的任务
    第二个问题,则是说当前线程有可能会释放其他线程所持有的锁。因此,在程序设计中需要将问题重新定义:即在setnx操作时将value字段设置为任务的唯一标识符,在release操作时先获取key节点并比较其值是否等于当前id。如果一致,则允许释放;如果不一致,则抛异常进行回滚处理。这其实也是一种间接的方法来解决第一个问题
    缺点:get key操作与检查value是否等于id这两个步骤并非完全同步执行,在程序运行过程中可能会出现逻辑上的不一致性

  • 5、设置事务ID时会涉及nx和px字段
    通过Lua编写一个用于获取键并进行比较的脚本是可行的方法,并且这些库(如jedis、luttce和redisson)都提供了良好的Lua脚本支持
    缺点:在集群环境中操作时存在潜在风险,在分布式锁机制下可能导致数据不一致的问题。具体来说,在应用分布式锁时会遇到以下问题:由于Redis主从同步通常是异步操作,在某些情况下主Master节点会在内存中执行nx操作后立即返回结果给客户端,并成功获得锁权限;然而在此期间Master节点可能会因故障而失效或数据未及时同步;随后另一个节点将被选举为主Master节点,在此期间其他线程仍可正常获取锁权限

    • 6、redlock
      为了解决上面提到的redis集群中的分布式锁问题,redis的作者antirez的提出了red lock的概念,假设集群中所有的n个master节点完全独立,并且没有主从同步,此时对所有的节点都去setnx,并且设置一个请求过期时间re和锁的过期时间le,同时re必须小于le(可以理解,不然请求3秒才拿到锁,而锁的过期时间只有1秒也太蠢了),此时如果有n / 2 + 1个节点成功拿到锁,此次分布式锁就算申请成功
      缺点 :可靠性还没有被广泛验证,并且严重依赖时间,好的分布式系统应该是异步的,并不能以时间为担保,程序暂停、系统延迟等都可能会导致时间错误(网上还有很多人都对这个方法提出了质疑,比如full gc发生的锁的正确性问题,但是antirez都一一作出了解答,感兴趣的同学可以参考一下这位同学的文章

四、基于zookeeper实现的分布式锁

4.1、实现方式

ZooKeeper是一个专为分布式应用提供一致性服务的关键组件,在其内部采用层次化的文件系统目录树架构设计。每个目录仅允许一个独特的文件名标识以确保数据完整性。通过ZooKeeper机制实现分布式锁管理的具体步骤如下:首先创建一个名为lock的共享资源;随后生成一个唯一的随机字符串作为lockName;接着利用zookeeper.create方法建立该锁定机制;在系统中多个节点上注册并配置好该锁定资源使其具备不可变性特性;当请求方试图获取 lock 时系统会执行以下操作:若当前节点已持有 lock 则立即释放并清除相关记录;若未持有则会将请求转发给下一个节点直至成功获取为止。

(1)建立一个叫做mylock的目标文件夹;
(2)在线程A试图获取锁时,在该目标文件夹中生成了一个临时顺序标识;
(3)在该目标文件夹中访问了所有子文件夹,并对每个文件夹中的数值进行了比较;
(4)在线程B的过程中发现自身数值并非当前最小值,并配置了一个监控机制关注比我稍大的数值变化;
(5)在完成操作后删除自身的标识,并将注意力集中在我所监控的那个特定变量上。

这里是一个Apache开源项目Curator的介绍。它作为一个ZooKeeper客户端设计而成,并且 Curator实现了分布式锁机制中的核心组件InterProcessMutex功能。该组件通过 acquire 方法实现对锁的获取操作,并通过 release 方法完成资源释放过程。

优点 :具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。

缺点 :因为需要频繁的创建和删除节点,性能上不如Redis方式。

4.2、两种利用特性实现原理:

  • 1、利用临时节点特性
    zookeeper的临时节点有两个特性,一是节点名称不能重复,二是会随着客户端退出而销毁,因此直接将key作为节点名称,能够成功创建的客户端则获取成功,失败的客户端监听成功的节点的删除事件
    缺点 :所有客户端监听同一个节点,但是同时只有一个节点的事件触发是有效的,造成资源的无效调度

  • 2、利用顺序临时节点特性
    Zookeeper协议中的顺序临时_nodes继承了_temp_nodes这一特征,在父_nodes下生成子_tmpnodes时会附加一个32位整数作为标识码。具体操作如下:通过键初始化根_tmpnode后,在其下方生成一系列有序tmpnodes;每当尝试获得锁时,在其下方生成一系列有序tmpnodes;随后遍历所有root_tmpnode下的有序tmpnodes并按插入时间排序;找到排序中最小的时间戳对应的tmpnode;若发现该tmpnode恰好匹配当前键,则可顺利获取锁;否则系统需回溯至其父_tmpnode中继续检查。

五、优缺点总结

5.1、基于数据库分布式锁实现

优点 :直接使用数据库,实现方式简单。
缺点

  1. db操作性能欠佳,并且存在锁表风险。
  2. 非阻塞操作失败后需执行轮询操作, 消耗大量CPU资源;
  3. 长时间未提交或长时间轮询可能导致大量连接被消耗。

5.2、基于jdk的并发工具自己实现的锁

优点 :无需额外引入中间件支持,在架构设计上更为简洁
缺点 :实现一个可靠、高可用且高效功能的分布式锁服务确实具有较高的技术挑战。

5.3、基于redis缓存

该系统采用Redis进行配置,并结合独特的ID和Lua脚本实现分布式锁机制。
优点:Redis具备高效的读写性能,并在此基础上实现了基于Redis的分布式锁机制设计;其优势在于能够有效提升系统的吞吐量与响应速度。
缺点:该方案依赖中间层组件作为支撑,在实际应用中可能会导致在分布式系统中节点间数据同步存在潜在风险;此外,在出现异常情况时需由人工进行排查和修复。

2. 基于redis的redlock
优点 :可以解决redis集群的同步可用性问题
缺点

  1. 基于中间件设计,并未被广泛应用于实际项目中, 维护成本较高, 需要部署多个独立的Master节点; 在实现时需同时对多个Master节点申请锁定, 从而降低了系统的效率
  2. 锁删除失败的情况难以通过现有机制进行有效控制
  3. 该系统采用了非阻塞机制, 在操作失败后需进行查询以恢复状态, 同时会占用较多CPU资源

5.4、基于zookeeper的分布式锁

优势:系统完全避免了Redis中的超时问题;采用Zookeeper的数据一致性机制,在完成同步操作后才返回结果;当Zookeeper进行主从节点切换时服务不可用的问题也得到了有效规避;整体稳定性得到显著提升

缺点:基于中间件框架设计,在可靠性和效率之间取得了一定的平衡(但仍然保持较高的水平)。相较于Redis而言,其性能表现略逊一筹。

jdk的方式不太推荐。

  1. 从认知的难易程度来看(按由简单至复杂的顺序),系统模型 < 临时存储层 < AOP框架。
  2. 在开发难度上进行排序(由浅入深),AOP框架介于缓存系统与数据库系统之间。
  3. 在效能高低上排列(由优至劣),缓存系统的运行效能优于AOP框架,并且AOP框架的性能优于数据库系统。
  4. 在稳定性考量范围内排序(由重到轻),AOP框架稳定性高于缓存系统,并且缓存系统的稳定性高于数据库系统。

不存在无懈可击的实现方案

全部评论 (0)

还没有任何评论哟~