首页天道酬勤分布式锁和一般锁有什么区别,redis分布式锁的原理

分布式锁和一般锁有什么区别,redis分布式锁的原理

张世龙 05-03 21:11 115次浏览

前言单机体系结构的应用可以通过直接使用同步或ReentrantLock解决多线程资源竞争问题。 如果公司业务发展迅速,可以通过部署多个服务节点来提高系统的并行处理能力。 因为本地锁定仅限于当前应用的线程。 在高并发方案中,分布式锁定很有帮助,因为群集中一个APP应用程序的本地锁定不排斥其他APP应用程序的资源访问,而是导致数据不一致。

常见分布式锁的应用场景秒杀活动、优惠券抢购、接口乘幂等性检查等

常用的分布式锁定1 .基于数据库实现分布式锁定1.1悲观锁定使用select … where … for update排他锁定

请注意,的其他附加功能与实现基本一致。 必须注意的是“where name=lock”,name字段必须跟踪索引。 否则,桌子会被锁定。 在某些情况下,例如,如果表不大,mysql优化程序将不再使用此索引,从而导致表锁定问题。

1.2乐观锁定这一乐观锁定与上述最大的区别在于基于CAS思想,它不具有排他性,不等待锁定,消耗资源,操作中不存在并发冲突,只有在更新版本失败后才能发现。 我们的收购,秒杀使用了这一实现防止了超额销售。 通过增加版本号字段实现乐观锁定

2 .基于JDK的实现思路:使用JDK并发工具启动控制唯一资源的其他服务。 例如,在服务中维护concurrentHashMap,当其他服务请求某个key进行锁定时,通过该服务公开的端口在网络通信中发送消息,当服务端解析该消息时,concuurent 当然,如果希望使用java的bio、nio或统一dubbo、spring cloud feign实现通信,也没问题

缺点:这种方式的分布式锁定看起来很简单,但考虑到可用性、可靠性、效率和可扩展性,编码难度很大。

3 .在高速缓存基础上以高并发方案实现分布式锁定时,APP应用程序在运行过程中经常受到网络、CPU、内存等因素的影响,因此实现线程安全的分布式组件需要考虑很多case 这个分布式锁有三个重要的考虑因素。

互斥(只有一个客户端可以获取锁定)

不能进行死锁

容错(大多数redis节点只需要创建此锁定)。

以下是redis分布式锁的各种实现方法和缺点。 根据时间的推移对:进行排序

3.1直接setnx直接利用setnx,运行业务逻辑后,调用del解锁。 简单粗暴!

缺点:如果setnx成功、还没有发布时间、服务已过期,则永远不会获取此密钥

3.2要修复在setnx上设置过期日期的第一种方法的缺陷,请在setnx上获取锁定,然后在expire上设置过期日期。 如果服务锁定,过期时将自动释放

缺点: setnx和expire是两种方法,不能保证原子性。 在setnx之后,如果expire还来不及,服务将锁定,仍然会出现无法释放锁定的问题

3.3 set nx px redis公式为了解决第二种方式存在的缺点,在2.8版中在set指令中添加了扩展参数nx和ex,保证了setnx expire的原子性,使用方法: set key value ex 5 nx

缺点:如果事务尚未在过期时间内执行,则锁会提前自动释放,其他线程可以获取锁上述缺点还会导致当前线程释放其他线程占用的锁

3.4添加事务id的上述第一个缺点是没有特别好的解决方案,并且只能将有效期限设置得尽可能长。 另外,最好不要执行耗时的任务的第二个缺点可以理解为当前线程可能释放对其他线程的锁定。 问题被转换为确保线程只能释放当前线程具有的锁定,即在设置NX时使value成为任务的唯一id。 发布时首先get key比较value是否与当前id相同,“是”则发布,否则抛出异常回滚,其实也改变了第一个问题

缺点:将get key和value与id进行比较只是两个步骤,不能保证原子性

3.5 set nx px事务id lua我们可以编写脚本来使用lua与getkey进行比较。 jedis/luttce/redisson支持lua脚本

缺点:在群集环境中,向主节点申请了分布式锁定,但由于redis的主从同步是异步进行的,所以主节点将nx写入内存后,直接返回,成功获取了客户端的锁定。 此时,即使主节点锁定,来不及同步数据,另一个节点上升为主节点,其他线程也可以获得锁定

