RPC 和 序列化

RPC

1 RPC调用流程

1.1 clerk客户端调用远程服务

Clerk::PutAppend()
raftServerRpcUtil::PutAppend()

raftServerRpcUtil是client与kvserver通信的入口, 包含kvserver功能的一对一映射:Get/PutAppend,通过stub对象------raftKVRpcProctoc::kvServerRpc_Stub *stu进行对应的rpc远程调用。

针对raft节点之间的通信,设计了RaftRpcUtil类型,包含raft节点功能:AppendEntries/ InstallSnapshot/ RequestVote

kvServerRpc_Stub::PutAppend()

kvServerRpc_Stub 是 kvServerRPC.proto文件编译后生成的存根,用于实现rpc远程调用。

stub使用MprpcChannel作为参数来初始化,因此接下来会调用MprpcChannel::CallMethod():

stub = new raftKVRpcProctoc::kvServerRpc_Stub(new MprpcChannel(ip, port, false));

MprpcChannel::CallMethod()

MprpcChannel:实现rpc通信的类 ,建立和服务端的连接,发送rpc请求,和接收rpc回复

所有通过stub调用的rpc方法,都会走到MprpcChannel::CallMethod() : 生成rpc请求数据并序列化,使用socket发送请求并接收回复

1.2 kvserver 提供rpc服务

KvServer::KvServer()

构造函数中发布rpc服务类型 : 服务的类型有两种,分别是Raft和KvServer

  • class Raft : public raftRpcProctoc::raftRpc
  • class KvServer : raftKVRpcProctoc::kvServerRpc

Raft类型的服务用于raft节点之间相互调用,KvServer类型的服务被client调用

RpcProvider::Run() 启动rpc服务 ,等待调用。项目中是阻塞等待,同步阻塞模型

RpcProvider::OnMessage()

已建立连接用户的读写事件回调:调用Callmethod来调用本地的业务

RpcProvider:网络对象类,发布节点能提供的rpc服务,并启动rpc服务(接收连接,接收请求并执行)

service->CallMethod()

有两种服务类型(Raft和KvServer),根据请求的service的类型,动态调用对应的CallMethod();

client调用 KvServerRpc::CallMethod(),raft节点调用raftRpc::CallMethod()。

KvServerRpc::CallMethod()

在函数内部,最终调用KvServer本地的方法。

2 rpc基础

2.1 概念

  1. RPC(Remote Procedure Call Protocol) 远程过程调用协议。
  2. RPC是一种通过网络从远程计算机上请求服务,不需要了解底层网络技术的协议。
  3. 远程调用就像调用本地方法一样便捷。

2.2 常用RPC技术或框架

  1. 应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
  2. 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
  3. 通信框架:MINA 和 Netty

2.3 rpc作用

  • 微服务化:跨平台的服务之间远程调用;
  • 分布式系统架构:分布式服务跨机器进行远程调用;
  • 支持跨语言调用。

2.4 架构

服务注册 ,就是将提供某个服务的模块信息(通常是这个服务的ip和端口 )注册到1个公共的组件上去(注册中心,比如: zookeeper\consul)。

服务发现,就是新注册的这个服务模块能够及时的被其他调用者发现。不管是服务新增和服务删减都能实现自动发现。

一个完整的服务发现中心还需要支持**负载均衡** 、容灾处理等功能。

负载均衡:简单数就是将请求分散给各个机器上,维持各个机器的请求的均衡态势。确保不会大量请求都涌入到某几个机器上导致机器过载。

容灾处理:就是及时剔除掉故障的机器。例如某次调用19.4.11.11上的OrderServer出现故障,那么服务中心会将这个地址剔除掉,防止下次再访问到这个有故障的地址。

2.5 rpc调用流程

2.6 重要组成

客户端:调用方

客户端存根:client stub,保存服务端地址信息 ,将客户端的请求方法、参数序列化 ,通过网络发送给服务端。

服务端存根:server stub,接收请求,反序列化,调用本地服务

服务端:服务提供者

网络传输:tcp或http

2.7 基础功能

2.7.1 服务寻址

rpc所有函数都有一个自己的ID,在所有进程中都唯一。客户端远程调用,必须附上这个ID.

客户端查一下映射表,找出对应ID,服务端根据对应的ID提供服务。

Call ID映射表一般是哈希表。

2.7.2 序列化和反序列化

概念

  1. 序列化:将消息对象转换为二进制流。
  2. 反序列化:将二进制流转换为消息对象。

必要性

  • 本地函数调用:只需要将数据压入栈中,然后让函数去栈中读取数据。
  • 远程调用:无法通过栈传递参数。需要客户端将请求序列化,传送给服务端。

序列化的优势

  • 二进制字节流便于网络传输
  • 可跨平台、跨语言。

2.7.3 网络传输

基于TCP

通过socket连接发送序列化的调用接口名称、方法名称和参数等。

基于HTTP

