阅读 51

Talos读写一致性


前文《万亿级消息背后:小米消息队列的实践》整体介绍了 Talos。
本文将深入介绍该系统设计中的关键问题—读写一致性。
Talos 消息队列做为一种特殊的存储系统,其一致性包含两方面:
  • 存储层多副本数据一致性

  • 调度层处理逻辑的读写一致性

前者由 Talos 基于 HDFS 的存储层来保障,本文将详细展开调度层一致性逻辑。

Talos中的读写一致性问题

做为一个消息队列系统,需要保证消息的读写一致性。这需要满足两个方面:

  • 单条消息是连续的字节存放,不同消息的字节数据不能交错存放,即消息内容一致性

  • 单 Partition 消息之间应当有序,写入顺序即是读取顺序,即顺序一致性

Talos 在 HDFS 的数据存储模型为:每个 Partition 在 HDFS 中对应一个目录,通过 append 方式追加消息到文件。Offset 代表此条消息在 Partition 中的位置,唯一且递增。文件名是上一个文件最后一条消息的 Offset + 1,即上一个文件的 EndOffset + 1.

上述读写一致性需要满足的两个方面,由此可进一步具体化:

  • 同一时刻同一 Partition 的数据文件,只能有一个 Talos Server 写入(否则文件中单条消息将不能完整连续的存储)

  • 同一时刻同一 Partition 的数据目录,只能有一个 Talos Server 写入(否则同时写多个文件,消息之间将丧失顺序性)

综上 Talos 的读写一致性需要满足如下条件: 同一时刻同一 Partition 目录,只允许一个 Talos Server 节点进行写入。


Talos是如何解决的读写一致性问题

01、一致性哈希?

前文《Talos网卡负载优化:基于个性化一致性哈希的负载均衡》提到,Talos 使用一致性哈希来指导 Partition 调度,基于此所有节点在大部分时刻对于调度信息都具有相同视角。

但是由于收到 ZK 通知的时间不能完全一致,少数时刻的视角并不一致,所以依据一致性哈希并不能保证同一时刻同一 Partition 目录只有一个 Talos Server 节点进行写入请求。

一致性哈希的作用是在大部分时间统一了各节点调度视角,各节点仅在哈希结果为应该接管某 Partition 时才参与其权限竞争,避免了无谓竞争造成的开销浪费。

02、分布式锁?

为了加强 Talos Server 节点与 Partition 的绑定关系,使用 TTL 的分布式锁机制给调度信息加锁,Server 节点会在超时时间之内续约。获得锁之后 Server 节点对此 Partition 标记为 Active 状态,Active 状态可以接受请求,反之则不能。

可见在分布式锁信息中,同一时刻 Partition 和 Server 节点唯一对应。那么这种方式是否能满足同一时刻同一 Partition 目录只有一个 Talos Server 节点进行写请求呢?否。
原因是:
  • 不能保证 Partition 的 Active 状态与分布式锁信息的一致性:如果发生长时间 GC,续约间隔时间可能超过 TTL 时间,在 GC 完成和下次续约之间,可能出现实际已掉锁但状态为 Active 的窗口,如下图

  • 更不能保证写请求和分布式锁信息的一致性:Server 在 Active 时接受写请求,但写请求进行时 Server 有可能会掉锁

综上,不能保证同一时刻同一 Partition 目录只有一个 Talos Server 节点进行写入。

分布式锁的作用是在一定时间粒度锁定绑定关系,避免了时间粒度内的细微抖动触发的迁移,使得服务进一步稳定,但是它无法解决节点假死或写数据时掉锁引发的脑裂问题。

03、Talos Fencing Process!

传统解决脑裂问题的方法是 Fencing 机制,例如 HDFS 中 NameNode HA 的脑裂问题方案:NameNode 每次写 Editlog 都需要传递一个 编号Epoch 给 JN,JN 会对比 Epoch,如果比自己保存的 Epoch 大或相同,则可以写,JN 更新自己的 Epoch 到最新,否则拒绝操作。在切换时,Standby 转换为 Active 时,会把 Epoch + 1,这样就能阻止之前的 NameNode 向 JN 写入日志;
Talos 按照以下流程进行 Partition 的初始化恢复,以实现类似原理的 Fencing :
  • Load file list:加载该 Partition 目录下的文件列表

  • Recover lease && Close:对加载到的最后一个文件,进行 recover lease && close 操作

  • Rotate new File:以最后一个文件的 EndOffet + 1 命名创建一个新的文件做为接下来要进行写入的文件

过程中任意一步失败则恢复过程失败,如需则从头重新进行。成功之后才能进行写操作。

如上过程可以满足以下效果:

(1) Recover 过程之前的文件不会被再打开或者 append 写

(2) 由于每创建一个新文件都会关闭相邻的最后一个文件,递推可知第三步时load到的文件都已经被 Recover lease && Close

(3) 新建的文件名是 load 到的文件的 EndOffet+1

这个过程能够保证,同一时刻同一 Partition 目录只有一个 Talos Server 节点进行写入。以下将对其证明:
假设 Talos 集群中,Server1 和 Server2 都要调度某 Partition

  • 由于(1),每个 Server 只写 Recover 过程中或之后,由本节点新创建的文件,所以不存在两个 Server 写同一文件的情况
  • 是否会出现创建两个不同文件从而同时写该Partition目录的情况呢?
    假设第m个文件的 Endoffset 是 endM,第n个文件的 Endoffset 是 endN,m<n。S1 加载到m个文件并创建了 endM+1,S2加载到n个文件并创建了 endN+1

    => 根据(2),m个文件都是 S2 已经加载到的且已经关闭的文件,则 endN >= endM+1

    => 则 endM+1 是在 S2 的 load 文件列表里已经关闭了的文件,不能再次创建或写入

    即不会出现创建两个不同的文件从而同时写该 Partition 目录的情况

得证。

上述逻辑可以达到图示效果,即不会出现两个 Server节点 同时写同一 Partition:

对比 HDFS 中 NameNode HA 的脑裂方案,两者都在存储层具有能判断新的 Master 的信息,用以判断在新的 Master 来临时,剥夺掉旧 Master 的写权利。

HDFS 在解决 Talos 读写一致性问题时的角色,相当于 JN 做 NameNode 与 standby NameNode 共享存储时的角色。Talos Fencing 以最后一个文件的文件名做为当前有效的 Epoch,创建 EndOffset+1 文件成功相当于获取了新的 Epoch,创建新文件之前进行 Recover lease 相当于剥夺了旧的 Epoch 的权利。

这种机制防止了 Talos 分布式节点写 Partition 时的脑裂。

往期文章回顾
小米Talos GC性能调优实践
小米流式平台架构演进与实践
Flink流式计算在节省资源方面的简单分析


本文首发于公众号“小米云技术”(ID:mi-cloud-tech),转载请标明出处。


关注下面的标签,发现更多相似文章
评论