rpc
# 什么是 rpc
- RPC(Remote Procedure Call)远程过程调用,简单的理解就是一个节点请求另一个节点提供的服务。
- 对应 rpc 的是本地过程调用,函数调用是最常见的本地调用过程
- 将本地过程调用变成远程过程调用会面临各种问题
# 本地过程调用
def add(a, b):
return a + b
total = add(1, 2)
print(total)
2
3
4
5
函数调用过程:
- 将 1 和 2 压入
add
函数的栈 - 进入
add
函数,从栈中取出 1 和 2 分别赋值给a
和b
- 执行
a + b
将结果压栈 - 最后将结果出栈,赋值给
total
,然后打印出数据
# 远程过程调用面临的问题
原本本地的函数放到另外一台服务器上去运行,会引入很多新的问题
Call 的 id 映射(不一定叫这个,但是一定是唯一的)
我们怎么告诉远程机器我们要调用
add
,而不是sub
或者foo
呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用add
,编译器就自动帮我们调用它的函数指针。但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以在 RPC 中,所有的函数必须有自己的一个
ID
,这个ID
在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID
,然后我们还需要在客户端和服务端分别维护一个{函数 <--> Call ID}
的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID
必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call ID
,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行响应的函数的代码。序列化和反序列化
客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要将参数压栈,然后让函数自己去栈里读就行。
但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言。
这个时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己可以读取的格式,这个过程叫序列化和反序列化。
同理,从服务端返回的值也需要序列化和反序列化的过程。
网络传输问题
远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都是需要通过网络传输,因此就需要有一个网络传输层。
网络传输层需要把
Call ID
和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回给客户端。只要能完成这两者的,都可以作为传输层使用。因此,它锁使用的协议其实是不限的,能完成传输就行,尽管大部分 RPC 框架都使用 TCP 协议,但是其实 UDP 也可以,而
gRPC
干脆就用了HTTP2
。Java
的Netty
也属于这层的东西。HTTP
协议来说,有一个问题:一次性,一旦对方反悔了结果,连接断开。所以gRPC
的HTTP2.0
可以实现长连接,且兼容HTTP
协议。