文章

网络协议打怪升级-应用层:RPC(gRPC)

网络协议打怪升级-应用层:RPC(gRPC)

1.RPC

1.1.RPC是什么

RPC全称(Remote Procedure Call Protocol),远程调用协议,主要用于异构的分布式系统间通信。

它更像是一种概念,或者说是一种技术模型,其核心思想为:

1
让你像调用本地接口一样调用远程服务器上的函数

1.2.RPC特性

  • 目标
    • 尽量保证提供类似本地调用的简洁语义的基础上,让分布式应用间的通信变得更加方便和高效
    • 调用者无需显示区分本地和远程调用,同时基于RPC使得服务治理(服务限流服务熔断)更加方便
  • 解决了哪些问题
    • 不同服务可能由不同团队开发,跨语言接口需求不断增加
    • 分布式服务之间的服务治理可以通过RPC框架解决
  • 核心组件有哪些
    • 客户端:发起rpc请求
    • 客户端存根:存放服务端的服务列表,客户端直接调用存根,rpc将客户端的调用请求序列化打包并通过服务端发送到服务器
    • 服务端口:接收rpc请求
    • 服务端存根:接收客户端请求并解包,然后调用本地方法
  • RPC的调用过程:
    • 服务寻址:使用注册中心(Registry),小型服务可以直接配置静态IP
    • 建立连接
    • 网络传输
    • 服务调用
    • 响应返回

2.gRPC

2.1.gRPC概念

gRPC是对RPC理论的一种工业实现现代化升级,由google在2015年开源,支持如下特性:

  • 使用HTTP/2协议
    • 支持多种HTTP/2特性,如多路复用,头部压缩,二进制传输等,传输效率高,网络包大小合适
  • 序列化默认使用protobuf(protocol buffer)协议
  • 多语言支持
  • 支持多种调用模式
    • 支持普通调用、流式调用(客户端/服务端/双工)
  • 可通过插件实现多种扩展
    • 负载均衡、拦截器、认证、支持转HTTP/HTTPS等

2.2.protobuf序列化

  • protobuf 核心特性
特性描述
二进制格式体积小,性能优
强类型.proto文件严格定义字段类型,代码生成
可选字段编号每个字段都有可选唯一编号,方便升级兼容
版本兼容-
多语言支持go,js,java,python…
  • protobuf 语法
1
2
3
4
5
6
syntax = "proto3";   //表示选择proto3语法

message Person {   //结构体,struct
  string name = 1;  //字段编号
  int32 age = 2;
}
  • 使用protoc生成代码
1
protoc --go_out=. --go-grpc_out=. person.proto

生成的代码中包含:

  • 消息结构体定义
  • 序列化、反序列化方法
  • gRPC服务端&客户端接口

  • 以客户端为例,看看protobuf的整个序列化、反序列化过程
1
2
3
4
5
6
7
8
9
10
11
客户端(结构体)        ↓ Protobuf 序列化
                [二进制请求数据]
                     ↓ 通过 HTTP/2 发送
                 服务端收到
                     ↓ Protobuf 反序列化
             服务端内部调用函数
                     ↓ 返回结构体
                ↓ Protobuf 序列化
             [二进制响应数据]
                   ↓ 客户端反序列化
         → 客户端得到响应结构体
  • .proto文件会被编译为二进制,字段编码为连续的“(字段编号+类型)+长度+值的形式” 以下message为例:

    1
    2
    3
    4
    5
    6
    7
    8
    
    message User {
    string name = 1;
    int32 age = 2;
    }
    
    name: "Joe"
    age: 30
    
    
    • 字段类型-wire_type
    字段类型wire_type用于的字段类型
    Varint0int32,int64,bool
    64-bit1double,fixed64
    Length-delimited2string,bytes,message
    32-bit5float,fixed32

    由此我们可以通过二进制编码规则得到上述message的二进制编码

    • 1-string name = 1;
    1
    2
    3
    4
    5
    
    字段类型:string -> Length-delimited -> 2
    字段编号:1
    (字段编号+字段类型):1 << 3 | 2 = 0x0A
    字段长度:3 = 0x03
    值Joe的utf-8字符串= 0x4A 6F 65
    

    最终,编号为1的name字段的编码为: 0x0A,0x03,0x4A,0x6F,0x65

    • 2-int32 age = 2
    1
    2
    3
    4
    
    字段类型:int32 -> Varint -> 0
    字段编号:2
    (字段编号+字段类型):2 << 3 | 0 = 0x10
    值30的16进制表示= 0x1E
    

    最终,编号为2的age字段的编码为: 0x10,0x1E

得到该message的编码:0x0A,0x03,0x4A,0x6F,0x65,0x10,0x1E

  • 为什么Varint字段类型不需要一个字节来表示字段值的长度?这就要提到Varint的编码机制-可变长度编码机制了 可变长度编码机制:对于Varint类型,使用每个字节的最高位表示值是否还有后续字节的表示。

    最高位表示是否还有后续字节,剩余7位表示具体值

    也就是说,数据值越小时,占用的字节数就越少。数据值越大,占用字节数越多,且都用最高位表示是否还有后续。

3.总结

RPC(远程调用协议)是一种思想,旨在忽略语言与其他调用细节,使调用其他服务时如调用本地服务一样丝滑。

gRPC则是RPC的落地实现,它既是一个协议,也是一个框架,由Google在2015年开源。序列化默认使用protobuf协议,包传输采用http2协议。 具有序列化包小、传输性能强、支持多路复用等特性,在现代微服务架构中被广泛使用。

protobuf协议采用二进制编码的形式,将结构体编码为(字段编码+字段类型)+[字段长度(Length-delimited)]+字段值的形式,对于Varint这种类型,采用可变长度编码,减少了重复传输的数据量,大幅降低包大小,提高了传输效率。

本文由作者按照 CC BY 4.0 进行授权