RPC VS HTTP
首先明确就是RPC更早
本来需要联通两个端点需要
-
c-s:客户端-服务端:其中一种常见的方式是使用RPC(Remote Procedure Call,远程过程调用)。通过RPC,客户端可以调用服务端的方法,从而实现数据传输和交互。 -
b-s:浏览器-服务器:确实使用HTTP协议是非常常见的情况。在Web应用程序中,浏览器通过HTTP请求向服务器发送数据或请求页面,服务器则通过HTTP响应来返回所需的内容给浏览器。这是Web开发中最普遍的通信方式之一。当然这并不是绝对的
两者区别:
- http
- 简单性和易用性:RPC可能需要一些额外的配置和复杂性,特别是在跨语言和跨平台的情况下
- 通用性:HTTP是一种通用协议,广泛用于Web应用程序和服务之间的通信。RESTful API等成为一种流行的方式,允许不同类型的客户端与服务器进行交互
- 安全性和防火墙友好:由于HTTP通常使用标准的80端口和443端口(HTTPS),它通常能够通过大多数防火墙和代理服务器,从而使得与服务器的通信更加便捷。而某些RPC协议可能使用非标准端口,可能会受到网络限制。
- RPC:远程函数调用
- 性能:某些RPC框架在性能方面可能优于HTTP。RPC通常是基于二进制协议,可以更加高效地
序列化和反序列化数据。这在大规模的数据传输和高并发场景中可能表现更好。 - 功能丰富:某些RPC框架提供了丰富的功能和特性,如服务发现、负载均衡、容错机制等,这些特性可以使得分布式系统的开发更加方便。
- 跨语言支持:RPC通常可以实现跨语言通信,这对于涉及多种编程语言的大型系统是至关重要的。
RPC 框架分层设计
- RPC的概念模型
- THeader 协议
- 字节内部 Kitex 实践分享:这就是一个字节研发的RPC 框架
基本概念
网路的作用:可以获取到远程的服务、资源
- 资源分享
- 远程函数调用 = 远程服务 (基于资源分享)
RPC就相当于自己实现了一个http框架,可扩展性强,资源使用率更优
函数调用基本流程
func main(){
//1.将a和b的值压栈
var a =2
var b 3
//2.通过函数指针找到calculate函数,进入函数取出栈中的值2和3,将其赋予×和y
//5.从栈中取出Z返回值,并赋值给result
result := calculate(a,b)
fmt.Println(result)
return
}
func calculate(x,y int)//3.计算×*y,并将结果存在Z
z:x*y
return z //4.将z的值压栈,然后从calculate返回
}
远程调用
-
相比本地函数调用,RPC调用需要解决的问题
- 函数映射 : 函数都有自已的一个ID,在做RPC的时候要附上这个ID,还得有个ID和函数的对照关系表,通过ID找到对应的函数并执行。
- 数据转换成字节流 :数据传输
- 网络传输:传输协议
概念模型
具体的设计
apache具体实现
实现流程
接下来和http一起理解会很简单(真服了:小声)
- 生或代码
(网路应用层: 提取接口: idl): url接口 通过编译器工具把IDL文件转换成语言对应的静态库 ,就是按照一定规则编译 - 编码解码
(网路应用层:生成二进制 (类似生成json以便传输,这里是protobuf)):json数据 从内存中表示到字节序列的转换称为编码,反之为解码,也常叫做序列化和反序列化,二进制<->代码 - 通信协议
(网路应用层:请求url的参数、响应的数据,就是这个东西):请求响应 规范了数据在网络中的传输内容和格式。除必须的请求/响应数据外,通常还会包含额外的元数据 - 网络传输
(传输层:TCP报文传输): 一样 通常基于成熟的网络库走TCP/UDP传输 - 常见框架 thrift、kitex
IDL(nterface description language)文件: 描述有哪些方法、参数
- 作用:IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序(比如客户单js、服务端java)可以相互通信,
就是类似接口提供给客户端知道有哪些方法可以调用,特定的规则- 原理:服务双方是通过约定的规范进行远程调用,双方都依赖同一份IDL文件,需要通过工具来生成对应的生成文件,具体调用的时候,用户代码需要依赖生成代码,所以可以把用户代码和生成代码看做一个整体。
- 简写:就是客户端和服务端一起维护一个idl,里边记录的就是方法接口,客户端就是根据idl的接口来
知道 服务器的 那些方法,然后就可以访问服务端的对应的方法了- 一言以蔽之:http通过url访问对于的servlet、rpc通过idl来访问对于的方法,不过需要额外的生成
- thrift的idl 、 dobbo的idl:一眼get
编解码层
生或代码+编码解码:生成代码和编解码层相互依赖,框架的编解码应当具备扩展任意编解码协议的能力
根据同一份IDL的协议,进行编码以实现,跨平台、跨语言(就比如生成exe文件:大家都可以调用)
所以使用二进制编码
二进制编码:常见有 Thrift 的 BinaryProtocol,Protobuf,实现可以有多种形式,例如 TLV 编码 和 Varint 编码
-
TLV
- tag: 标识符
- length:数据长度
- value:值
如:type、tag记录编号即可;length固定的变量长度不需要
原代码:1 :String : name = "zc"
编码:type:string(0b) tag: name(01) length:16 value: zc itemtype:复合类型
协议层
协议就是基于报文的,类似于http协议的报文
报文结构
以 Thrift 的 THeader 协议为例
- LENGTH数据包大小,不包含自身
- HEADER MAGIC:标识版本信息,协议解析时候快速校验
- SEQUENCE NUMBER:表示数据包的seqID,可用于多路复用,单连接内递增
- HEADER SIZE:头部长度,从第14个字节开始计算一直到PAYLOAD前
- PROTOCOL ID:编解码方式,有Binary和Compact两种
- TRANSFORM ID:压缩方式,如zb和snappy INFO ID:传递一些定制的meta信息PAYLOAD:消息体
多路复用:
如区分http连接的东西:IP-IP,同一个连接的不同请求就可以走同一条连接
协议解析
网络通信层
- 阻塞 IO 下,耗费一个线程去阻塞在 read(fd) 去等待用足够多的数据可读并返回。
- 非阻塞 IO 下,不停对所有 fds 轮询 read(fd) ,如果读取到 n <= 0 则下一个循环继续轮询。
socket
比如使用socket,这样自定义网路传输的一个策略
套接字编程中的客户端必须知道两个信息:服务器的P地址,以及端口号。 socket函数创建一个套接字,bind将一个套接字绑定到一个地址上。listen监听进来的连接
backlog:指定挂起的连接队列的长度,当客户端连接的时候,服务器可能正在处理其他逻辑而未调用accept接受连接,此时会导致这个连接被挂起,内核维护挂起的连接队列,backlog则指定这个队列的长度,accept函数从队列中取出连接请求并接收它,然后这个连接就从挂起队列移除。如果队列末满,客户端调用connect马上成功,如果满了可能会阻塞等待队列未满(实际上在Liux中测试并不是这样的结果这个后面再专门来研究)。Liux的backlog默认是128,通常情况下,我们也指 定为128即可