在 WhatsApp 这样大规模、高并发的分布式系统中,传统的悲观锁(Pessimistic Locking)几乎不可能用于核心的消息处理流程。其架构设计哲学是尽量避免对锁的依赖,以最大化系统的吞吐量和可用性。然而,在某些特定场景下,为了保证数据的一致性,WhatsApp 会使用乐观锁(Optimistic Locking)。
1. 乐观锁与悲观锁的定义
悲观锁(Pessimistic Locking): 在数据被读取时就对其进行锁定,假设会发生冲突。在整个事务完成之前,其他事务无法修改或读取被锁定的数据。
优点: 保证强一致性,避免冲突。
缺点: 并发度低,可能导致死锁和性能瓶颈,尤其在分布式环境下实现复杂且代价高昂。
乐观锁(Optimistic Locking): 不对数据进行加锁,假设冲突不常发生。事务在读取数据时记录一个版本号或时间戳。在提交更新时,检查版本号/时间戳是否与读取时一致,如果不一致则说明数据已被其他事务修改,当前事务失败并进行重试。
优点: 并发度高,无死锁风险,适用于读多写少、冲突不频繁的场景。
缺点: 冲突发生时需要回滚和重试,如果冲突频繁,性能可能下降。
2. WhatsApp 的主要策略:避免悲观锁
WhatsApp 的核心消息发送和接收流程几乎不使用悲观锁,原因如下:
海量并发: 每秒数百万的消息请求,如果对每条消息或相关的用户状态进行悲观锁,系统将立即成为瓶颈,吞吐量无法提升。
分布式复杂性: 在全球多个数据中心和数千个 爱沙尼亚 whatsapp 数据库 节点之间实现一个分布式悲观锁系统是极其复杂的工程挑战,会引入不可接受的延迟和死锁风险。
高可用性优先: 悲观锁倾向于牺牲可用性来换取强一致性(CAP 定理中的 CP)。WhatsApp 作为实时通讯应用,可用性是其生命线。
数据库特性: WhatsApp 广泛使用的分布式 NoSQL 数据库(如 Apache Cassandra 或 ScyllaDB)本身就是为高可用和高写入吞吐量而设计的,它们通常不提供或很少使用传统意义上的悲观锁。
3. 乐观锁在 WhatsApp 中的应用场景
虽然不使用悲观锁,但在需要确保特定数据操作的原子性和一致性时,WhatsApp 会选择使用乐观锁。
a. 轻量级事务(LightWeight Transactions, LWT)/ 比较并交换(Compare-and-Swap, CAS)操作:
数据库支持: Cassandra 等 NoSQL 数据库提供了 LWTs 或 CAS 操作,它们是乐观锁的一种实现。一个写入操作只有在满足特定条件(例如,某个字段的当前值与预期值一致)时才会被执行。
应用场景:
用户配置文件更新: 当用户修改其个人状态、头像或“关于”信息时,确保对该用户数据的更新是原子的。例如,只有当版本号匹配时才更新,防止两个并发更新导致数据丢失。
群组元数据更新: 例如,更改群名称、群头像、或者添加/移除群成员(针对单个成员的更新)。这些操作相对于消息发送频率较低,且对一致性要求较高,适合 LWT。
唯一性保证: 在确保用户 ID、群 ID 等唯一性时,LWT 可以用来进行原子性的“检查并设置”操作。
特点: 这些操作通常比普通写入慢,因为它们涉及多阶段的共识协议(如 Paxos)。因此,它们仅用于对一致性要求高、冲突不频繁的场景,不用于核心消息流。
b. 基于版本号/时间戳的冲突解决:
机制: 这是乐观并发控制的一种更广义的应用。每个数据项都带有一个版本号或精确的时间戳。当多个并发更新发生冲突时,系统会采用具有最新版本号或最大时间戳的写入(“最后写入胜出”)。
应用场景:
消息状态更新: 例如,当多个接收者同时发送“已送达”或“已读”回执时,系统会以最新的回执状态为准。
设备同步: 当用户在多个设备上使用 WhatsApp 时,消息的读写状态需要在设备间同步。版本号或时间戳有助于解决并发更改的冲突。
特点: 这种方式无需显式锁定,完全依赖于数据本身的元数据进行冲突解决,非常适合最终一致性系统。
4. 结论
WhatsApp 主要通过其分布式架构(数据分区、异步处理、消息队列、幂等性)来从根本上避免了对悲观锁的需求。当需要更强的一致性保证时,它会策略性地使用乐观锁(通过数据库的 LWT/CAS 功能或基于版本号/时间戳的冲突解决)。这种选择在高并发下提供了更好的可伸缩性和可用性,同时确保了关键数据的最终一致性。