RPC(Remote Procedure Call,远程过程调用)框架是分布式服务的基石,实现RPC框架需要考虑方方面面。其对业务隐藏了底层通信过程(TCP/UDP、打包/解包、序列化/反序列化),使上层专注于功能实现;框架层面,提供各类可选架构(多进程/多线程/协程);应对设备故障(高负载/死机)、网络故障(拥塞/网络分化),提供相应容灾措施。

RPC节点间为了协同工作、实现信息交换,需要协商一定的规则和约定,例如字节序、压缩或加密算法、各字段类型。通信协议的应用随处可见,例如我们对可选信息或字段经常使用TLV进行编码,HTTP、FTP等协议基于可读文本的 "Field: Value" 格式,各种系统也经常使用json、XML格式完成相互间通信。

不同的通信协议适用于不同的应用场景,比如内部系统的交互我们选择json,一来可读性较好,二来各种语言都提供了解析json的库、方便编码。Google Protocol Buffers是生成环境中常用的通信协议,除了可以设定Client/Server间通信格式,Protocol Buffers还对数据进行压缩,节省传输流量、加快传输速度。下面我们来了解Google Protocol Buffers。

Protocol Buffers

我们看如何使用Protocol Buffers(以下简称PB),首先在.proto文件中定义数据格式,下面以Person.proto为例:

message Person {
required string name = ;
required int32 id = ;
optional string email = ; enum PhoneType {
MOBILE = ;
HOME = ;
WORK = ;
} message PhoneNumber {
required string number = ;
optional PhoneType type = [default = HOME];
} repeated PhoneNumber phone = ;
}

message类型内,可以定义int、string、bool、string等类型的字段,也可以嵌套定义messages类型。每个字段可以是required、optional或repeated类型,分别表示必须每次通信必须填充该字段、可选或可重复。每个message类型内的每个字段被赋值唯一的数字值,PB以二进制格式进行数据传输,数字值在二进制中作为该字段的标识。关于PB数据格式的更多内容可参考Protocol Buffers Language Guide

完成数据定义后,接下来可以使用protoc工具解析Person.proto文件,生成Person类:

protoc --cpp_out=/home/bangerlee/PB ./Person.proto

执行以上命令后,可以看到 /home/bangerlee/PB 目录下生成了两个文件:

person.pb.cc  person.pb.h

其中定义了操作(get/set)Person类各个字段的函数。

有了接口,我们就可以在代码中这样使用Person类,写入操作如下:

