这是我参与「第五届青训营 」伴学笔记创作活动的第 10 天
RPC框架详解
一、什么是RPC
Remote Procedure Call,RPC 是一种进程间通信方式,允许像调用本地服务一样调用远程服务,通信协议大多采用二进制方式。
简单的rpc程序
client端代码
#include"comm.hpp"
int main()
{
//获取管道文件
int fd = open(ipcPath.c_str(),O_WRONLY);
if(fd < 0)
{
perror("open");
exit(1);
}
//ipc过程
string buffer;
// while(true)
// {
// cout << "Please Enter Message Line :> ";
// std::getline(std::cin,buffer);
// write(fd,buffer.c_str(),buffer.size());
// }
buffer = "I am process A";
write(fd,buffer.c_str(),buffer.size());
//关闭管道文件
close(fd);
return 0;
}
service端代码
#include"comm.hpp"
#include<sys/wait.h>
static void getMessage(int fd)
{
char buffer[SIZE];
while(true)
{
memset(buffer,'\0',sizeof(buffer));
ssize_t s = read(fd,buffer,sizeof(buffer)-1);
if(s > 0)
{
cout<<"["<<getpid()<<"]"<<"client say>"<<buffer<<endl;
}
else if(s == 0)
{
cout<<"["<<getpid()<<"]"<<"read end of file, clien quit, server quit too!" << endl;
}
else
{
perror("read");
break;
}
}
// memset(buffer,'\0',sizeof(buffer));
// ssize_t s = read(fd,buffer,sizeof(buffer)-1);
// if(s > 0)
// {
// cout<<"["<<getpid()<<"]"<<"client say>"<<buffer<<endl;
// }
// else if(s == 0)
// {
// cout<<"["<<getpid()<<"]"<<"read end of file, clien quit, server quit too!" << endl;
// }
// else
// {
// perror("read");
// }
}
int main()
{
//创建管道文件
if(mkfifo(ipcPath.c_str(),MODE) < 0)
{
perror("mkfifo");
exit(1);
}
//Log("创建管道文件成功!",Debug)<<"step first"<<endl;
//文件操作
int fd = open(ipcPath.c_str(),O_RDONLY);
if(fd < 0)
{
perror("open");
exit(2);
}
//Log("打开管道文件成功!",Debug)<<"step second"<<endl;
pid_t id = fork();
if(id == 0)
{
getMessage(fd);
exit(1);
}
waitpid(-1,nullptr,0);
//关闭管道文件
close(fd);
//Log("关闭管道文件成功!",Debug)<<"step third"<<endl;
//删除管道文件
unlink(ipcPath.c_str());
//Log("删除管道文件成功!",Debug)<<"step fourth"<<endl;
return 0;
}
comm.hpp配置
#ifndef _COMM_H_
#define _COMM_H_
#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<cassert>
#include"Log.hpp"
using namespace std;
#define MODE 0666
#define SIZE 128
string ipcPath = "./fifo.ipc";
#endif
Log.hpp配置
#ifndef _LOG_H_
#define _LOG_H_
#include<iostream>
#include<ctime>
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
const std::string msg[] =
{
"Debug",
"Notice",
"Warning",
"Error"
};
std::ostream& Log(std::string message,int level)
{
std::cout<<"|"<<(unsigned)time(nullptr)<<"|"<<msg[level]<<"|"<<message;
return std::cout;
}
#endif
程序运行
在xshell中新开两个窗口,一个跑client端,另一个跑service端,在client中输入的数据,会被service端接收
从这个简单的例子,我们可以清晰的了解到,rpc其实就是远程调用
rpc需要解决的问题
- 函数映射
- 数据转换为字节流
- 网络传输
二、rpc概念模型
1984年,Nelson教授发表了论文《Implementing Remote Procedure Calls》,提出了rpc的过程的五个模型:
User,User-Stub,RPC-Runtime,Servie-Stub,Service
一次完整的rpc过程
- 调用方持续把请求参数对象序列化成二进制数据,经过 TCP 传输到服务提供方
- 服务提供方从 TCP 通道里面接收到二进制数据
- 根据 RPC 协议,服务提供方将二进制数据分割出不同的请求数据,经过反序列化将二进制数据逆向还原出请求对象,找到对应的实现类,完成真正的方法调用
- 然后服务提供方再把执行结果序列化后,回写到对应的 TCP 通道里面
- 调用方获取到应答的数据包后,再反序列化成应答对象
rpc通信流程中包括了协议、序列化与反序列化、网络通信
RPC的好处
- 单一职责,开发、运维、部署都是独立的,利于分工
- 可扩展型强,资源使用率更优
- 故障隔离,服务的整体性更可靠
三、RPC的分层设计
编解码层
编码
通过代码生成工具把IDL文件生成对应语言的lib库
二进制编码(TLV编码)
- Tag:标签,可以理解为类型
- Length:长度
- Value:值
协议层
特殊结束符
表示一个协议单元的结束
变长协议
由定长和不定长部分组成,定长部分需要描述不定长内容的长度
协议构造
网络通信层
底层是SOCKET API
网络库
-
提供易用api
- 封装底层SOCKET API
- 连接管理和事件分发
-
功能
- 协议支持:tcp、udp、uds等
- 优雅退出、异常处理
-
性能
- 应用层buffer减少copy
- 高性能定时器、对象池
四、rpc的关键指标
稳定性
保障性策略
- 熔断:保护调用方,防止被调用的服务出现问题而影响整个链路
- 限流:保护被调用方,防止大流量压垮服务器
- 超时控制:避免浪费资源在不可用结点上
- 使用负载均衡、请求重试等保证请求成功率
易用性
开箱即用
扩展性
观测性
除基础三件套外,还加了环境变量、配置等