vlambda博客
学习文章列表

Ratis代码解析之一-gRPC


Ozone采用Ratis保证数据一致性,Ratis采用Raft协议实现,代码可见Github。本文首先介绍Ratis的gRPC模块代码,后续会陆续介绍其他模块。

1. Ratis简介

目前主要有两种分布式协议Paxos、Raft。Raft在保证一致性效果上等价于Paxos,和Paxos同等高效。而 Raft的优势是易理解,易实现。

Ratis采用Raft实现,为了保证Ozone数据一致性,Ozone的每个读写请求,都采用三备份,需要写入一个Leader和两个Follower。一个Leader和两个Follower为一个group,各为一个Ratis Server,只是角色不同,一组Ratis Server分布在三个Datanode里,作为Datanode的子线程。一个leader和两个follower各将一份请求写到log里,请求被一个group的大多数Ratis Server写入log后,即回复给client。

以写数据请求为例,该请求发到Leader后,Leader不仅需要将写数据请求写入log,而且需将被写的数据写入Leader的本地存储。Ratis负责请求写入log,然后Ratis回调Ozone的writeStateMachineData将数据写入Leader本地存储。请求写入Leader的log后,Leader将写请求以及数据发给Follower,数据需要从Leader本地存储读,数据不从请求里获取的原因是需要保证Leader写入了数据。然后Follower开始写log以及数据。可以看出,Leader将数据写入本地存储后,才将请求发给Follower,因为本地存储是磁盘,会造成Leader和Follower串行将数据写入磁盘,速度较慢。优化的方法是,Leader先将数据写入内存Cache,再将数据写入本地存储磁盘。而Leader在将数据写入内存Cache后,就可以将数据读出,并发给Follower,这样Leader和Follower可以并行将数据写入磁盘。

2. Ratis模块简介

Ratis主要模块分为三部分:ratis-client、ratis-grpc、ratis-server。

ratis-client封装给上层使用,提供操作group的接口:groupAdd、groupRemove、getGroupList等,以及发送请求的接口sendRequest等。Ozone使用ratis时,需要首先用groupAdd创建一个Leader和两个Follower的group,然后将请求包装成Ratis的请求RaftClientRequest,通过sendRequest发给Leader。

ratis-server负责数据同步主要逻辑:选举、快照、日志同步等功能,每个leader和follower都是一个RaftServerImpl实例。

ratis-grpc负责ratis-client和ratis-server,以及ratis-server和ratis-server之间的通信。

3. Ratis gRPC简介

Ratis采用gRPC作为网络框架,gRPC相关封装代码在ratis-grpc包下。一个完整的请求分为client和leader、leader和两个follower之间通信。例如Ozone需要ratis将请求写三份时,会用ratis client向ratis leader发送该请求,而ratis leader又会向两个ratis follower发送该请求,当ratis leader和两个ratis follower的大多数写完该请求时,ratis leader会向ratis client回复写成功。client、leader、follower通信时序图如下,后文会做详细介绍。

4. Client和Leader通信

该部分相关代码在ratis-grpc/src/main/java/org/apache/ratis/grpc/client,该目录下有三个文件负责通信:GrpcClientProtocolClient、GrpcClientProtocolService、GrpcClientRpc。

ratis client相关代码在ratis-client目录下,本文不涉及该部分。client调用GrpcClientRpc::sendRequestAsync向leader发送请求。leader会在GrpcClientProtocolService的RequestStreamObserver::onNext里处理该请求。leader处理请求时,不仅会自己将请求写入log,还会通知两个follower写入log,可见下文。leader处理完后会向client回复,client在GrpcClientProtocolClient的AsyncStreamObservers::onNext里判断写请求三备份是否成功。

5. Leader和Follower通信

该部分相关代码在ratis-grpc/src/main/java/org/apache/ratis/grpc/server,该目录下有三个文件负责通信:GrpcClientProtocolService、GrpcLogAppender、GrpcServerProtocolClient。

leader在GrpcClientProtocolService的RequestStreamObserver::onNext里收到请求后,会交给RaftServerImpl::submitClientRequestAsync处理,该函数首先校验当前server是否是leader等,然后leader将请求放入待写队列,并通知leader向follower发请求的线程GrpcLogAppender。GrpcLogAppender负责将请求从leader发送到follower,leader为每个follower维护一个GrpcLogAppender线程。

GrpcLogAppender调用GrpcServerProtocolClient:: appendEntries将Leader已经写成功的请求发给follower,follower在GrpcServerProtocolService的ServerRequestStreamObserver::onNext里接收请求,并交给RaftServerImpl:: appendEntriesAsync处理,appendEntriesAsync需要首先校验Leader的请求,包括该请求的源Server是否是Leader,以及该请求是否已经在Follower写入等,然后将请求写入日志,处理完后回复给leader,leader在GrpcLogAppender的AppendLogResponseHandler::onNext检查follower是否写成功。如果leader和两个follower大多数写成功,leader向client回复成功,client在上文提到的GrpcClientProtocolClient的AsyncStreamObservers::onNext处理回复。

欢迎阅读其他Ozone系列文章









jerryshao@tencent.com