Person person;
person.set_name("bangerlee");
person.set_id();
person.set_email("bangerlee@gmail.com");
fstream output("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output);

读取操作如下:

fstream input("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

以上我们初步了解了如何使用PB,PB运用了一些编码规则,使得需要传输的数据(二进制格式)更小,下面我们就来了解PB如何对不同数据类型的编码规则。

编码(Encoding)

对整形int、字符串类型string等,PB有不同的编码方式。对整型int,PB使用了Varints编码方式,Varints编码的优势是使用了更少的bytes来表示很小的int类型值。

Varints编码方式中,每个byte的最高位bit有特殊含义,如果为1,表示后续的byte也是这个数字的一部分;如果为0,则表示结束。剩余的7个bit用于表示数据。数字300用Varints编码方式表示为:

   

由Varints编码规则,去掉第一个byte的最高位1,去掉第二个byte的最高位0,则有:

Varints字节序使用little-endian,以上数字用big-endian并转换成10进制有:

→    ++

→ + + + =

以上了解了Varints对int整型的编码方式,我们再来看PB如何编码更多数据类型:

PB编码中,数据以key-value的形式表示,第一个byte即为key。以上表格中不同数据类型对应指定type值,假设message中各字段的数字标识为tag,则key、type和tag有以下对应关系:

key = tag <<  | type

即key的最后3个bit用于存储type,有了这层关系,我们试着演算PB中对int和string的编码。

假设我们截获到以下PB数据:

  

这段数据具体表示什么?我们用以上对应关系演算一下,首先该数据key是08,二进制表示即:

 

最后3个bit表示type,即type为0(Varint格式数据),左移3位得到tag值为1。有了这些信息,我们可以知道这个数据应该是这样定义的:

message Test1 {
xxx int32 a = ;
}

继续地,我们用Varint格式来解析 96 01,有以下演算过程:

  =
→ ++ (丢弃最高位的bit并转为big-endian)

→ + + + =

因此我们可以知道这段数据表示150这个数。

又假设我们截获到以下一段PB编码:

       6e 

同样套用以上关系,key是12,二进制表示即:

 

最后3个bit表示type,即type为2(Length-delimited),左移3位得到tag值为2。有了这些信息,我们知道这个数据可能是这样定义的:

message Test2 {
xxx string b = ;
}

数据类型具体是string、bytes或其他,这并不影响我们解析这段数据,对于Length-delimited格式数据,第2个byte表示数据长度(Len),对应以上编码即Len为7,这实质是TLV编码格式。

后续的7个bytes表示有效的传输数据,为UTF-8编码下的"testing"字符串。

小结

以上介绍了通信协议 - Google Protocol Buffers,了解了其基本使用方法和编码方式。PB支持前向兼容,可以在不修改Client/Server程序的情况下修改其中一端的数据格式,在各种RPC框架中经常可以看到它的身影。

Reference: Protocol Buffers Developer Guide

Protocol Buffers Encoding

RPC框架实现 - 通信协议篇的更多相关文章

  1. RPC框架实现

    转载RPC框架实现 RPC(Remote Procedure Call,远程过程调用)框架是分布式服务的基石,实现RPC框架需要考虑方方面面.其对业务隐藏了底层通信过程(TCP/UDP.打包/解包.序 ...

  2. 基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇

    基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇 前提 最近对网络编程方面比较有兴趣,在微服务实践上也用到了相对主流的RPC框架如Spring Cloud Gateway底层也切换 ...

  3. 一个简单RPC框架是怎样炼成的(I)——开局篇

    开场白,这是一个关于RPC的相关概念的普及篇系列,主要是通过一步步的调整,提炼出一个相对完整的RPC框架. RPC(Remote Procedure Call Protocol)--远程过程调用协议, ...

  4. 基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇

    前提 前置文章: Github Page:<基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> Coding Page:<基于Netty和SpringBoot实现 ...

  5. 基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇

    前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> 前 ...

  6. Hadoop系列番外篇之一文搞懂Hadoop RPC框架及细节实现

    @ 目录 Hadoop RPC 框架解析 1.Hadoop RPC框架概述 1.1 RPC框架特点 1.2 Hadoop RPC框架 2.Java基础知识回顾 2.1 Java反射机制与动态代理 2. ...

  7. 看了这篇你就会手写RPC框架了

    一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...

  8. 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍

    概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...

  9. RPC框架调用过程详解

    RPC框架调用过程详解 2017年09月16日 21:14:08 荷叶清泉 阅读数 6275   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...

随机推荐

  1. .Net多线程编程—并发集合

    并发集合 1 为什么使用并发集合? 原因主要有以下几点: System.Collections和System.Collections.Generic名称空间中所提供的经典列表.集合和数组都不是线程安全 ...

  2. 三分钟学会用 js + css3 打造酷炫3D相册

    之前发过该文,后来不知怎么回事不见了,现在重新发一下. 中秋主题的3D旋转相册 如图,这是通过Javascript和css3来实现的.整个案例只有不到80行代码,我希望通过这个案例,让正处于迷茫期的j ...

  3. AFNetworking 3.0 源码解读 总结(干货)(上)

    养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...

  4. 深入浅出Redis-redis哨兵集群

    1.Sentinel 哨兵 Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所 ...

  5. AspNetPager分页控件样式的使用

    分页是Web应用程序中最常用到的功能之一,AspNetPager  简单实用,应用到项目后台中,棒极了! 自定义样式: <style type="text/css"> ...

  6. 微信小程序开发日记——高仿知乎日报(中)

    本人对知乎日报是情有独钟,看我的博客和github就知道了,写了几个不同技术类型的知乎日报APP要做微信小程序首先要对html,css,js有一定的基础,还有对微信小程序的API也要非常熟悉 我将该教 ...

  7. javascript排序

    利用array中的sort()排序 w3cfunction sortNumber(a,b) { return a - b } var arr = new Array(6) arr[0] = " ...

  8. iOS之计算上次日期距离现在多久, 如 xx 小时前、xx 分钟前等

    /**  *  计算上次日期距离现在多久  *  *  @param lastTime    上次日期(需要和格式对应)  *  @param format1     上次日期格式  *  @para ...

  9. 敏捷转型历程 - Sprint3 一团糟的演示会

    我: Tech Leader 团队:团队成员分布在两个城市,我所在的城市包括我有4个成员,另外一个城市包括SM有7个成员.另外由于我们的BA离职了,我暂代IT 的PO 职位.PM和我在一个城市,但他不 ...

  10. mysql 5.7中的用户权限分配相关解读!

    这篇文章主要介绍了MySQL中基本的用户和权限管理方法,包括各个权限所能操作的事务以及操作权限的一些常用命令语句,是MySQL入门学习中的基础知识,需要的朋友可以参考下 一.简介 各大帖子及文章都会讲 ...