一、概述

  在这篇博文中将整理一下到 Lab 3B 为止的系统结构,并在后续的博文中开启对于整个系统关键技术点实现思路的记述。


二、系统介绍

2.1 概述

  该服务为通过 Get/Put/Append 方法调用该服务的客户端提供 强一致性保障 ,这里所说的强一致性是指如果客户端一次调用一个方法,那这个方法的调用应该跟调用仅有一个副本的系统一样,并且每个调用都应该能观察到前面的调用序列对状态的修改结果。而对于并发调用,返回值和最终状态必须与前面所说的客户端一次调用一个方法即按顺序操作的结果相同。


2.2 支持操作

  该服务支持三种操作:Put(Key、Value)、Append(Key、Value) 和 Get(Key),可以认为它维护了一个简单的键/值对数据库。

  • Put() 替换数据库中特定键的值;
  • Append(key, arg) 将arg附加到键的值,附加到不存在的键的行为类似 Put() ;
  • Get() 获取键的当前值;

三、系统整体结构

3.1 系统整体结构示意图

6.824 Fault-tolerant key/value storage system v1.0(四)( Brief description of code architecture)


3.2 系统结构划分

  当前系统的整体结构基本如上图所示,从最底层看起目前的系统可以大致分为以下这五个部分:

  1. Client :外部调用服务的客户端,对于服务的调用方式目前仅基于 Method Call;
  2. Clerk :可以简单理解为接待员,它处于 Client 和 Server 的中间层,主要职能是接收来自 Client 的请求后,通过管理与 KV Server 之间的 RPC 交互完成对 KV Server 中数据的修改或查询操作,且每个 Clerk 仅对应一个 Client;
  3. KV Server :Key/Value 数据库服务器,且每个 Server 对应一个底层的 Raft Peer,但需要注意的是 Clerk 与 KV Server 非一一对应的关系,Clerk 仅会将对数据的操作发送给 Leader Raft Peer;
  4. Raft Protocol :提供基于 Raft 算法的数据一致性保障;
  5. LabRPC :因为目前暂时使用的还是 Lab 提供的 PRC 框架,因此所有节点之间的 RPC 通讯目前均基于 LabRPC;

3.3 系统各部分之间的关联

   首先因为 LabRPC 部分算是系统中的一个通信模块,主要用于通信且不包含业务和算法逻辑,所以在分析各部分关联时我们先将其剔除掉,直接看剩下的四个部分。

  • 首先, Client 和 Clerk 是一一对应的关系,对于 ClientClerk 之间的调用目前主要是通过 方法的调用 来完成的,是 Lab 中为了便于测试而提供的默认调用方式,而这种实现方式的限制很多,不仅不能够通过网络来进行调用服务,更别说支持其它语言对该服务的调用了。因此在后期可以将其扩展为基于 gRPC gateway 的方式来支持外部通过 HTTP JSON 对服务进行调用,以此来增强服务的灵活性和跨语言调用性。
  • 其次, Clerk 和 KV Server 之间为调用时一一对应的关系,即每个 Clerk 保存了当前集群中所有 KV Server 的信息,但 Clerk 在调用时仅会调用一个 KV Server 。对于 ClerkKV Server 之间的调用主要是通过 RPC 来完成的,Clerk 在发送完 RPC 后一般会阻塞等待 KV Server 的回应后再进行其它的操作。目前项目中使用的是 Lab 提供的 LabRPC,后期优化时可将其替换为 gRPC + Protobuf 来进一步提升系统的性能和服务的规范性。
  • 再其次,KV Server 和 Raft Peer 之间又是一一对应的关系,每个 KV Server 唯一对应底层的一个 Raft Peer ,系统通过底层的 Raft Protocol 来保障集群服务器中数据的一致性。而对于 KV ServerRaft Peer 之间的通信是通过 方法调用通道 两种方式进行的。方法调用主要用于 KV Server 创建并初始化 Raft Peer 。Raft Peer 会通过通道将可应用的数据和 快照 发送给 KV Server,而 KV Server 会通过通道通知 Raft Peer 进行数据日志的压缩;
  • 最后,Raft Peer 之间是完全通过 PRC 来进行通讯的,这里需要注意 KV Server 互相之间是完全隔离的,两个 KV Server 之间只能通过底层的 Raft Protocol 实现数据的通信,而针对任意一个 KV Server 中数据的读取或修改操作都会被实时的同步到所有的 KV Server 中(这里只是便于理解,系统中仅 Leader Raft Peer 才有权限进行写数据的操作,所以数据同步的方向永远是从 Leader 到 Follower),以此来保障整个集群中数据的一致性;

3.4 系统各部分之间调用的细节

6.824 Fault-tolerant key/value storage system v1.0(四)( Brief description of code architecture)
   上面我们已经概述了系统各部分之间的调用方式,但是比较不好理解的就是 Clerk 对 KV Server 的调用,在上面我们说过每个 Clerk 都会保存集群中所有 KV Server 的信息,但是在真正进行调用的时候是只会调用唯一的一个 KV Server,那系统是怎么来判断到底应该调用哪个 KV Server 的呢?

   首先 Client 会将对数据操作的请求发送给 Clerk ,Clerk 接收到请求后会将其封装为一个 RPC,然后从自己所保存的所有 KV Server 中随机挑选一个进行发送(优化后可首先尝试上一次成功执行操作的 KV Server),当这个随机挑选的 KV Server 接收到该 RPC 后会通过方法调用来获取它底层所对应的 Raft Peer 的当前状态(是否为 Leader),如果底层 Raft Peer 当前的状态是 Leader 则 KV Server 会一直等待底层的 Raft Protocol 完成对本次操作的集群同步,并在接收到 ApplyMsg 后实际执行该数据操作,最后返回结果给 Clerk (这期间 Clerk 是一直处于阻塞等待的)。而如果底层 Raft Peer 当前的状态非 Leader ,那 KV Server 会直接返回消息表示其不是 Leader ,而 Clerk 在接收到这条回复后会再选取其它的 KV Server 进行尝试直至操作被成功执行。

  下图是上述的整个流程的流程图,这里为了降低理解的难度已经删去了很多关键的技术点和需要考虑的复杂因素,在后面的博文里会逐步将整个请求处理的流程补充起来。


6.824 Fault-tolerant key/value storage system v1.0(四)( Brief description of code architecture)

四、系统源码

https://github.com/TIYangFan/DistributedSystemBaseGolang ( 如果可以帮到你,Please Star ^ _ ^ ~ )


五、内容总结

  在这篇博文中对该系统的整体结构进行了分析,并分析了几个重点模块之间的调用方式。

  最后谈下为什么突然又开始写 Raft 相关的博文,这主要是因为当时在做 Lab 3B 时遇到了瓶颈,总是存在少数的用例无法通过的情况,然后为了换一下心情,所以后面的一周里我基本都在学习 Zab 协议的一些内容,并进行 Zookeeper 的一些源码分析,前两天再回去重新整理项目代码的时候在自己的不懈努力下终于通过了 Lab 3B 的全部测试用例,所以趁着思路还比较清晰会在最近的一段时间将到 Lab 3 为止的系统实现整理一下,也便于以后的重构和优化扩展。


相关文章: