Ozone原理|Ozone如何设计实现高可用SCM

在之前的分享中,我们提到了Ozone的High Availbility(HA),当前已经实现完成的是Ozone Manager的HA功能,Ozone Manager已经支持了一主多备的部署模式,可以支持快速故障倒换和快照机制,在主Ozone Manager发生故障时,备份OM能够根据Raft选举机制迅速选出新的OM并开始服务,从集群切主的动作到服务可用,可以达到秒级时延。按照当前Ozone的部署模式,除了Ozone Manager之外,HDDS(Hadoop Distributed Data Store)中的核心元数据管理组件SCM(Storage Container Manager)一般也是部署在集群的Master节点上,那么当发生节点级故障时,Ozone集群能否迅速完成故障倒换也取决于SCM的可用性。于是,SCM HA成为了当前Ozone项目优先级最高的功能之一,笔者作为SCM HA的主要设计人员,来分享一下SCM HA的设计思路和实现方案。

利用Ratis实现状态同步

Storage Container Manager(SCM),是HDDS中主要管理Container相关元数据的组件,同时相关的Pipeline和Block信息也在SCM中管理,SCM负责创建数据Pipeline,并且分配Container到不同的Pipeline中,而每个Container负责在不同的数据节点DataNode上分配Block用来存储数据。Pipeline,Container和Block信息会持久化到节点上的RocksDB,也会记入内存Map中。同时每一个DataNode也需要像SCM上报心跳和资源状态(Pipeline,Container等)来维持健康状态 SCM HA需要在一个SCM Group中随时保持状态同步,在SCM写入SCM DB和Map之前,数据需要在主备之间保持同步和一致,同时比如选择Pipeline和创建Container等流程都需要在Leader做出计算和决定,所以主备之间的元数据复制是保持SCM状态统一的关键。

SCM HA同样利用了Ratis实现的Raft机制进行State Replication,每一次Leader在更新状态DB和Map的时,同时通过RaftLog的Replication能力,将Leader更新的元数据同步到Follower的RaftLog中,同时增加Ratis的流程,在接收到RaftLog的更新后同步写入Follower的DB和Map中,保证Leader和Follower的状态保持接近,缩短故障发生时,Follower成为Leader并状态Ready的准备时间。Ratis作为一个插件化的Raft机制实现,给与了开发者很大的灵活性定义对于RaftLog和StateMachine的运用,同时,RaftLog的Purge也会配合StateMachine的Snapshot进行。

关于SCM复制状态的选择

SCM作为管理Control Plane资源分配的关键组件,状态复制一定要保证Strong Consistency,所以每一个Pipeline, Container和Block的状态变化都需要通过Ratis在Leader和Follower之间同步。同时,由于SCM需要负责管理HDDS整个集群(DataNode)的健康状态,SCM会接受到很多来自DataNode的心跳和报告,尤其是在集群比较大的时候,SCM收到的心跳和报告数量和内容都比较大。由于RaftLog的写入都是append-only,受到磁盘和介质的时延影响,对于SCM复制的状态需要作出一定选择。相比起Ozone Manager主要维护用户Request记录,SCM需要维护的元数据种类比较多,单次更新的数量量较大,但是更新的频率不如OM高。

当前的设计中,SCM会主动把Pipeline,Container,Block相关变化,和Node关于Container的状态变化,在Leader持久化到本地DB前,写入Ratis,同步给Follower。从DataNode发来的心跳, Pipeline Report和Container Report等都具备幂等性,即如果前面的Report丢失,下一次的Report收到后,可以保证状态一致,后面的Report不依赖前面Report的完整性,于是所有从DataNode发来的心跳和Report都不会持久化到RaftLog中。所有的DataNode都会把所有的Heartbeat和Report发到所有的SCM上,这样的优点在于可以减少SCM写RaftLog的次数和数据量,避免RaftLog成为SCM的性能瓶颈,但是另一方面这样的选择增加了DataNode的网络压力(从原来的将Report和心跳发给一个SCM到发给多个SCM),同时在SCM故障倒换的时候,不能保证Follower马上能和Leader保证对于DataNode集群一样的view,如果Follower和前任Leader收到的DataNode心跳不是完全一致,那么在切主的一段时间内,根据原来的DataNode view分配出的Pipeline和Container有重组的风险。关于这类问题,后续会通过延长Pipeline和Container生命周期来缓解。

如何序列化数据到SCM Ratis Server

由于利用了RaftLog在Leader和Follower之间同步状态,那么Leader SCM需要将各种不同数据结构的状态都统一用一个比较大的数据结构包含,然后序列化写入Ratis Server中,完成Replication的输入,利用protobuf序列化之后的数据结构由Leader Ratis Server发送RPC请求到Follower上,然后反序列化,写入Follower的DB和Map中。在Ozone Manager的HA实现中,Ozone Manager设计了OMRequest和OMResponse的protobuf数据结构,包含了所有的OM请求和相应,根据用户请求序列化到OM Ratis Server中。

然而对于SCM来说,SCMRequest和SCMResponse由于包含了Pipeline,Container和Block级别的不同信息,所有数据结构设计的时候会显得比较复杂而且难以识别,起初在设计的时候尝试使用过跟OM相似的思路,设计SCMRequest和SCMResponse的proto。

在意识到这样的设计和实现不便后,我们利用了Java reflection的序列化方法,实现了一个叫Replicate的java annotation,将整个Java reflection请求序列化到SCM的proto结构中,写入SCM Ratis Server。这样的设计使得每一个需要序列化的请求,只需要在interface上加上@Replicate,请求就会通过注册的Invocation Handler利用Java序列化成bytes,然后Leader上的Handler会将bytes发送到本地的SCM Ratis Server,远端Follower的Handler收到RPC请求后,会调用Handler反序列化RPC请求内容,然后调用Ratis API进行状态更新操作。

这样的设计和实现大大减小了SCM HA在序列化数据结构上的难度,针对SCM处理多种不同元数据(Pipeline,Container,Block和Node)带来的数据结构复杂性选取了和OM上序列化数据不同的方案,让代码重构更加顺利。

总结

高可用性作为分布式系统必备的能力,在存储领域里一直是极其重要的一环,Apache Ozone项目的HA工作从项目初期就被摆上很高的优先级,在OM HA的实现和逐步完善后,SCM HA是当前Ozone社区最关键的特性之一,是Ozone社区0.6.0版本发布的核心内容。当前SCM HA正在核心代码实现阶段,具体进度可以关注ozone社区关于SCM HA的JIRA:HDDS-2823,也欢迎感兴趣的小伙伴一起加入设计实现。

欢迎阅读其他Ozone系列文章

Hadoop原生对象存储Ozone

Ozone如何利用Multi-Raft优化写入吞吐量

浅谈Ozone的HA能力

Ozone Native ACL的特性及应用

聊一聊Ozone如何高效利用Raft机制