文章目录
前言
在分布式系统中,为了避免中心服务节点存在单点问题,我们往往会有HA(High Availability)的防御措施。比如一个简单的HA部署方案,额外在另一个机器上部署一个完全一样的服务。当当前的这个服务出现问题时,能够快速切换到这个用来做HA的服务。HA服务切换本身并不是重点,这里的重点是如何保持HA服务间的状态同步。这点在分布式系统的HA设计实现中尤其之关键。本文笔者来聊聊Ozone对象存储系统中核心服务OzoneManager的HA的设计实现原理。
OzoneManager HA的目标
在之前Ozone的alpha版本中,OM服务都是以单点服务部署的方式来处理外部请求的。显然这种缺乏HA的运行模式在运用到实际生产环境中将会存在风险。于是Ozone社区在JIRA[HDDS-505: OzoneManager HA]下设计实现了OM HA的特性。同样是作为分布式存储系统的HA实现,OzoneManager的HA实现和HDFS NameNode HA实现有许多异同之处。
在官方社区的OM HA的设计文档中,列出了以下几点目标实现要点:
- 底层使用Raft协议来实现OM服务的HA特性。换句话说,这里将不是只有1个Active,另1个Standby服务这样的模式。而是2N+1的模式,1个OM Leader服务,2N个OM Follower服务。OM Follwer间通过投票选举出Leader服务。中间投票选举的过程遵从的就是Raft协议。这点即OM HA服务实现基于的一大核心要点。
- 有了OM Leader,OM Follower服务之后,另外一个要点是Leader/Follower间的状态同步控制,以此保证服务在角色切换后,还能保证完全一致的服务状态,就是我们所说的Strong Consistency。
- OM HA服务对于用户的透明性。用户理应对于OM HA完全透明,它在配置了给定的多OM服务地址后,就可以进行服务的请求了了。不管背后的服务处于什么角色状态,客户端用户都无须做任何调整。换言之,客户端始终能找到正确的OM服务,进行请求的发送,而不是单一定向的定点服务发送。
OM HA的Raft方式实现
OM HA中使用了Raft做HA的底层实现,那么它是如何工作的呢?以及它是如何做服务状态的同步的呢?
OM HA在这里使用了Raft的Java实现库Apache Ratis,里面实现了基于Raft协议的Leader选举以及Leader/Follower的状态一致性的控制。不过今天笔者并不准备展开篇幅阐述Raft中一致性同步控制的具体机理,我们还是着重谈谈OM HA本身。
因为有了Raft库Ratis的底层一致性的实现保证后,在上层OM中,它需要做的事情就变得清晰,明了了。在Apache Ratis中,是通过定义StateMachine状态机的方式来表示一个服务的当前状态。外界的每次请求就是此StateMachine即将要apply的一个新的State,每次apply完这个State后,整个状态机就会变为下一个State。因此,多个服务初始拥有相同初始State的StateMachine,在经历了若干次顺序一致的状态改变后,StateMachine的最终状态必然是一致的。这就是基于StateMachine的保证状态同步的核心原理。
这里我们将StateMachine的抽象概念定义转化为更为具体化的概念定义:
- 每次外界而来的待apply的State即为外界的request请求。
- 服务的StateMachine为OM服务的metadata信息,包括volume, bucket,key信息等各种表信息。
在Leader/Follower的HA模式下,请求状态的处理流程如下:
1)客户端发送请求给Leader处理
2)Leader接收到客户端请求,然后将请求复制分发到其它Follower上
3)Leader服务获取到超过半数以上Follower收到请求信息的ack回复之后,apply用户请求到内部状态机。
4)Leader会通知其它Follower去apply它们接收到的请求。
上述请求我们可以理解为是Transaction,从StateMachine的角度来看,概念叫做log entry。Leader复制请求到其它Follower的过程为log的write过程, 如果log被其它Follower成功接收到了,则意为此log为committed的log。
因此在这里StateMachine的实现异常关键,Ozone在这里实现了其内部使用的状态机类OzoneManagerStateMachine,对应的2个核心方法,log entry的写入,和log entry的状态apply方法:
/**
* Validate/pre-process the incoming update request in the state machine.
* @return the content to be written to the log entry. Null means the request
* should be rejected.
* @throws IOException thrown by the state machine while validating
*/
@Override
public TransactionContext startTransaction(
RaftClientRequest raftClientRequest) throws IOException {
ByteString messageContent = raftClientRequest.getMessage().getContent();
OMRequest omRequest = OMRatisHelper.convertByteStringToOMRequest(
messageContent);
Preconditions.