您好,登录后才能下订单哦!
这篇文章主要讲解了“分布式锁的原理与实现是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“分布式锁的原理与实现是什么”吧!
如下图所示,一个应用被部署到多个机器上做负载均衡。为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,我们该如何解决这个问题呢?
在传统单体应用单机部署的情况下,可以使用并发处理相关的功能(如Java并发处理相关的API:ReentrantLcok或synchronized)进行互斥控制来解决。但是,随着业务的发展,系统架构也会逐步优化升级,原本单体单机部署的系统被演化成分布式集群系统,由于分布式系统多线程、多进程并且分布在多个不同机器上,这将使原单机部署情况下的并发控制锁策略无法满足,并不能提供分布式锁的能力。为了解决这个问题就需要一种跨机器的互斥机制来控制共享资源的访问,这就是分布式锁解决的难题!
针对分布式锁的目的来反向推导其应用场景,主要包括两类:
1、处理效率提升:应用分布式锁,可以减少重复任务的执行,避免资源处理效率的浪费;
2、数据准确性保障:使用分布式锁可以放在数据资源的并发访问,避免数据不一致情况,甚至数据损失等。
分布式的CAP理论:
任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。
通常情况下,大家都会牺牲强一致性来换取系统的高可用性,这样我们很多的场景,其实是只需为了保证数据的“最终一致性”。
需要注意的是,这个最终时间需要是用户可以接受的范围内的。
另外,要实现分布式锁,需要具备一些条件,主要包括以下几项:
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、获取锁与释放锁的高可用及高性能;
3、具备非阻塞锁特性,获取不到锁将直接返回获取锁失败;
4、具备锁失效机制,防止死锁。
上述条件,主要突出锁本身的提效和保障准确性的应用特性,同时避免其本身对资源访问造成影响;
关于分布式锁的实现,可以分别控制在不同的环节。
常见的主要分为以下这几种:
ZooKeeper 是一个分布式协调服务的开源框架。主要用来解决分布式集群中应用系统的一致性的问题,例如怎样避免同时操作同一数据造成脏读的问题。ZooKeeper 本质上是一个分布式的小文件存储系统。提供基于类似于文件系统的目录树方式的数据存储,并且可以对树种的节点进行有效管理。
那如何使用ZooKeeper实现分布式锁?
1)客户端连接zookeeper,并在/tmp下创建临时的且有序的子节点,第一个客户端对应的子节点为lock-0000,第二个为lock-0001,以此类推;
2)客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听刚好在自己之前一位的子节点删除消息,获得子节点变更通知后重复此步骤直至获得锁;
例如/tmp下的子节点列表为:lock-0000、lock-0001、lock-0002,序号为1的客户端监听序号为0的子节点删除消息,序号为2的监听序号为1的子节点删除消息。(业务代码执行完即删除子节点)
3)执行业务代码流程,删除当前客户端对应的子节点,锁释放。
ZooKeeper分布式锁方式,性能相对Redis方式较差,主要原因是写操作(获取锁释放锁)都需要在Leader上执行,然后同步到follower。
Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
主要的优势包括:
性能极高 – Redis能读的速度是11w+次/s,写的速度是8w+次/s
丰富的数据类型 – Redis主要支持 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型
原子性 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行
丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
Redis实现简单分布式锁过程:
(1)获取锁:使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁:设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁:通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
利用Redis实现分布式锁,实现可能存在的缺点:
在执行delete进行释放锁的时候,假如操作删除锁动作失败,那此 Key-Value 过期时间则不好控制,可能会一直存在,可能对后续数据验证造成影响。
数据库层面是最终数据写入的时候,对数据做写入控制处理,算是分布式锁的最终末端环节。主要包括以下三种方式,下面分别介绍一下。
实现方式一:唯一索引
UNIQUE KEY `uidx_name` (`name`) USING BTREE;
上述case中,我们对 name
字段做了索引的唯一性约束,当存在多个新增数据请求同时提交到数据库的话,数据库自身则会利用唯一索引,来保证数据的唯一性。
实现方式二:排他锁
执行以下SQL:
SELECT status FROM users WHERE id = 3 FOR UPDATE;
假如,在另一个事务中再次执行:
SELECT status FROM users WHERE id = 3 FOR UPDATE;
则第二个事务会一直等待上一个事务的提交,此时第二个查询处于阻塞的状态。
排它锁的应用:
在进行事务操作时,通过 “FOR UPDATE” 语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。
实现方式三:乐观锁
实现逻辑:乐观锁每次在执行数据修改操作时,都会带上一个数据版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1 操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA 问题。
除了 version 以外,还可以使用时间戳,因为时间戳天然具有顺序递增性。
比较麻烦的一点:就是在操作业务前,需要先查询出当前的 version 版本。
数据库分布式锁实现可能存在的缺点:
DB操作性能较差,并且有锁表的风险;
非阻塞操作失败后,需要轮询,占用cpu资源;
长时间不commit或者长时间轮询,可能会占用较多连接资源
感谢各位的阅读,以上就是“分布式锁的原理与实现是什么”的内容了,经过本文的学习后,相信大家对分布式锁的原理与实现是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。