1.概述
前段时间接触了raft协议,唯一的感受就是易于理解。对于raft,在分布式领域还是有一片天地的。当然,光看算法不去工程化就是耍流氓,所以我专门拉了一下sofa团队的jraft代码。然后对其实现进行分析,一方面是为了提高自己的编码功底,一方面也是更加深入的理解。今天我们就先看看其架构实现。然后会对其进行逐一拆分,深入底层代码。
sofastack是一个不错平台,大家也可以持续关注。
2.架构设计
纵观设计架构图,还是非常容易理解的。
整个系统围绕着Node进行。涵盖了日志管理、元数据存储、快照、状态机、日志复制等模块。Node和Node之间通过RPC进行通信。
系统日志模块和状态机的任务都是通过Disruptor异步去执行。
这里主要介绍一下几个类的功能。
-
FSMCaller主要就是将日志同步到状态机。
-
LogManager,顾名思义,就是管理日志。
-
MetaStorage用来存储节点的元数据信息。
-
SnapshotExecutor就是快照方面的实现。
具体实现细节还是要深入到源代码。
为了我们更方便的去分析代码。我们从对应简单的例子counter出发。
3.源码分析
CounterServer服务端启动流程
// 这里让 raft RPC 和业务 RPC 使用同一个 RPC server, 通常也可以分开
final RpcServer rpcServer = RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint());
// 注册业务处理器
CounterService counterService = new CounterServiceImpl(this);
rpcServer.registerProcessor(new GetValueRequestProcessor(counterService));
rpcServer.registerProcessor(new IncrementAndGetRequestProcessor(counterService));
// 初始化状态机
this.fsm = new CounterStateMachine();
// 设置状态机到启动参数
nodeOptions.setFsm(this.fsm);
// 设置存储路径
// 日志, 必须
nodeOptions.setLogUri(dataPath + File.separator + "log");
// 元信息, 必须
nodeOptions.setRaftMetaUri(dataPath + File.separator + "raft\_meta");
// snapshot, 可选, 一般都推荐
nodeOptions.setSnapshotUri(dataPath + File.separator + "snapshot");
// 初始化 raft group 服务框架
this.raftGroupService = new RaftGroupService(groupId, serverId, nodeOptions, rpcServer);
// 启动
this.node = this.raftGroupService.start();
1.首先通过工厂模式创建一个RpcServer。RaftRpcServerFactory#addRaftRequestProcessors 主要就是注册一些时间处理器。根据不同的协议处理。这个序列化协议使用的google的protobuf。每当有不同的请求过来都会调用不同的处理器方法。
2.然后创建一个一个业务类。因为这个例子比较简单,其实就是维护一个分布式自增键。所以只有两个操作,一个就是增加value大小,一个是读取该值。对应的Processor也就是调用了CounterService的这两个操作。当然这不是我们关注的重点。
3.创建业务状态机。raft提供了一个StateMachine接口,奈何他的方法太多,业务方有时候没有必要去实现。所以他提供了一个适配器。这个也就是适配器模式。是可以去学习的。
StateMachine有很多方法。比如节点状态变化的回调。其中核心的还是onApply方法。参数是一个Iterator,可以看出是可以批量apply的。这个方法,业务方一般要去主动实现。
4.设置NodeOptions并初始化RaftGroupService ,最后调用strat启动服务。
RaftGroupService#start方法
1.该节点的信息校验
2.将该几点添加到NodeManager 中。这是一个单例的实现。用于存储该进程RPC地址信息。和raft的group 信息。
3.通过工厂创建raft node并init。init rpc server。
NodeImpl#init方法
这个方法比较长,我们慢慢分析
1.获取JRaftServiceFactory,这个工厂主要用于创建各种存储实现类。这里通过SPI机制暴露给也业务方实现。当然raft有个默认的实现。DefaultJRaftServiceFactory
具体SPI实现后面会介绍一下。其实就是JRaftServiceLoader 实现。
2.初始化配置信息。校验,ip准确,并且一个ip:port只能创建初始化一次。初始化定时线程池。初始化各种计时器。
3.初始化配置管理器,主要就是集群配置
4.初始化请求的disruptor队列
this.applyDisruptor.<