一、基础概念
1、本地函数调用
func main(){
var a = 2
var b = 3
result := calculate(a,b)
fmt.Println(result)
return
}
func calculate(x,y int){
z := x * y
return z
}
(1)将a和b的值压栈
(2)通过函数指针找到calculate函数,进入函数取出栈中的值2和3,将其赋予x和y
(3)计算x * y,并将结果存在z
(4)将Z的值压栈,然后从calculate返回
(5)从栈中取出z返回值,并赋值给result
以上步骤只是为了说明原理。事实上编码器经常会做优化,对于参数和返回值少的情况会直接将其存放在寄存器,而不需要压栈弹栈的过程,甚至都不需要调用call,而直接做inline操作。
2、远程函数调用(RPC-Remote Procedure Calls)
函数映射:怎样告诉支付服务我们要调用付款这个函数,而不是退款或充值?
- 在本地调用中,函数体是直接通过函数指针指定的,我们调用哪个方法,编译器就会自动帮我们调用相应的函数指针
- 在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所有函数都有自己的一个ID,在做RPC的时候要附上这个ID,还要有一个ID和函数的对照关系表,通过ID找到对应的函数并执行。
客户端如何将参数值传给远程函数?
- 在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读即可
- 在远程调用中,客户端跟服务端是不同的进程,不能通过内存传递参数。这时需要客户端把参数先转成一个字节流,传给服务端后再把字节流转成自己能读取的格式。
3、RPC概念模型
4、一次RPC的完整过程
- IDL(Interface description language)文件
通过一种中立的方式来描述接口,使得在不同平台上运行的对象和不同语言编写的程序可以相互通信。
- 生成代码
通过编译器工具把IDL文件转换成语言对应的静态库。
- 编解码
从内存中表示到字节序列的转换称为编码,反之为解码,也常叫做序列化和反序列化。
- 通信协议
规范了数据在网络中的传输内容和格式,除必须的请求/相应数据外,通常还包含额外的元数据
- 网络传输
通常基于成熟的网络库走TCP/UDP传输
5、RPC的好处
(1)单一职责,有利于分工协作和运维开发
(2)可扩展性强,资源使用率更优
(3)故障隔离,服务的整体可靠性更高
6、RPC带来的问题
(1) 服务宕机,对方如何处理?
(2)在调用过程中发生网络异常,如何保证消息的可达性?
(3)请求量突增导致服务无法及时处理,有哪些应对措施?
二、分层设计
1、分层设计-以Apache Thrift为例
2、编解码层-生成代码
3、编解码层-数据格式
- 语言特定的格式
许多编程语言都内建了将内存对象编码为字节序列的支持,例如Java有java.io.Serializable
- 文本格式
JSON、XML、CSV等文本格式,具有人类可读性
- 二进制编码
具备跨语言和高性能等优点,常见有Thrift的BinaryProtocol、Protobuf等
4、编解码层-二进制编码
TLV编码
- Tag:便签,可以理解为类型
- Length:长度
- Value:值,Value也可以是TLV结构
5、编解码层-选型
- 兼容性:支持自动增加新的字段,而不影响老的服务,这将提高系统的灵活度
- 通用性:支持跨平台、跨语言
- 性能:从空间和时间两个维度考虑,也就是编码后数据大小和编码耗费时长
6、协议层
(1)概念
(2)协议构造
- LENGTH:数据包大小,不包含自身
- HEARER MAGIC:标识版本信息,协议解析时快速校验
- SEQUENCE NUMBER:表示数据包的seqID,可用于多路复用,单连接内递增
- HEADER SIZE:头部长度,从第14个字节开始计算一直到PAYLOAD前
- PROTOCOL ID:编解码方式,有Binary和Compact两种
- TRANSFORM ID:压缩方式,如zlib和snappy
- INFO ID:传递一些定制的meta信息
- PAYLOAD:消息体
(3)协议解析
5、网络通信层
(1)Sockets API
(2)网络库
- 提供易用API
封装底层Socket API 连接管理和事件分发
- 功能
协议支持:tcp、udp和uds等 优雅退出、异常处理等
- 性能
应用层buffer减少copy
高性能定时器、对象池等