前言:
在分布式系统中,一致性( c )意味着每次读取都会获取最新的写入数据或异常,可用性( a )意味着可以对每个请求得到非异常的响应,而不保证获取最新的写入数据。 分区容错( p )意味着即使节点之间的网络异常,系统也将继续操作。
redis是AP架构,所谓牺牲的一致性,如果reid在主从同步锁的key时,出现挂机情况,那这样是会丢失锁的。出现主从架构锁是失效问题
解决方案:
RedLock (红锁方案已经被淘汰) : 底层实现手段和zookeeper类似,客户端发送setnx指令,同时向多个redis节点发信息,超过半数redis节点加锁成功,才会返回成功。
在Redis的分布式环境中,假设有N个Redis master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。我们假设有5个Redis master节点,
为了取到锁,客户端应该执行以下操作:
- 获取当前Unix时间,以毫秒为单位。
- 依次尝试从N个实例,使用相同的key和随机值获取锁。在步骤2,当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis实例。
- 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
- 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
- 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。
天生的bug :
1 。高并发多客户端同时抢占锁,会导致任何一个客户端都无法同时获取半数以上的锁,导致永远加锁失败。(脑裂现象),所以当客户端从大多数Redis实例获取锁失败时,应该尽快地释放(部分)已经成功取到的锁,这样其他的客户端就不必非得等到锁过完“有效时间”才能取到(然而,如果已经存在网络分裂,客户端已经无法和Redis实例通信,此时就只能等待key的自动释放了,等于被惩罚了)。
2. 算法安全问题 :假设有个客户端用同一个key从多个redis获取到了锁,由于时间漂移问题,每个redis上的锁失效时间是不一样的,
如果客户端在获取到大多数redis实例锁,使用的时间接近或者已经大于失效时间,客户端将认为锁是失效的锁,并且将释放掉已经获取到的锁,所以我们只需要在有效时间范围内获取到大部分锁这种情况。在上面已经讨论过有争议的地方,在锁的最小失效
时间内,将没有客户端再次取得锁。所以只有一种情况,多个客户端会在相同时间取得N/2+1实例的锁,那就是取得锁的时间大于失效时间(TTL time),这样取到的锁也是无效的.
这个算法是没办法证明正确,或者是有bug的。
zookeeper是CP架构,不保证高可用,消息传进去,并不是每一次都能返回成功。zookeeper集群中,主节点接收到key,会将key同步到一半以上的从节点,才会将成功消息返回。保证了服务挂机时不会丢失锁。zookeeper性能是不如redis的。