知识卡片

防护令牌用递增编号让资源方拒绝过期天选者的写入

普通读书笔记卡

内容

[[进程可能任意暂停多久导致基于时钟的租约机制潜藏危险]]暴露出一个更普遍的问题: 一个节点自认为是”天选者”(分区领导者、锁的持有者),并不代表法定人数的其他节点 也这么认为——它可能已经因为网络中断或GC暂停被判定死亡、被别人取而代之,只是自己 还没意识到。如果这个过期的天选者继续以为自己拥有权力、继续向其他组件发消息,而 这些组件真的采信了它,系统就可能做出错误的事(比如两个自认为持有同一把锁的客户端 同时写同一份文件,导致文件损坏——HBase确实出过这个问题)。防护令牌是应对这类问题 的一个简洁机制:每次锁服务授予锁或租约,都附带返回一个单调递增的数字(防护令牌), 要求客户端每次向被保护的资源发写请求时都必须带上当前令牌;如果客户端1拿到令牌33后 陷入长时间停顿、租约过期,客户端2随后拿到更大的令牌34并开始正常写入,等客户端1恢复 后带着令牌33发起写请求,资源服务端因为已经记得自己处理过更高编号(34)的请求, 就会直接拒绝这个带旧令牌的请求——即使客户端1本身毫不知情自己已经”过期”。关键点是 这个检查必须由资源服务端主动执行,光靠客户端自己检查自己的锁状态是不够的(客户端1 根本不知道自己已经掉队);如果用ZooKeeper做锁服务,天然单调递增的事务ID(zxid)或 节点版本号(cversion)就能直接充当防护令牌用。这也说明一个更普遍的道理:一个服务 不应该假设它的客户端总是守规矩,主动在服务端做校验、拒绝滥用,本身就是良好的 设计习惯。

参考来源

- 位置:《数据密集型应用系统设计》第八章《分布式系统的麻烦》"领导者和锁""防护令牌" (源文件:_epub-src/ch8_split_004.html) - 结论依据:原文举HBase因锁实现不正确导致文件损坏的真实案例,给出防护令牌机制: 客户端写请求必须携带单调递增的令牌,资源服务端拒绝携带更旧令牌的请求,并说明 ZooKeeper的zxid/cversion可直接作为防护令牌,检查必须由资源端而非客户端执行, 直接支撑本卡片结论。 - 原始内容:我们假设每次锁定服务器授予锁或租约时,它还会返回一个防护令牌……客户端 1以33的令牌获得租约……客户端2以34的令牌获取租约……存储服务器会记住它已经处理了 一个具有更高令牌编号(34)的写入,因此它会拒绝带有令牌33的请求。