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

写在前面

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

分布式锁的三个属性

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

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

未必!
如果你的业务⽆关紧要,如果你的业务是可以挂掉的内部系统,如果你的业务可以接受出错的时候,直接返回错误给⽤户,那⼀个单节点 Redis 或关系型数据库的分布式锁就能满⾜你的需求。
如果你的业务不允许随意宕机,那我们就要来好好讨论容错性了。

实现容错性

讨论“分布式”,意味着可能会发⽣各种各样的错误,Google 公布的数据显示:
在这里插入图片描述
参考引⽤:https://research.google.com/people/jeff/Stanford-DL-Nov-2010.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 时钟同步,但仍未能保证每台机器⾛时相同
  • 时间戳不可靠:https://aphyr.com/posts/299-the-trouble-with-timestamps
  • Google 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 和 keepalive
  • fail-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异常偏高)
在这里插入图片描述
参考引⽤:https://zookeeper.apache.org/doc/r3.7.0/recipes.html#sc_recipes_Locks

etcd 实现分布式锁

共识算法 + TTL + 序列号 + watch 机制
在这里插入图片描述
参考引⽤:https://github.com/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)是颇具难度的,⽆⾮必要,不要轻易造轮⼦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃了也弱了。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值