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

42 阅读5分钟

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

只不过是字节给我的任务罢了

RPC

远程函数调用(Remote Procedure Calls)

需要解决的问题

  • 函数映射,每个函数都有一个ID
  • 数据转换成字节流,
  • 网络传输

一次RPC的完整过程

  • IDL文件,Interface Description Language 接口描述语言通过中立的方式描述接口,使得不同平台上运行的对象和用不同语言编写的程序可以相互通信
  • 生成代码,通过编译工具把IDL文件转换为对应语言的静态库
  • 编解码,从内存中表示到字节序列的转换称为编码,反之为解码,也叫做序列化和反序列化
  • 通信协议,规范数据在网络中的传输内容和格式,除了必须的请求/响应数据,通常还包括额外的元数据
  • 网络传输,通常基于成熟的网络库走TCP/UDP传输

RPC的好处

  • 单一职责,利于分工协作和运维开发
  • 可扩展性强,资源使用率更优
  • 故障隔离,服务的整体可靠性更高

RPC框架

分层设计

  • 编码层

    • 客户端和服务端依赖于同一个IDL文件,生成为不同的代码

    • 数据格式

      • 语言特定的格式:很多编程语言都內建了将内存对象编码为字节序列的支持,如java.io.Serializable

      • 文本格式:JSON、XML、CSV

      • 二进制编码:BinaryProtocol、Protobuf等

        • TLV编码

          • Tag,标签,可以理解为类型
          • Length,长度
          • Value,值,Value也可以是个TLV结构
        • 选型

          • 兼容性,支持自动增加新的字段,不影响老的服务
          • 通用性,支持跨平台、跨语言
          • 性能,从空间和时间两个维度考虑,也就是编码后数据大小和编码耗费时长
  • 协议层

    • 概念

      • 特殊结束符
      • 变长协议
    • 协议构造

      • LENGTH:数据包大小,不包含自身

      • HEADER MAGIC:标识版本信息,协议解析时会快速校验

      • SEQUENCE NUMBER:表示数据包的seqID,可用于多路复用,单连接内递增

      • HEADER SIZE:头部长度,从第14个字节开始计算到PAYLOAD之前

      • PROTOCOL ID:编解码方式,有Binary、Compact两种

      • TRANSFORM ID:压缩方式,如zlib和snappy

      • INFO ID:传递一些定制的meta信息

      • PAYLOAD:消息体

        image.png

    • 协议解析

      • 先读取MagicNumber,再读取编解码方式,最后解码消息体

        image.png

  • 网络通信层

    • Sockets API

      • 位于应用层和传输层之间

        image.png

        • 提供易用API

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

          • 支持tcp、udp、uds等
          • 优雅退出,异常处理
        • 性能

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

image.png

关键指标

稳定性

  • 保障策略

    • 熔断:保护调用方,防止调用的服务出现问题而影响到整个链路
    • 限流:保护被调用方,防止大流量压垮服务
    • 超时控制:避免在不可用节点上浪费资源
    • 上面三者都可以叫做“降级”
  • 请求成功率

    • 负载均衡
    • 重试
  • 长尾请求:明显高于平均响应时间的,占比较小的请求

    image.png

    • 注册中间件

    • 可以将上述稳定性策略以注册中间件的方式注入到服务端

易用性

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

扩展性

  • Middleware

  • Option

  • 编解码层

  • 协议层

  • 网络传输层

  • 代码生成工具插件扩展 image.png

    观测性

  • Log日志服务、Metric监控QPS等、Tracing链式追踪服务状态

  • 内置观测性服务

高性能

  • 场景

    • 单机多机
    • 单连接多连接
    • 单/多Client 单/多Server
    • 不同大小的请求包
    • 不同的请求类型:例如pingpong、streaming
  • 目标

    • 高性能
    • 低延迟
  • 手段

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

企业实践

整体架构

remote层是用于和远端交互的层

image.png

自研网络库

  • 原生库无法感知连接状态

    • 池中存在失效连接,影响连接池复用
  • 原生库存在Goroutine暴涨的风险

    • 一个连接一个Goroutine的模式,由于连接利用率低下,存在大量Goroutine占用调度开销,影响性能。

Netpoll

  • 引入epoll监听机制,感知连接状态
  • 建立Goroutine池,复用Goroutine
  • 引入Nocopy Buffer,向上层提供NoCopy的调用接口,编解码层面零拷贝,提升性能。
扩展性设计
  • 支持多协议,可以支持自定义协议扩展 image.png

    image.png

    网络库优化
  • 调度优化

    • epoll_wait在调度上的控制
    • gopool重用Goroutine降低同时运行协程数
  • LinkBuffer

    • 读写并行无锁,支持nocopy地流式读写
    • 高效扩缩容
    • Nocopy Buffer池化,减少GC
  • Pool

    • 引入内存池和对象池,减少GC开销
编解码优化
  • Codegen

    • 预计算并预分配内存,减少内存操作次数,包括内存分配和拷贝
    • Inline减少函数调用次数和避免不必要的反射操作
  • JIT(Just In TIme)即时编译

    • 基于JIT编译技术的高性能动态Thrift编解码器——Frugal
    • 使用JIT编译技术改善用户体验的同时带来更强的编解码功能,减轻用户维护生成代码的负担
合并部署

微服务过微,传输和序列化开销越来越大

将亲和性强的服务实例尽可能调度到同一个物理机,远程RPC调用优化为本地IPC调用

  • 中心化部署调度和流量控制
  • 基于共享内存的通信协议
  • 定制化的服务发现和连接池实现
  • 定制化的服务启动和监听逻辑

参考

juejin.cn/post/719632…