全局热备-Handoff
Handling Failures: Hinted Handoff
Dynamo实际上不需要系统保障所有的quorum节点可用。它使用一种“sloppy quorum”的策略。简单来说,一份数据储存在ABC三个节点中。当A节点宕机或不可达时,就临时储存在D节点中。D节点单独有一块区域存储这些本不属于自己的数据,并进行定时轮询。如果发现A节点可用,就将数据传输回去并删掉本地的副本。始终保持三个节点的数据副本。(注意此处选择D不是任意的,应该是在环中最近一个健康节点,这样才能保证故障时读写数据能找到该节点)。
与此同时,Amazon为了保障Dynamo的高可用性,还会将数据存储节点分布在多个数据中心中,数据中心间通过高速通道连接,避免自然灾害等导致一个数据中心同时宕机的风险。当然这样的部署对于小型机构来说是基本不可能的,仍需要依赖于Amazon平台的基础设施。
当遇到不健康的节点时,会沿着一致性哈希环的顺时针方向顺延,将数据保存到一个临时节点的本地的一个独立数据库中,并且不断扫描原节点是否可用,一旦可用了就将这个数据归还,并将其从本地数据库中删除,这个过程就是Dynamo的Hinted Handoff。
Hinted Handoff:暗示的转交,如果写操作过程中节点 A 暂时不可用,可以自动将
该节点上的副本转交到别的节点去,这是为了保证副本总数不减少。而这个转交的数据会设置一个暗示的标记,等到节点 A 恢复了,会被重新转交回 A。
Handling Permanet Failures: Replica Synchronization
Dynamo使用Merkle tree来进行节点同步,以尽量减少同步时需要传输的数据。Merkle tree是一棵hash树,简单来说,每个节点就是一个单独key的value的hash值,而父节点又是其子节点的hash值。那么在比较两个节点是否不一致时,只需要比较最顶层节点,然后根据比较结果依次向下比较,直到同步所有的不一致叶子节点。而不需要将所有数据拿出来进行比较。每个物理节点会对其上每一个虚拟节点维护一棵单独的Merkle tree。
这个方案的缺点是当有节点加入或离开时,需要重新计算整棵树的hash值。该问题后续有解决方案。
仍然要弄清楚,Hinted Handoff和Replica Synchronization两者的适用场景有什么不同及联系:相较而言,Hinted Handoff适用于节点临时不可用的场景,节点还是会回到集群中来,这一过程几乎是透明的。而Replica Synchronization则适用于节点永久不可用,需要进行数据同步。
Membership and Failure Detection
- Ring Membership:
管理员可以通过控制台或浏览器去连接一个Dynamo节点,并发出添加或删除一个节点的指令。处理该指令的节点会将指令写入持久存储的节点改动历史中,一个基于Gossip的协议将改动传播到各个节点,并最终维持各个节点上信息一致:每个节点每秒都会选择一个随机节点,这两个节点之间协商永久存储的节点改动历史。通过协商,每个节点都能更新自己需要负责存储的区域数据。 - External Discovery:
在之前介绍的动态添加删除的节点过程中,可能出现逻辑分区,如A,B同时添加进环,它们都认为自己是环中的一员,但还不能立即感知到对方。此时会有一个由外部机制(静态文件定义,或者外部的配置服务)决定的种子节点,所有其他节点会和种子节点交流自己的membership,使得逻辑分区基本不可能出现。 - Failure Detection:
当节点A发现节点B不响应其请求时,就会认为节点B失效了,从而将请求发送到可选的其他节点中去,实施Hinted Handoff。同时A节点会不断轮询B,来查看其是否已经恢复。而去中心化的节点失效探查协议使用了简单的类似gossip的协议。这适用于节点的临时失效。而对于明确的节点增加和删除,则不在此协商范围内。
////////////////////////////////////////////////////////////
Handling temporary failures
Recovering from permanent failures
使用 Merkle Tree 的反熵(anti-entropy)。Merkle 是这样一种数据结构,非叶子节点提供了多层 Hash 的功能:
反熵协议是用来帮助副本之间的同步的,使用 Merkle 的主要优点是每个分支可以独立地检查,而不需要下载整个树或整个数据集。
Membership and failure detection
基于 Gossip 的成员协议(membership protocol)和故障检测。Gossip 协议本身就是为了去中心化而设计的,虽然无法保证在某个时刻所有节点状态一致,但可以保证在某个最终的时刻一致。成员协议用于在 hash 环上增加或减去节点。
关于 Dynamo 的吐槽
对于 Dynamo 的去中心化,实在是功过兼备,毕竟引入了上面介绍的一堆复杂的机制,尤其对于数据的一致性问题,更是争议不小。使用一个 Master 节点,丢失了中心化,但是一致性的问题就容易解决得多,系统也会更简单;退一步说,如果要去中心化,但是使用 Paxos 这样的协议,来选举一个“Master” 出来,那也能比较简洁地保证一致性。但是 Dynamo 最后的实现,让用户来解决冲突的做法(有时候用户也没法确定该用哪个版本),确实有些别扭;而采用绝对时间来解决冲突的方法,则是在机制上有天生的缺陷(时间无法做到绝对同步)。
网上曾经有一篇很火的吐槽 《Dynamo: A flawed architecture – Part 1》,抱怨了一些 Dynamo 的问题,新浪的 Tim Yang 写了一篇文章简单翻译了一下,我就不再赘述,大致上抱怨的问题包括:
- 一致性方面,Dynamo 没有办法保证避免脏读;
- Quorum 机制中只是 R+W>N 在遇到节点不可用的时候,并不能保证强一致性;
- Hinted Handoff 机制在跨 IDC 的情况下,会因为异地传输开销而性能低下;
- 灾难恢复方面,某一个 IDC 挂掉的时候,没人可以计算到底丢了多少数据;
- 论文里面一些自相矛盾的地方,一个是对节点对等的描述,一个是对最终一致的描述;
- Dynamo 给用户造成了误导,以为一直是在 CAP 的 C 和 A 中必须做一个取舍,其实单节点中心就可以同时做到 CA;
- Dynamo 宣称去中心化,但是并没有完全做到,比如交换机故障造成网络分片的时候,服务就不可用了。
这篇文章的标题写着 part 1,只可惜 part 2 没有出现。这篇文章引起了不少争议,作者后来自己写了一篇 《Dynamo – Part I: a followup and re-rebuttals》来回应,文章结尾总结了一下他对 Dynamo 的观点:
- 尽量去避免脏读;
- 不受控的脏读任何时候都不可接受,即便在灾难发生的时候—— 就算数据丢失也比它要好得多,大多数情况下,管理员会关闭部分或者全部的服务,而不是去用丢失或者损坏的数据来响应用户
- 一个数据中心内的网络分片要避免,在一个数据中心内考虑 P(partition tolerance)是不合理的;
- 中心化并不意味着低 Availability,高可用的服务是可能的,虽然说 scalability 可能会成为问题;
- 开发设计的对称性并不能很好适应硬件和网络的非对称性;
- 数据中心一致性、高可用性和扩展性是可以同时达到的,只要在一个数据中心里面(也就是说 P 被放弃的时候),BigTable+GFS,HBase+HDFS,甚至 Oracle RAC 都是很好的例子;
- Dynamo 的读写即便在一个数据中心内也会引起脏读;
- 谁也不知道脏读避免的时间边界在哪里;
- 跨数据中心的情况下,没法跟踪有多少数据待更新,而灾难恢复的时候,也没法知道有多少数据丢失。
///////////////////////////////////////////////////
另外关于Cassandra的高可用机制是其Hinted Handoff机制的实现。当写入时发现有节点因为网络分区、硬件故障或者其他原因而断掉,协调器节点就会把包含写入信息的hint贴出来"现在b炸了,我要要写入它的信息,等B恢复后,我会写入它"这个特性保证Cassandra永远都是可以写入。但是注意的是hint没有真正写入到指定节点上时,是不会读取到的。在一致性级别是ANY的情况下,Hint被认为写入成功。不过也存在问题就是下线一段时间后,可能累积了大量的hint,当节点重新上线后,可能触发洪水攻击,为解决此问题,cassandra可以配置时间窗口大小或者完全禁用hint handoff.
当然了hint并不能真正完全解决可用性问题,一致性的保证还需要修复repair机制。
repair机制的实现依赖anti-entropy一种特殊类型的gossip协议,它会通过比较所有的副本数据然后发现其中的冲突差异的地方。这个机制并不是只是简单用于修复数据,它本质是还是一个数据同步协议。Cassandra的副本同步会有两个时机来做,一种是读数据时的read-repair,另一种就是anti-entropy repair。前者很好理解,在读过不同副本有不满足一致性级别的数据时,如果有存在副本有过期值,那么就会立即执行read-repair。而anti-entropy修复则需要手动在节点上执行nodetool操作。执行的节点会跟自己的邻居节点进行MerkleTree的比较,如果发现差异的地方则认为存在冲突,就会进行anti-entropy修复。整体的修复过程称之为major compaction(也是通过compaction来实现的)。MerkleTree是在major compaction过程中生成的,并且只在需要在比较树的时间内保留。
////////////////
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类