RPC原理与实现 | 青训营笔记

111 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 十四 天

RPC原理与实现

  • RPC的基本概念
  • PRC框架分层设计
  • RPC框架的核心指标
  • Kitex实践

基本概念

函数映射

在本地调用中,函数体是直接通过函数指针来指定的,我们调用哪个方法,编译器就自动帮我们调用对应函数指针。但远程调用某一个函数时,函数又是如何被指定的呢?

远程调用本质上也是一次网络请求,所以远程函数都应该有一个自己的ID,在做PRC时只要带上这个ID,再通过函数与ID的对照关系表,就可以确定函数的指定并执行。

参数传递

在本地调用时,我们只需要将参数压栈,然后让函数读取栈中的值。但是在远程调用时显然无法通过压栈来实现,如何传参呢?

这时候就需要调用方将参数转成字节流,作为请求的参数发送给被调用方,再由被调用方将字节流转化成自己能读取的格式。

网络传输

如何保证远程调用在网络上高效稳定地传输呢?

可以借鉴TCP协议的可靠传输,同时引入保证超时机制让远程调用被消费。

分层设计

编解码层

代码生成

通过代码生成工具把IDL文件转换成不同语言对应的lib代码,里面封装了编解码逻辑

数据格式

  • 语言特定格式

    • 语言内置字节序列器,java.io.Serializable
    • 存在安全和兼容性问题
  • 文本格式

    • JSON、XML、CSV等,具有人类可读性
    • 编码纯在歧义,XML和CSV不能区分数字和字符串,JSON不能区分整数和浮点数
    • JSON存在需要采用反射机制进行序列化和反序列化,性能较差
  • 二进制编码

    • BinaryProtocol -> TLV编码、Protobuf -> Varint编码,具备跨语言和高性能等特点

选型

  • 兼容性

    • 支持自动增加新的字段,而不影响老的服务,这将提高系统的灵活度
  • 通用性

    • 支持跨平台、跨语言
  • 性能

    • 从空间和时间两个维度来考虑,也就是编码后数据大小和编码耗费时厂

协议层

消息切分

  • 特殊结束符:以一个特殊字符作为每个协议单元的结束标志
  • 变长协议:length+body,以定长的的length描述不定长的body

协议构造

协议构造.png

  • LENGTH: 数据包大小,不包含自身
  • HEADER MAGIC: 标识版本信息,协议解析时候快速校验
  • SEQUENCE NUMBER: 表示数据包的seqID,可用于多路复用,单连接内递增
  • HEADER SIZE:头部长度,从第14个字节开始计算一直到PAYLOAD前
  • PROTOCOL ID: 编解码方式,有Binary和Compact两种
  • TRANSFORM ID: 压缩方式,如zlib和snappy
  • INFO ID: 传递一些定制的meta信息
  • PAYLOAD: 消息体

网络通信层

网络库

  • 提供易用 API

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

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

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

核心指标

PRC框架衡量指标可以从以下五个方面着手:稳定性、易用性、扩展性、观测性、高性能

稳定性

  • 熔断: 保护调用方,防止被调用的服务出现问题而影响到整个链路
  • 限流: 保护被调用方,防止大流量把服务压垮
  • 超时控制: 避免浪费资源在不可用节点上
  • 负载均衡
  • 重试: 防止重试风暴,限制单点重试和限制链路重试
  • 长尾请求: 一般在是指明显高于均值占比较小的请求,业界常用标准P99标准

易用性

  • 开箱即用:合理的默认参数选项、丰富文档
  • 周边工具:代码生成工具、脚手架工具

拓展性

  • 支持中间件拓展
  • 编解码层拓展
  • 代码生成工具插件拓展等等

观测性

  • Log、Metric、Tracing三件套
  • 内置观测性服务
  • 暴露自身状态:环境变量、配置、Client/Server初始化参数、缓存信息

高性能

  • 连接池化
  • IO多路复用
  • 高性能编解码协议
  • 高性能网络库

Kitex实践

整体架构

  • Kitex Core:主干逻辑,定义了框架的层次、接口,还有接口的默认实现
  • Kitex Byted:对字节内部的拓展,继承了内部的二方库还有字节相关的非通用实现
  • Kitex Tool:代码生成相关实现

自研网络库 Netpoll 优化

  • 避免了原生库连接的无感知
  • 避免了原生库存在goroutine暴涨的风险

性能优化

  • 网络优化

    • 调度优化

      • epoll_wait 调度延迟优化
      • 合理利用 unsafe.Pointer
    • LinkBuffer 减少内存拷贝,从而减少 GC -> nocopy
    • 引入内存池和对象池
  • 编解码优化

    • Codegen:预计算提前分配内存,inline,SIMD等
    • JIT:无生产代码,将编译过程移到了程序的加载(或首次解析)阶段,可以一次性编译生成对应的 codec 并高效执行

课后

  1. 行业内各个流行的 RPC 框架的优劣对比

    dubbo不支持跨语言,thrift和GRPC不支持服务治理并且支持的序列化格式单一