以下内容为目前自己理解的总结,如有错误请大家指正。
什么是锁
在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。
而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。这个标记可以理解为锁。
不同地方实现锁的方式也不一样,只要能满足所有线程都能看得到标记即可。如java中synchronize是在对象头设置标记,Lock接口的实现类基本上都只是某一个volitile修饰的int型变量其保证灭个线程都能拥有对该int的可见性和原子修改,linux内核中也是利用互斥量或信号量等内存数据做标记。
除了利用内存数据做锁其实任何互斥的都能做锁(只考虑互斥情况),如流水表中流水号与时间结合做幂等校验可以看作是一个不会释放的锁,或者使用某个文件是否存在作为锁等。只需要满足在对标记进行修改能保证原子性和内存可见性即可。
分布式
分布式情况
此处主要指集群模式下,多个相同服务同时开启.
分布式与单机情况下最大的不同在于其不是多线程而是多进程。
多线程由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。而进程之间甚至可能都不在同一台物理机上,因此需要将标记存储在一个所有进程都能看到的地方。
分布式锁
当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。
与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题。(我觉得分布式情况下之所以问题变得复杂,主要就是需要考虑到网络的延时和不可靠。。。一个大坑)
分布式锁还是可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存如Redis、Memcache。至于利用数据库、文件等做锁与单机的实现是一样的,只要保证标记能互斥就行。
单机Redis锁
基本锁
原理:利用Redis的setnx如果不存在某个key则设置值,设置成功则表示取得锁成功。
缺点:如果获取锁后的进程,在还没执行完的时候挂调了,则锁永远不会释放。
改进型
改进:在基本型是锁上的setnx后设置expire,保证即使获取锁的进程不主动释放锁,过一段时间后也能自动释放。
缺点:
setnx与expire不是一个原子操作,可能执行完setnx该进程就挂了。
当锁过期后,该进程还没执行完,可能造成同时多个进程取得锁。(貌似这个问题目前还没有很优雅的解决方案)
再改进
改进:利用Lua脚本,将setnx与expire变成一个原子操作,可解决一部分问题。
缺点:还是锁过期的问题。
步骤
1. 直接调用Lua脚本原子setnx同时expire,设置一个随机值。
2. 获取到锁则执行同步代码块,没获取则根据业务场景可以选择自旋、休眠、或做一个等待队列等拥有锁进程来唤醒(类似Synchronize的同步队列)。
3. 当同步代码块执行完成,先判断锁的key是否是自己设置的,如果是则删除key(可利用Lua做成原子操作),不是则表明自己的锁已经过期,不需要删除。(这时候就出现了多进程同时有锁的问题了)
总结
一般情况下直接用setnx加expire就够了,但从安全性的角度看还是存在一下几个问题:
单点问题。单机Redis只在单机上,如果单机down了,那么所有需要用分布式锁的地方均获取不到锁,全部阻塞。需要做好降级的处理。
可能出现多进程同时拥有锁。
Redlock
Redlock是Redis的作者antirez给出的集群模式的Redis分布式锁,它基于N个完全独立的Redis节点(通常情况下N可以设置成5)。
步骤
1. 获取当前时间(毫秒数)。
2. 按顺序依次向N个Redis节点执行获取锁的操作。获取锁的操作与单机锁一样。
3. 如果获取锁成功的节点数>=N/2+1,则再计算获取锁的时间有没有超过锁过期时间(可考虑设置一个必须留多长的时间给代码块执行),如果超过了则认为取锁失败。
4. 如果取锁失败则应该对所有节点进行释放锁的操作。
优化
当有5个节点,某次上锁对a,b,c三个节点上锁成功,而后c马上down了,此时还没通过AOF或RDB写入磁盘。而后c又马上恢复,此时c没有上锁数据,因此此时可能出现c,d,e三个节点被别的进程上锁。所以在节点恢复时应该延时起码一个锁的过期时间。
Zookeeper锁
zookeeper锁相关基础知识
zk一般由多个节点构成(单数),采用zab一致性协议。因此可以将zk看成一个单点结构,对其修改数据其内部自动将所有节点数据进行修改而后才提供查询服务。
zk的数据以目录树的形式,每个目录称为 znode, znode中可存储数据(一般不超过1M),还可以在其中增加子节点。
子节点有三种类型。序列化节点,每在该节点下增加一个节点自动给该节点的名称上自增。临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除。最后就是普通节点。
Watch机制,client可以监控每个节点的变化,当产生变化会给client产生一个事件。
zk基本锁
原理:利用临时节点与watch机制。每个锁占用一个普通节点/lock,当需要获取锁时在/lock下创建一个临时节点,创建成功则表示获取锁成功,失败则watch/lock节点,有删除操作后再去争锁。临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁。
缺点:所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大。
zk锁 优化
原理:上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,知识其序号不同。只有序号最小的可以拥有锁,当需要不是最小的则watch序号排在前面的一个节点(公平锁)。
步骤:
1. 在/lock节点下创建一个有序临时节点(EPHEMERAL_SEQUENTIAL)。
2. 判断创建的节点序号是否最小,如果是最小则获取锁成功。不是则取锁失败,然后watch序号比本身小的前一个节点。
3. 当取锁失败,设置watch后则等待watch事件到来后,再次判断是否序号最小。
4. 取锁成功则执行代码,最后删除本身节点,释放了锁。
分布式锁总结
分布式锁存在的问题
均可能存在多进程拥有锁的情况。redis锁主要是expire时间与代码执行时间的问题,zk锁的问题在于zk是通过心跳监控进程存活状态,如果进程进行GC pause或者因为网络原因导致很长时间没与zk联系,则将导致zk认为进程已挂,而后锁自动释放,而此时进程并未挂任然在执行。
Redlock锁的时间问题。由于redis的expire的实现是通过pexpireat,如果某个节点发生时钟跳跃,则该节点可能过早释放锁导致一系列问题。
解决方案
获取锁时提供一个fencing token(两种说法,一种说需要有序,一种说随机值就可以,我觉得随机值就可以),在进程获取锁后对数据进行操作时,数据所在的资源服务器需要去锁中查看当前token,如果token对的才执行,不对则放弃执行。
我觉得对于放弃执行的应该在我们的代码块中增加类似事物的rollback的操作。因此如果资源服务器拒绝了我们的操作则表明此时起码已经存在了另外一个进程拥有锁了,为了保证数据安全性不能继续执行,因此需要回滚到执行代码块之前而继续去竞争锁。
至于Redis锁的时间问题,Antirez说在运维层面是可以控制时钟跳跃的区间的,只要能控制跳跃区间与expire的比例就没问题,详细可看《基于Redis的分布式锁真的安全吗?》
总结
大多数时候采用zk锁就好了,没必要再考虑安全性的问题。其实也可以通过zk锁+幂等校验来达到双层保障。
fencing 机制需要对数据服务进行修改适配,个人觉得没这个必要吧。。。
分享到:
相关推荐
在现代互联网应用中,分布式系统架构因其良好的扩展性和高可用性而被广泛采用。然而,在分布式环境下...通过本文的介绍和分析,相信开发者们可以更加深入地理解并应用Redis分布式锁,从而提升整个系统的性能和可靠性。
本篇文章将深入探讨如何利用Redis实现分布式锁以及如何构建一个基于Redis的任务队列。 分布式锁是解决多节点共享资源时防止数据冲突的关键机制。在Go中,我们通常通过与Redis交互来实现这一功能。Redis提供了`SETNX...
### 分布式锁与信号量:分布式同步控制 #### 一、引言 在现代分布式系统中,确保各个节点间的协调一致以及数据的一致性至关重要。分布式同步控制技术旨在通过一系列机制和算法来实现这一点,确保分布式环境下的...
分布式锁是一种在分布式系统中实现同步机制的关键技术。在大型分布式环境中,多个节点可能同时访问共享资源,这就需要一种机制来确保并发操作的正确性。ZooKeeper,一个由Apache基金会开发的分布式协调服务,提供了...
基于FourInOne可以轻松实现分布式配置信息,集群管理,故障节点检测,分布式锁,以及淘宝configserver等等协同功能。 其次, FourInOne可以提供完整的分布式缓存功能。如果对一个中小型的互联网或者企业应用,仅仅...
为了在分布式环境中实现这一点,我们可以利用Redis提供的某些特性,如单个命令的原子执行、发布/订阅机制、分布式锁等。这些工具可以帮助我们在不同节点间协调事务,保证在并发环境下也能正确执行。 在C++中与Redis...
现在呢,我新开辟了一个新项目,仓库地址为:这个项目我跟着大牛老师做一做,并且我会根据自己的理解进行代码的重构(我觉得有很多值得优化的点,还有就是支付,我还是想用原生的接入方式而不是课程中说的接入它的...
开发包里自带了一系列傻瓜上手demo,包括分布式计算、统一配置管理、集群管理、分布式锁、分布式缓存、MQ等方面, 每个demo均控制在少许行代码内,但是涵盖了Fourinone主要的功能,方便大家快速理解并掌握。...
7. **分布式锁**:为了保证并发控制,XAtest可能使用了分布式锁机制,确保在多线程或多节点环境中,同一资源不被多个事务同时修改。 8. **原子广播(Atomic Broadcast)**:在分布式系统中,保证事务的提交信息在...
在分布式系统开发的过程中,需要考虑到分布式系统特有的问题,如分布式事务处理、分布式锁、一致性协议等。其中,分布式事务处理要保证在多个节点上执行的多个操作要么全部成功,要么全部不发生,以保持数据的一致性...
这些示例可能包括两阶段提交(2PC)、补偿型事务(Saga)、分布式锁等策略的实现。两阶段提交是最常见的分布式事务解决方案,但在高并发场景下可能会出现性能问题。Saga是一种长事务的解决方式,它将一个大事务拆分...
Redis因其高可用性、快速响应和丰富的数据结构,成为了分布式锁的理想选择。 Redis中的SetNX命令是创建分布式锁的关键。它尝试设置一个键值对,只有当该键不存在时才会成功,这使得在同一时间只有一个客户端能获取...
在分布式系统或者多线程编程中,锁机制是保证数据一致性、避免竞态条件的关键工具。本主题将深入探讨“同步读锁”和“异步互斥写锁”的概念、实现原理以及源码分析,主要以`LockDemo`为例进行阐述。 首先,我们需要...
文章对监测数据进行了分析,以清晰理解混凝土水化热释放作用下底板的应变状态。监测结果显示,分布式光纤感测技术在实时施工监测中的潜力,同时为大型水工结构的设计、施工和维护提供了重要的见解。在关键词中也强调...
实现这一点通常通过使用某种哈希算法或者随机数生成器,并结合分布式环境下的协调机制(如分布式锁或序列号分配服务)来保证。 3. **不连续**:go-shortid生成的ID序列不是连续的,这可以增加攻击者猜测其他ID的...
2. 分布式算法:涵盖共识算法(如Paxos和Raft)、分布式锁服务、分布式数据存储(如Gossip协议)和分布式计算模型(如MapReduce)。 3. 容错机制:包括故障检测、恢复策略、备份和复制技术,以及如何设计容错系统来...
安装完成后,我们就可以在自己的Python项目中导入并使用"aioextensions"提供的功能,如异步Zookeeper客户端、分布式锁等。 总结来说,aioextensions是一个强大的Python库,它结合了异步I/O、Zookeeper支持和云原生...
C#可以通过Entity Framework等ORM工具处理数据库事务,以及分布式锁等机制来保障数据一致性。 5. **故障恢复**:分布式系统必须能够处理节点故障。C#提供了事件驱动的编程模型,可以利用异常处理、重试策略、断路器...
Zookeeper是由Apache Hadoop项目开发的一个开源组件,它提供了一种可靠的分布式一致性服务,广泛应用于数据分布式存储、配置管理、命名服务、分布式锁和组服务等领域。它的设计目标是简单、高效且可扩展,通过强一致...
1. Distributed Lock:分布式锁,提供公平锁和非公平锁。 2. CountDownLatch:计数器,用于同步多个线程。 3. Semaphore:信号量,用于限制并发访问数量。 4. Barrier:栅栏,用于同步多线程执行到某一点。 5. ...