3.6 redlock为了解决上述redis集群中的分布式锁定问题,提出了redis作者antirez的red lock的概念。 假设群集中的所有n个master节点都是完全独立的,并且没有主从同步,则所有节点都将进入setnx,并设置到期日期re和请求锁定的到期日期le。 同时re必须比le小。 (否则,在获得锁之前需要3秒,而锁只有1秒的有效期是愚蠢的。 )此时,如果n/2的一个节点取得了锁定,则此次分布式锁定即使申请成功

缺点:可靠性尚未得到广泛验证,严重依赖时间,分散性好

系统应该是异步的,并不能以时间为担保,程序暂停、系统延迟等都可能会导致时间错误(网上还有很多人都对这个方法提出了质疑,比如full gc发生的锁的正确性问题,但是antirez都一一作出了解答,感兴趣的同学可以去官网溜一圈!)

4. 基于zookeeper实现的分布式锁 4.1. 实现方式

ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:

(1) 创建一个目录mylock;

(2) 线程A想获取锁就在mylock目录下创建临时顺序节点;

(3) 获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;

(4) 线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;

(5) 线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。

4.2. 两种利用特性实现原理: (1) 利用临时节点特性 zookeeper的临时节点有两个特性,一是节点名称不能重复,二是会随着客户端退出而销毁,因此直接将key作为节点名称,能够成功创建的客户端则获取成功,失败的客户端监听成功的节点的删除事件

缺点: 所有客户端监听同一个节点,但是同时只有一个节点的事件触发是有效的,造成资源的无效调度

(2) 利用顺序临时节点特性 zookeeper的顺序临时节点拥有临时节点的特性,同时,在一个父节点下创建创建的子临时顺序节点,会根据节点创建的先后顺序,用一个32位的数字作为后缀,我们可以用key创建一个根节点,然后每次申请锁的时候在其下创建顺序节点,接着获取根节点下所有的顺序节点并排序,获取顺序最小的节点,如果该节点的名称与当前添加的名称相同,则表示能够获取锁,否则监听根节点下面的处于当前节点之前的节点的删除事件,如果监听生效,则回到上一步重新判断顺序,直到获取锁。

这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。优点: _具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。_缺点: 因为需要频繁的创建和删除节点,性能上不如Redis方式。

总结 1.基于数据库分布式锁实现

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

缺点:(1)db操作性能较差,并且有锁表的风险 (2)非阻塞操作失败后,需要轮询,占用cpu资源; (3)长时间不commit或者长时间轮询,可能会占用较多连接资源

2.基于jdk的并发工具自己实现的锁

优点: 不需要引入中间件,架构简单

缺点: 编写一个可靠、高可用、高效率的分布式锁服务,难度较大

3.基于redis缓存

(1)redis set px nx + 唯一id + lua脚本

优点: redis本身的读写性能很高,因此基于redis的分布式锁效率比较高

缺点: 依赖中间件,分布式环境下可能会有节点数据同步问题,可靠性有一定的影响,如果发生则需要人工介入

4.基于redis的redlock

优点: 可以解决redis集群的同步可用性问题

缺点:(1)依赖中间件,并没有被广泛验证,维护成本高,需要多个独立的master节点;需要同时对多个节点申请锁,降低了一些效率 (2)锁删除失败 过期时间不好控制 (3)非阻塞,操作失败后,需要轮询,占用cpu资源;

5.基于zookeeper的分布式锁

优点: 不存在redis的超时、数据同步(zookeeper是同步完以后才返回)、主从切换(zookeeper主从切换的过程中服务是不可用的)的问题,可靠性很高

缺点: 依赖中间件,保证了可靠性的同时牺牲了一部分效率(但是依然很高)。性能不如redis。

综上所得:

jdk的方式不太推荐。

从理解的难易程度角度(从低到高)数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高)Zookeeper >= 缓存 > 数据库

从性能角度(从高到低)缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低)Zookeeper > 缓存 > 数据库

没有绝对完美的实现方式,具体要选择哪一种分布式锁,需要结合每一种锁的优缺点和业务特点而定。

乐观锁使用场景,悲观锁和乐观锁使用场景