隨着企業的發展,我們的服務架構也變得龐大、復雜,在不同內部功能模塊之間像調用函數一樣進行數據通信,架構演變成微服務架構是一個不錯的解決方案。
微服務這種分布式的架構如何實現不同服務、不同編程語言、不同進程之間的簡單、高效通信?
微服務除了基於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適用於提供對外服務。