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适用于提供对外服务。

参考

原文地址:https://www.cnblogs.com/sss4/p/14513202.html