在好久以前的单机时代,一台电脑中跑着多个进程,进程之间没有交流各干各的,就这样过了不少年。忽然有一天有了新需求,A进程须要实现一个画图的功能,刚好邻居B进程已经有了这个功能,偷懒的程序员C想出了一个办法:A进程调B进程的画图功能。因而出现了IPC(Inter-process communication,进程间通讯)。就这样程序员C愉快的去吃早餐去了!java
又过了几年,到了互联网时代,每一个电脑都实现了互联互通。这时候雇主又有了新需求,当时还没挂的A进程须要实现使用tensorflow识别出笑脸 >_< 。说巧不巧,远在几千里的一台快速运行的电脑上已经实现了这个功能,睡眼惺忪的程序媛D接手了这个A进程后借鉴以前IPC的实现,把IPC扩展到了互联网上,这就是RPC(Remote Procedure Call,远程过程调用)。RPC其实就是一台电脑上的进程调用另一台电脑上的进程的工具。成熟的RPC方案大多数会具有服务注册、服务发现、熔断降级和限流等机制。目前市面上的RPC已经有不少成熟的了,好比Facebook家的Thrift、Google家的gRPC、阿里家的Dubbo和蚂蚁家的SOFA。git
接口定义语言,简称IDL,是实现端对端之间可靠通信的一套编码方案。这里有涉及到传输数据的序列化和反序列化,咱们经常使用的http的请求通常用json当作序列化工具,定制rpc协议的时候由于要求响应迅速等特色,因此大多数会定义一套序列化协议。好比:程序员
Protobuf:github
讲到Protobuf就得讲到该库做者的另外一个做品Cap'n proto了,号称性能是直接秒杀Google Protobuf,直接上官方对比:json
虽然知道不少比Protobuf更快的编码方案,可是快到这种地步也是厉害了,为啥这么快,Cap’n Proto的文档里面就马上说明了,由于Cap'n Proto没有任何序列号和反序列化步骤,Cap'n Proto编码的数据格式跟在内存里面的布局是一致的,因此能够直接将编码好的structure直接字节存放到硬盘上面。贴个栗子:数组
咱们这里要定制的编码方案就是基于protobuf和Cap'n Proto结合的相似的语法。由于本人比较喜欢刀剑神域里的男主角,因此就给这个库起了个名字—— Kiritobuf。网络
首先咱们定义kirito的语法:架构
=号左边是参数名,右边是参数类型框架
参数类型:函数
UInt8, UInt16, UInt32, UInt64 - Floating-point: Float32, Float64 - Blobs: Text, Data - Lists: List(T)
定义好了语法和参数类型,咱们先过一下生成有抽象关系代码的流程:
取到.kirito后缀的文件,读取所有字符,经过词法分析器生成token,获得的token传入语法分析器生成AST (抽象语法树)。
首先咱们新建一个kirito.js文件:
定义好了一些必要的字面量,接下来首先是词法分析阶段。
一、词法解析
咱们设计词法分析获得的Token是这样子的:
词法分析步骤:
代码以下:
二、语法分析
获得上面的词法分析的token后,咱们就能够对该token作语法分析,咱们须要最终生成的AST的格式以下:
看上图咱们能友好的获得结构、参数、数据类型、函数之间的依赖和关系,步骤:
一、遍历词法分析获得的token数组,经过调用分析函数提取token之间的依赖节点
二、分析函数内部定义token提取规则,好比:
三、递归调用分析函数提取对应节点依赖关系,将节点添加到AST中
代码以下:
三、转换器
获得了语法分析的AST后咱们须要进一步对AST转换为更易操做的js对象。格式以下:
经过上面这个格式,咱们能够更容易的知道有几个service、service里有多少个函数以及函数的参数。
代码以下:
RPC协议有多种,能够是json、xml、http2,相对于http1.x这种文本协议,http2.0这种二进制协议更适合做为RPC的应用层通讯协议。不少成熟的RPC框架通常都会定制本身的协议已知足各类变化莫测的需求。
好比Thrift的TBinaryProtocol、TCompactProto-col等,用户能够自主选择适合本身的传输协议。
(除了按字节编址还有按字编址和按位编址),咱们这里只讨论字节编址。每一个机器由于不一样的系统或者不一样的CPU对内存地址的编码有不同的规则,通常分为两种字节序:大端序和小端序。
举个栗子:
好比一个整数:258,用16进制表示为0x0102,咱们把它分为两个字节0x01和ox02,对应的二进制为0000 0001和0000 0010。在大端序的电脑上存放形式以下:
小端序则相反。为了保证在不一样机器之间传输的数据是同样的,开发一个通信协议时会首先约定好使用一种做为通信方案。java虚拟机采用的是大端序。在机器上咱们称为主机字节序,网络传输时咱们称为网络字节序。网络字节序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操做系统等无关,从而能够保证数据在不一样主机之间传输时可以被正确解释。网络字节序采用大端排序方式。
咱们这里就不造新应用层协议的轮子了,咱们直接使用MQTT协议做为咱们的默认应用层协议。MQTT(Message Queuing Telemetry Tran-sport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通信协议,采用大端序的网络字节序传输,该协议构建于TCP/IP协议上。
先贴下实现完的代码调用流程,首先是server端:
client端:
不管是server端定义函数或者client端调用函数都是比较简洁的步骤。接下来咱们慢慢剖析具体的逻辑实现。
贴下具体的调用流程架构图:
调用流程总结:
说完了调用流程,如今开始讲解具体的实现。
server:
定义protocol接口,加上这一层是为了之后的多协议,mqtt只是默认使用的协议:
接下来是server端的暴露出去的接口:
client:
定义protocol接口:
最后是client端暴露的接口:
就这样,一个简单的IDL+RPC框架就这样搭建完成了。这里只是描述RPC的原理和经常使用的调用方式,要想用在企业级的开发上,还得加上服务发现、注册,服务熔断,服务降级等,读者若是有兴趣能够在Github上fork下来或者提PR来改进这个框架,有什么问题也能够提Issue, 固然PR是最好的 : ) 。
仓库地址:
RPC: https://github.com/polixjs/po...
IDL:https://github.com/rickyes/ki...
更多文章请访问数澜社区,欢迎你们来一块儿学习~