RPC開發模式


隨着企業的發展,我們的服務架構也變得龐大、復雜,在不同內部功能模塊之間像調用函數一樣進行數據通信,架構演變成微服務架構是一個不錯的解決方案。

微服務這種分布式的架構如何實現不同服務、不同編程語言、不同進程之間的簡單、高效通信? 

微服務除了基於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()
http實現遠程調用demo服務端

客戶端

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))
RPC客戶端Demo

 

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()
RPC服務端

--------------

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))
RPC客戶端

 

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端
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))
rpc-client端

 

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()
rpc-server

--------------

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))
rpc-clien

流式調用

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()
server端

--------

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)
client端

 

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適用於提供對外服務。

 

 

 

 

 

 

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM