失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 分布式锁和数据一致性的讨论——redis集群做分布式锁的风险

分布式锁和数据一致性的讨论——redis集群做分布式锁的风险

时间:2023-11-08 11:34:47

相关推荐

分布式锁和数据一致性的讨论——redis集群做分布式锁的风险

文章目录

写在前面分布式锁的三个属性分布式锁就⼀定要实现这三个属性吗? 实现容错性方法一:基于多个 Redis 节点实现分布式锁问题一:进程可能会被挂起,直到锁的 TTL 过期问题二:墙上时钟在分布式系统中不可靠 ⽅法⼆:复制(Replication)主从异步复制主从同步复制主从半同步复制基于 Quorum 的数据冗余复制分布式共识算法 Paxos 或 Raft Chubby 设计与实现系统架构其它系统特性,可以解决乱序到达等问题 ZooKeeper 实现分布式锁etcd 实现分布式锁分布式互斥问题(Distributed mutual exclusion)总结复盘

写在前面

分布式锁的应用场景就是高并发,高并发下如果锁出了任何问题,就可能会导致脏数据的产生,那么,用redis做分布式锁真的安全吗?

分布式锁的三个属性

互斥(Mutual Exclusion):同⼀时刻只有⼀个客户端持有锁避免死锁(Dead lock free):设置锁的存活时间(Time To Live,TTL)容错(Fault tolerance):避免单点故障,锁服务要有⼀定容错性

分布式锁就⼀定要实现这三个属性吗?

未必!

如果你的业务⽆关紧要,如果你的业务是可以挂掉的内部系统,如果你的业务可以接受出错的时候,直接返回错误给⽤户,那⼀个单节点 Redis 或关系型数据库的分布式锁就能满⾜你的需求。

如果你的业务不允许随意宕机,那我们就要来好好讨论容错性了。

实现容错性

讨论“分布式”,意味着可能会发⽣各种各样的错误,Google 公布的数据显示:

参考引⽤:/people/jeff/Stanford-DL-Nov-.pdf

方法一:基于多个 Redis 节点实现分布式锁

加锁:

1.依次对多个 Redis 实例进⾏加锁,使⽤单实例 Redis 的加锁命令;

2.每次获取锁的超时时间远⼩于 TTL,超时则认为失败,继续向下⼀个节点获取锁;

3.计算总消耗时间,只有在超过半数节点都成功获取锁,并且总消耗时间⼩于 TTL,才认为成功持有锁;

4.成功获取锁后,要重新计算 TTL = TTL - 总消耗时间;

5.如果获取锁失败,要向所有 Redis 实例发送解锁命令。

解锁:

1.删除所有实例中的 key

也就是我们说的-红锁(RedLock),实现起来其实挺麻烦的,但是,实现了RedLock又引发了其他的问题。

问题一:进程可能会被挂起,直到锁的 TTL 过期

当客户端1获取到第一个锁之后,程序恰好进行GC了stop-the-world(或者其他原因导致的挂起),而这个时间恰好又超过了过期时间,就会造成客户端2获取不到锁。

该问题可以延长TTL,但是治标不治本,仍有系统挂起超时的隐患。

问题二:墙上时钟在分布式系统中不可靠

计算机科学家类Leslie Lamport的逻辑时钟论文中,提出了物理时钟在分布式系统中并不可靠,不能用物理时钟来判断事件的先后顺序。

分布式集群靠 NTP 时钟同步,但仍未能保证每台机器⾛时相同时间戳不可靠:/posts/299-the-trouble-with-timestampsGoogle Spanner 设计了⼀套复杂的时间机制(TrueTime)来实现强⼀致性

时钟漂移问题是真实存在的

也就是说,RedLock虽然避免了单点故障,但是有着一定程度上的时间不一致问题,仍会导致高并发下锁的不一致问题。

⽅法⼆:复制(Replication)

