什么是rpc
RPC(Remote Procedure Call) 远程过程调用,简单理解就是一个节点请求另一个节点提供服务。
对应rpc的是本地过程调用,函数调用是最常见测本地过程调用。
本地过程调用:
将本地过程调用变为远程过程调用,例如原本本地函数放到另外一台服务器上执行,会面临各种各样的问题。
- Call的id映射
- 序列化和反序列化
- 网络传输
1.CallID映射。我们怎么告诉远程机器我们要调用add,而不是sub或者Foo呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用add,编译器就自动帮我们调用它相应的函数指针。但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个{函数<-->Call ID)的对应表。两者的表不一定需要完全相同,但相同的函数对应的CallID必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的CallID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
2.序列化和反序列化。客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。 这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
3.网络传输。远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输。因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端,只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了 HTTP2。Java的Netty也属于这层的东西。
?不同语言、不同服务器的数据传输问题需要解决将内存中对象变为网络传输对象的问题,下面的步骤是基本的通信处理逻辑,其中最重要的是序列化与反序列化。
- 建立连接requests, socket
- 将本地数据序列化
- 发送序列化后的数据
- 等待等对方返回序列化结果,然后反序列化为本地可处理的对象格式
- 继续解析进行业务逻辑处理。
提到系列化与反序列化大家第一时间想到的应该是json,但是json的性能较低。在网络传输这一步, http2.0相比于http1可以保持长链接,而且向下兼容,因此http2.0可以用于远程数据的网络传输。
rpc、http以及restful之间的区别?
rpc和http
上一节中讲解过要解决rpc的调用问题,那么我们得解决两个非常重要的问题:
- 序列化和反序列化
- 网络传输协议(http还是tcp协议)
所以这里大家应该能看到了http本身属于网络传输协议的一种,rpc要实现目的必须要依赖网络传输协议,所以有同学会问了:网络协议http可以传输,我们直接基于tcp协议直接链接不也可以达到网络传输的效果吗? 是的,确实是这样,所以这里我们可以得出结论了:http协议只是我们实现rpc框架的一种选择而已,你可以选择也可以不选择,所以rpc和http之间不是竞争关系。
接下来看看rpc和restful的关系
python使用httpserver实现rpc
我们使用python完成一个web服务器,b并手写一个客户端完成访问,体验rpc的功能。
server端程序示例:
# _*_ coding:utf-8 _*_
__author__ = 'wulian'
__date__ = '2022/4/17 0017 17:09'
# 手写一个http服务器
import json
from urllib.parse import urlparse, parse_qsl
from http.server import HTTPServer, BaseHTTPRequestHandler
host = ('', 8003)
class AddHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed_url = urlparse(self.path)
qs = dict(parse_qsl(parsed_url.query))
a = int(qs.get("a", 0))
b = int(qs.get("b", 0))
self.send_response(200)
self.send_header("content_type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({ # json.dumps序列化
"result": a + b
}).encode("utf-8"))
if __name__ == '__main__':
sever = HTTPServer(host, AddHandler)
print("启动服务器")
sever.serve_forever()
client端程序示例:
# _*_ coding:utf-8 _*_
__author__ = 'wulian'
__date__ = '2022/4/17 0017 17:31'
# 模拟客户端
import json
import requests
# # 写法一:调用体验差
# rps = requests.get("http://127.0.0.1:8003/?a=1&b=2")
# print(rps.text)
# 写法二:进行封装,调用体验好
class Client:
def __init__(self, url):
self.url = url
def add(self, a, b):
rsp = requests.get(f"{self.url}/?a={a}&b={b}")
return json.loads(rsp.text).get("result", 0)
client = Client("http://127.0.0.1:8003")
print(client.add(1, 2))
print(client.add(5, 3))
运行结果,启动server端:
??client端1:
?client端2:
由上面的程序可以看见,不同 节点间的call id是通过url映射的;
RPC开发要素分析
rpc开发的四大要素 RPC技术在架构设计上有四部分组成,分别是:客户端、客户端存根、服务端、服务端存根。
- **客户端(Client):**服务调用发起方,也称为服务消费者。
- **客户端存根(Client Stub):**该程序运行在客户端所在的计算机机器上,主要用来存储要调用的服务器的地址。另外,该程序还负责将客户端请求远端服务器程序的数据信息打包成数据包,通过网络发送给服务端Stub程序;其次,还要接收服务端Stub程序发送的调用结果数据包,并解析返回给客户端。
- **服务端(Server):**远端的计算机机器上运行的程序,其中有客户端要调用的方法。
- **服务端存根(Server Stub):**接收客户Stub程序通过网络发送的请求消息数据包,并调用服务端中真正的程序功能方法,完成功能调用;其次,将服务端执行调用的结果进行数据处理打包发送给客户端Stub程序。
python中基于xml的rpc库?
python提供了基于xml的rpc库,相比于使用httpserver实现rpc,基于xml的rpc只需要关注业务逻辑,不需要关注数据解析。
服务端程序示例:
# _*_ coding:utf-8 _*_
__author__ = 'wulian'
__date__ = '2022/4/18 0018 9:05'
from xmlrpc.server import SimpleXMLRPCServer
class Calculater:
def add(self, x, y):
return x+y
def multiply(self, x, y):
return x*y
def sub(self, x, y):
return abs(x-y)
def div(self, x, y):
return x/y
obj = Calculater()
server = SimpleXMLRPCServer(("localhost", 8080))
# 将实例注册给rpc server
server.register_instance(obj)
print("Listening on port 8080")
server.serve_forever()
客户端程序示例:
# _*_ coding:utf-8 _*_
__author__ = 'wulian'
__date__ = '2022/4/18 0018 9:03'
from xmlrpc import client
server = client.ServerProxy("http://localhost:8080")
print(server.add(2, 3))
运行结果:
? ? ? ? 服务器端
?????????客户端
python中基于json的rpc库??
SimpleXMLRPCServer是基于xml-rpc实现的远程调用,上面我们也提到除了xml-rpc之外,还有json-rpc协议。 那python如何实现基于json-rpc协议呢? 答案是很多,很多web框架其自身都自己实现了json-rpc,但我们要独立这些框架之外,要寻求一种较为干净的解决方案,我们使用isonrpclib。
下载地址:
https://github.com/tcalmant/jsonrpclibhttps://github.com/tcalmant/jsonrpclib
安装:
pip install jsonrpclib-pelix
服务端程序示例:
# _*_ coding:utf-8 _*_
__author__ = 'wulian'
__date__ = '2022/4/18 0018 9:25'
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
# 实例化server
server = SimpleJSONRPCServer(("localhost", 8080))
# 将函数注册到server中
server.register_function(lambda x, y: x+y, "add")
print("Listening on port 8080")
# 启动server
server.serve_forever()
客户端程序示例:
# _*_ coding:utf-8 _*_
__author__ = 'wulian'
__date__ = '2022/4/18 0018 9:25'
import jsonrpclib
server = jsonrpclib.ServerProxy("http://localhost:8080")
print(server.add(2, 3))
运行结果:
服务端
客户端
?上面的示例只是展示除了json的rpc功能,并没有展示出相对于SimpleXMLRPCServer的优势。实际上jsonrpclib有如下优势:
- 提供了ThreadPoo的l多线程处理能力。
- 自定义http请求的header
- ?JSON 对象比xml对象要小得多,更易解析
服务端程序示例:
# _*_ coding:utf-8 _*_
__author__ = 'wulian'
__date__ = '2022/4/18 0018 10:20'
from jsonrpclib.SimpleJSONRPCServer import PooledJSONRPCServer
from jsonrpclib.threadpool import ThreadPool
import time
def add(a, b):
time.sleep(2) # 如果不是并发的,客户端每调用一次需要等待2s
return a+b
# Setup the notification and request pools
nofif_pool = ThreadPool(max_threads=10, min_threads=0)
request_pool = ThreadPool(max_threads=50, min_threads=10)
# Don't forget to start them
nofif_pool.start()
request_pool.start()
# Setup the server
server = PooledJSONRPCServer(('localhost', 8080), thread_pool=request_pool)
server.set_notification_pool(nofif_pool)
# Register methods
server.register_function(add)
try:
server.serve_forever()
finally:
# Stop the thread pools (let threads finish their current task)
request_pool.stop()
nofif_pool.stop()
server.set_notification_pool(None)
客户端程序示例:
# _*_ coding:utf-8 _*_
__author__ = 'wulian'
__date__ = '2022/4/18 0018 10:21'
import jsonrpclib
import threading
def request():
server = jsonrpclib.ServerProxy("http://localhost:8080")
print(server.add(2, 3))
for i in range(10):
thread = threading.Thread(target=request)
thread.start()
import time
time.sleep(10) # 防止主线程退出
运行结果:
服务端
客户端:因为并发了 所以有的结果打在一行
微服务架构应该考虑的问题
?作为一个微服务架构师,我们除了rpc问题以外,我们选择一个rpc框架时候还需要考虑如下问题:
- 超时机制--重试
- 限流 处于长期的可用状态--高可用
- 解耦
- 负载均衡 微服务-分布式应用的一种具体体现
- json-rpc是否满足上述要求
- 序列化和反序列化数据压缩是否高效
- rpc框架是否支持多语言
- 框架生态是否完善
|