随着企业的发展,我们的服务架构也变得庞大、复杂,在不同内部功能模块之间像调用函数一样进行数据通信,架构演变成微服务架构是一个不错的解决方案。
微服务这种分布式的架构如何实现不同服务、不同编程语言、不同进程之间的简单、高效通信?
微服务除了基于HTTP协议进行API、消息队列进行数据交互,也可以统一使用gRPC协议的Protobuf数据格式进行更加简单、高效的数据交互。
使用RPC协议和HTTP协议实现微服务的数据交互区别是什么?如何完成Golang、Python...数据交互?
什么是RPC?
RPC是Remote Procedure Call的缩写,就是像调用本地函数一样调用远程服务器上运行程序的函数,并返回调用结果。
说起来很简单做起来难,把原来本地的函数放到另一台服务器上去进行远程调用,会面临以下几大问题。
Call的id映射:服务端提供了很多可调用服务就跟Django的视图函数,如何保证客户端发送远程调用时可以区别不同可调用服务、并可以返回结果是关键。
序列化和反序列化:数据进行序列化/反序列化的速度,和二进制数据的大小会影响网络传输速度。
网络传输:既然远程调用肯定要通过网络协议进行传输,是采用TCP还HTTP网络传输协议呢?
RPC框架的组成
一个基本RPC框架由4部分组成,分别是:客户端、客户端存根、服务端、服务端存根
客户端(Client):服务调用的发起方,也称为消费者。
客户端存根(ClientStub):
该程序运行在客户端所在的计算机上,主要用来存储 要调用服务器地址,对客户端发送的数据进行序列化、建立网络连接之后发送数据包给Server端的存根程序。
接收Server端存根程序响应的消息,对消息进行反序列化。
服务端(Server):提供客户端想要调用的函数
服务端存根(ServerStub):接收客户端的消息并反序列化,对server端响应的消息进行序列化并响应给Client端
总体来讲客户端和服务端的Stub在底层帮助我们实现了Call ID的映射、数据的序列化和反序列化、网络传输
这样RPC客户端和RPC服务端 只需要专注于服务端和客户端的业务逻辑层。
RPC和HTTP的区别?
RPC是一种解决客户端和服务端之间数据通信的方案
HTTP协议可以实现RPC即在服务端和客户端之间完成远程过程调用,但是HTTP仅仅是实现RPC的方式之一并非唯一方式。
从传输性能角度来说HTTP协议并不是实现RPC的最优方案
但是从客户端兼容性角度来说支持HTTP协议的客户端非常广泛,尤其是浏览器天然支持HTTP协议,HTTP客户端的开发成本也比较低。
import json from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.parse import urlparse, parse_qsl host = ("", 8003) class AddHandler(BaseHTTPRequestHandler): def do_GET(self): # 获取当前访问URL current_url = urlparse(self.path) # 获取URL携带的参数 query_args = dict(parse_qsl(current_url.query)) print(query_args) arg1, arg2 = int(query_args.get("arg1", 1)), int(query_args.get("arg2", 1)) self.send_response(200) self.send_header("content-type", "application/json") self.end_headers() self.wfile.write(json.dumps({"result":arg1 + arg2},ensure_ascii=False).encode("utf-8")) if __name__ == '__main__': server = HTTPServer(host, AddHandler) print("启动服务器") server.serve_forever()
客户端
import json import requests #自己实现1个rpc框架 class ClientStub(): def __init__(self, url): self.url = url def add(self, arg1, arg2): remote_call_result = requests.get(url="%s?arg1=%s&arg2=%s" % (self.url, arg1, arg2)) remote_call_result = json.loads(remote_call_result.text).get("result", 0) return remote_call_result # http的调用 # 1.每个函数调用我们都得记住url的地址,参数如何传递?返回数据如何解析? client = ClientStub(url="http://127.0.0.1:8003/") print(client.add(2, 2)) print(client.add(22, 33)) print(client.add(33, 80))
Python之RPC开发模式
上面我们提到一个基本RPC框架必须实现服务端客户端存根和客户端存根服务,我们才能在RPC客户端像调用函数一样去调用RPC服务端注册的函数并返回结果。
在Python中有一些RPC框架,但是它们仅仅支持在不同的Python进程间通信。无法向gRPC一样支持
所以在学习gRPC之前先使用Python体验一下 RPC开发模式和之前Web框架开发模式的区别。
XMLRPC框架
Python内置了1个SimpleXMLRPCServer库,实现了RPC,基于XML数据格式完成不同进程(微服务)之间的数据交互。
from xmlrpc.server import SimpleXMLRPCServer class CalculateService(): # 服务端 加运算 def add(self, x, y): return x + y # 服务端 减运算 def subtract(self, x, y): return abs(x - y) # 服务端 乘运算 def multiply(self, x, y): return x * y # 服务端 除运算 def divide(self, x, y): return x / y obj = CalculateService() # SimpleXMLRPCServer相当于RPC服务端Stub: # 处理RPC服务端数据序列化、反序列化、数据传输到RPC客户端、处理服客户端Stub的网络请求、并把RPC服务端产生的数据响应给RPC客户端的STUC server = SimpleXMLRPCServer(("127.0.0.1", 8002)) # 只需要把我们写的Python类注册给RPC框架,我们的方法就会暴露给RPC客户端, # 这样RPC客户端就可以像调用本地函数一样调用RPC服务端的暴露的服务 server.register_instance(obj) print("远程调用服务端开启") server.serve_forever()
--------------
from xmlrpc import client #ServerProxy: # 相当于客户端Stub:负责客户端数据序列化、反序列化、和服务端Stub建立网络连接、并把RPC客户端数据发送给服务端Stus。 RPC_server=client.ServerProxy("http://127.0.0.1:8002") #在RPC客户端像调用本地函数一样调用 在RPC服务端注册的函数 print(RPC_server.add(2,5)) print(RPC_server.subtract(2,5)) print(RPC_server.multiply(2,5)) print(RPC_server.divide(2,5))
JRPC框架
jsonrpclib-pelix是基于json数据格式进行RPC的库。RPC server端支持线程池。切
安装:
pip install jsonrpclib-pelix -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
使用:
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer #简单的rpc server端 # 1.实例化1个rpc server stub server = SimpleJSONRPCServer(("127.0.0.1", 8002)) # 2.将函数注册到rpc server stub中 server.register_function(lambda x, y: x + y, "add") server.register_function(lambda x: x, "ping") # 3启动rpc server stub server.serve_forever() #线程池rpc server端
-----------------
import jsonrpclib remote_sercer=jsonrpclib.ServerProxy("http://127.0.0.1:8002") print(remote_sercer.ping("Hellow")) print(remote_sercer.add(33,33))
zerorpc框架
以上2种RPC框架,只能在2个python进程之间相互调用,如果是不同语言Node.js和Python呢?
zerorpc就支持Node.js和Python相互之间数据交互。
除此之外以上2种RPC框架的RPC Server端和RPC Client端都是通过直连的通信方式。
zerorp是基于zeroMQ消息队列 + msgpack消息序列化-比http的json数据格式更加高效的协议,来实现类似跨语言的远程调用。
在RPC Server 和 RPC Client中间增加1个消息队列,对它们进行解耦,那么RPC就会变成异步操作。
zero RPCPythonserver端使用协程提升并发效果。
安装
pip install zerorpc -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
启动zerorpc的服务端
D:\微服务\pythonStart> zerorpc --server --bind tcp://*:1234 time binding to "tcp://*:1234" serving "time"
zerorpc客户端调用服务端
D:\zhanggen\>zerorpc --client --connect tcp://127.0.0.1:1234 strftime %Y/%m/%d connecting to "tcp://127.0.0.1:1234" '2021/03/12'
一元调用
import zerorpc class HelloRPC(object): def hello(self,name): return "Hello %s"%(name) def add(self,a,b): return a+b server=zerorpc.Server(HelloRPC()) server.bind("tcp://127.0.0.1:86") server.run()
--------------
import zerorpc rpc_client=zerorpc.Client() rpc_client.connect("tcp://127.0.0.1:86") print(rpc_client.hello("张根")) print(rpc_client.add(1,1))
流式调用
tcp数据传输是流式的我们可以慢慢获取执行结果
import zerorpc import os #rpc-server 流式响应 class StreamingRPC(object): @zerorpc.stream def streaming_range(self, fr, to, step): return range(fr, to, step) @zerorpc.stream def run_cmd(self,cmd): tmp = os.popen(cmd).readlines() return tmp s = zerorpc.Server(StreamingRPC()) s.bind("tcp://0.0.0.0:4242") s.run()
--------
import zerorpc c = zerorpc.Client() c.connect("tcp://127.0.0.1:4242") flag=True while flag: cmd=input("请输入cmd: ".strip()) if cmd=="exit": flag=False else: ret=c.run_cmd(cmd) for i in ret: print(i)
Golang之RPC开发模式
总结
1.选择RPC框架要考虑的因素
在微服务架构中使用哪款RPC框架,需要考虑一下几种因素。
语言生态:RPC框架是否支持 主流编程语言
数据传输效率:考虑RPC网络传输使用的网络协议,以及数据传输格式其中包括:
- 数据格式序列化之后的大小:压缩的越小越节省带宽,在网络中的传输速度越快。
- 数据序列化和反序列化的速度:json.dups()和json.loads()的速度更快。
超时机制:客户端连接服务端超时之后重试
服务高可用性:RPC服务的高可用,尤其是RPC Server端
负载均衡:服务如何进行快速的横向扩展
2.RPC应用场景
RPC模式和之前Web框架开发模式 相比起来简单了很多。
但是我们使用基于web框架也可以达到这种效果,而且RPC开发模式限制了我们使用的客户端。
我们使用RPC进行开发的优势在于:
架构:和传统的后端服务相比我们使用RPC构架的业务会比较容易灵活扩展。
网络协议选择多样性:我们可以选择 RPC的网络传输协议,你使用web框架都是基于HTTP协议。
传输效率高:可以基于thrift实现高效的二进制传输。
负载均衡:RPC服务端有些已经支持了负载均衡,我们也不需要在搭建Nginx。
限流:如果RCP消息通过消息队列传输,我们还可以对客户端和服务端进行异步、解耦和限流。
OpenStack的架构是RPC应用场景的典型案例之一,OpenStack内部组件间通过RPC进行通信,通过 RESTfull API提供对外服务。
RPC适用于分布式架构中不同内部服务组件之间进行数据交互,HTTP适用于提供对外服务。