不依赖多个 Redis 节点,数据存储服务⾃身保证容错性复制有很多种⽅法,但要保证数据强⼀致性,即 CAP 定理中的 CP提供⼀个强⼀致、能够容错⼀定数量节点的分布式锁服务

主从异步复制

1.主节点收到写请求,执⾏完毕

2.主节点响应客户端

3.主节点复制到从节点

如果复制之前主节点宕机、损坏——会造成数据丢失。

主从同步复制

1.主节点收到写请求,执⾏完毕

2.主节点复制到从节点

3.主节点收到所有从节点的确认信息,响应客户端

如果任意⼀个节点宕机、损坏、I/O 阻塞——会造成系统可⽤性降低(但数据没丢)

主从半同步复制

1.主节点收到写请求,执⾏完毕

2.主节点复制到从节点

3.主节点收到⼀个从节点的确认信息,响应客户端

4.其余从节点继续复制数据

如果从节点 2 因为某种原因写失败——会造成从节点数据不⼀致

基于 Quorum 的数据冗余复制

●W + R > N 且 W > N/2

●需要解决冲突

●需要数据修复

●案例:Dynamo、Cassandra

只能实现最终⼀致性

分布式共识算法 Paxos 或 Raft

实现强⼀致性(线性⼀致性)容忍不超过半数节点故障案例:Spanner、etcd、CockroachDB……

缺点也不是没有,就是⼯程上⽐较难实现

Chubby 设计与实现

系统架构

如 Chubby 由⼀个 Master 和多个副本组成,只有 Master 能够处理请求和读写⽂件,副本通过 Paxos 算法复制 Master 来实现容错提供类 UNIX ⽂件名称:/is/foo/wombat/pouch,可以降低培训难度每个节点包含⼀些 metadata,存储单调递增的编号、访问控制 ACL 策略等

其它系统特性,可以解决乱序到达等问题

sequencer:引⼊序列号,可以通过检查序列号是否合法来避免乱序到达问题事件:⽀持事件监听,例如:⽂件内容改变、⼦节点添加、锁的获取缓存:Chubby 通过客户端内存缓存来减少读流量session 和 keepalivefail-overs:能够处理 Master 宕机或重新选举

ZooKeeper 实现分布式锁

1.调⽤ create(),并设置 sequence 和 ephemeral 标志;

2.调⽤ getChildren() 获取⼦节点列表,不要设置 watch 标志(可以避免惊群效应);

3.检查⼦节点列表,如果步骤 1 创建的节点的序列号最⼩,则客户端持有该分布式锁,结束;

4.如果创建的序列号不是最⼩的,则客户端调⽤ exist(p, watch=true) 监听⽐当前序列号⼩⼀位的节点(记为 p),当 p 被删除时收到事件通知;

5.如果 exist() 返回 null,即前⼀个分布式锁被释放了,转到步骤 2;否则需要⼀直等待步骤 4 中 watch 的通知。

(惊群效应——持有锁的线程释放锁,剩下的线程争先抢后的竞争锁,导致cpu异常偏高)

参考引⽤:/doc/r3.7.0/recipes.html#sc_recipes_Locks

etcd 实现分布式锁

共识算法 + TTL + 序列号 + watch 机制

参考引⽤:/etcd-io/etcd/blob/main/tests/integration/clientv3/concurrency/mutex_test.go

分布式互斥问题(Distributed mutual exclusion)

其他分布式算法简单介绍

● Lamport

● Ring-based

● Ricart-Agrawala

● Maekawa

总结

复盘

1.系统设计本质上是在做取舍(trade-off),没有完美的架构,更多的情况是要在⼏个互相竞争的问题之间进⾏权衡。有时候,基于单节点 Redis 实现的分布式锁就能满⾜需求;

2.微信实现了⾃⼰的 Chubby,Facebook 实现了 Delos,但实现⼀个共识协议(⽆论是 Paxos 还是 Raft)是颇具难度的,⽆⾮必要,不要轻易造轮⼦。

如果觉得《分布式锁和数据一致性的讨论——redis集群做分布式锁的风险》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。