RPC框架入门

74 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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   3const 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

程序运行

image-20230210121528043

在xshell中新开两个窗口,一个跑client端,另一个跑service端,在client中输入的数据,会被service端接收

image-20230210121848625

从这个简单的例子,我们可以清晰的了解到,rpc其实就是远程调用

rpc需要解决的问题

  • 函数映射
  • 数据转换为字节流
  • 网络传输

二、rpc概念模型

1984年,Nelson教授发表了论文《Implementing Remote Procedure Calls》,提出了rpc的过程的五个模型:

User,User-Stub,RPC-Runtime,Servie-Stub,Service

image-20230210122244912

一次完整的rpc过程

  1. 调用方持续把请求参数对象序列化成二进制数据,经过 TCP 传输到服务提供方
  2. 服务提供方从 TCP 通道里面接收到二进制数据
  3. 根据 RPC 协议,服务提供方将二进制数据分割出不同的请求数据,经过反序列化将二进制数据逆向还原出请求对象,找到对应的实现类,完成真正的方法调用
  4. 然后服务提供方再把执行结果序列化后,回写到对应的 TCP 通道里面
  5. 调用方获取到应答的数据包后,再反序列化成应答对象

rpc通信流程中包括了协议、序列化与反序列化、网络通信

RPC的好处

  • 单一职责,开发、运维、部署都是独立的,利于分工
  • 可扩展型强,资源使用率更优
  • 故障隔离,服务的整体性更可靠

三、RPC的分层设计

image-20230210123558645

编解码层

编码

image-20230210123657069

通过代码生成工具把IDL文件生成对应语言的lib库

二进制编码(TLV编码)

  • Tag:标签,可以理解为类型
  • Length:长度
  • Value:值

image-20230210123944037

协议层

特殊结束符

表示一个协议单元的结束

image-20230210124130618

变长协议

image-20230210125838203

由定长和不定长部分组成,定长部分需要描述不定长内容的长度

协议构造

image-20230210130604260

网络通信层

image-20230210130947735

底层是SOCKET API

网络库

  1. 提供易用api

    • 封装底层SOCKET API
    • 连接管理和事件分发
  2. 功能

    • 协议支持:tcp、udp、uds等
    • 优雅退出、异常处理
  3. 性能

    • 应用层buffer减少copy
    • 高性能定时器、对象池

四、rpc的关键指标

稳定性

保障性策略

  • 熔断:保护调用方,防止被调用的服务出现问题而影响整个链路
  • 限流:保护被调用方,防止大流量压垮服务器
  • 超时控制:避免浪费资源在不可用结点上
  • 使用负载均衡、请求重试等保证请求成功率

易用性

开箱即用

扩展性

image-20230210134340543

观测性

除基础三件套外,还加了环境变量、配置等

image-20230210134447313

高性能