客户端发送GET/PUT/POST/DELETE请求给服务端

服务端根据不同的请求参数和请求url进行方法调用,返回结果

对比

基于TCP更灵活,可减少网络开销,性能高。但实现复杂;

HTTP已经实现序列化,但http传输效率比tcp低。

2.8 服务治理

2.8.1 负载均衡

本项目实现的rpc没有做负载均衡,因为当前是Read Log模式,读写请求都是直接发送给leader。后面如果优化为从follower读,可能用得到负载均衡。

  1. 轮询算法(Round Robin) :轮询算法是最简单的负载均衡算法之一,它按照请求的顺序依次将每个请求分配到不同的服务器上。当有新的请求到来时,负载均衡器会依次将请求发送到不同的服务器,直到所有的服务器都被轮询过一遍,然后再从头开始。
  2. 最小连接数算法(Least Connections) :最小连接数算法会将新的请求分配到当前连接数最少的服务器上,以确保各服务器的负载尽可能均衡。这种算法考虑了服务器的负载情况,优先将请求发送到负载较低的服务器上。
  3. 最少响应时间算法(Least Response Time) :最少响应时间算法会将请求发送到响应时间最短的服务器上,以保证响应时间的最小化。这种算法通常需要负载均衡器记录每个服务器的响应时间,并动态调整请求的分配策略。
  4. 哈希算法(Hashing) :哈希算法根据请求的某些属性(如客户端IP地址、URL等)计算哈希值,并将请求发送到对应哈希值的服务器上。这种算法能够确保相同请求始终被发送到同一台服务器上,适用于需要保持会话一致性的场景。
  5. 加权轮询算法(Weighted Round Robin) :加权轮询算法在轮询算法的基础上 引入了权重的概念,不同的服务器具有不同的权重值。根据权重值的不同,负载均衡器会调整请求的分配比例,以实现负载均衡。
  6. 拓展:hash环也是一种重要的负载均衡算法,也可以提及。

3 本项目rpc

本项目实现的rpc比较简单,采用的是同步阻塞模式。

rpc请求格式:

  1. head_size(4个字节,存储head_str的长度)
  2. head_str(RpcHeader类型的序列化)
  3. args(PutAppendArgs/GetArgs 请求对象的序列化

RpcHeader:

cpp 复制代码
message RpcHeader
{
    bytes service_name = 1;
    bytes method_name = 2;
    uint32 args_size = 3;
}

可以优化的点:

  • 异步rpc
  • rpc设为长连接还是短连接?长连接考虑设计一个定时器
  • 负载均衡
  • 服务发现,服务注册

序列化

1 Protobuf

kvServerRPC.proto文件

cpp 复制代码
syntax = "proto3";
package raftKVRpcProctoc; //所在的命名空间
option cc_generic_services = true;  //开启stub服务

message GetArgs{
  bytes Key = 1 ;
  bytes ClientId = 2 ;
  int32 RequestId = 3;
}
message GetReply  {
  bytes Err = 1;
  bytes Value = 2;
}

message PutAppendArgs  {
  bytes Key = 1;
  bytes  Value = 2 ;
  bytes  Op = 3;
  bytes  ClientId = 4;
  int32  RequestId = 5;
}
message PutAppendReply  {
  bytes Err = 1;
}

service kvServerRpc
{
  rpc PutAppend(PutAppendArgs) returns(PutAppendReply);
  rpc Get (GetArgs) returns (GetReply);
}

.proto文件定义message类型和service类型,编译后,生成.pb.h文件和.pb.cc文件。

.proto文件中定义的message类型可以序列化和反序列化(SerializeToString/ParseFromString).

编译后会生成对应Stub(存根)类型 raftKVRpcProctoc::kvServerRpc_Stub,使用Stub对象可以调用.proto文件中定义的service。

2 boost

快照:kvserver、kvdb和raft节点使用boost库进行序列化

相关推荐
ktkiko114 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
点点滴滴的记录18 小时前
RPC核心实现原理
网络·网络协议·rpc
徒步僧18 小时前
ThingsBoard规则链节点:RPC Call Reply节点详解
qt·microsoft·rpc
zfoo-framework2 天前
游戏中Dubbo类的RPC设计时的注意要点
网络·网络协议·rpc
帅气的人1233 天前
thrift rpc 四种类型的服务端的实现详细介绍
java·开发语言·网络·网络协议·rpc
eaglewgs5 天前
浅谈RPC的实现原理与RPC实战
网络·网络协议·rpc
菜鸟起航ing6 天前
Apache Dubbo (RPC框架)
rpc·apache·dubbo
帅气的人1236 天前
thrift idl 语言基础学习
java·开发语言·python·rpc·go·thrfit
一片蓝蓝的云7 天前
实现RPC接口的demo记录
网络·网络协议·rpc
Likelong~7 天前
设计一个灵活的RPC架构
网络协议·rpc·架构