Distributed RPC相关

什么是RPC

RPC是指远程过程调用。

例如分布式中的2台服务器是部署在不同的地方,如何使服务器A上的应用调用服务器B上提供的方法或函数就是RPC的一部分内容。

这2台服务器的内存不共享、资源不共享,所以我们必须通过网络来表达调用的语义和传递调用需要的数据等信息。

image-20210410190010334

不要将它想的太复杂,考虑好以下这些点你完全可以做到RPC:

  • 调用者通过网络告诉服务者,我要调用谁,我是谁,并且对参数进行传递
  • 服务者收到消息后进行调用,并且将返回结果通过网络发送给调用者
  • 由于可能存在多个调用者,所以服务者需要开辟2个通道,一个用于接收调用者的请求,一个用于响应调用者的请求

Python中RPC实现

RabbitMQ实现RPC

流程图如下:

image-20210410194951776

  1. 生产者(也称 RPC 客户端)发送一条带有标签(消息ID(correlation_id)+ 回调队列名称)的消息到发送队列;
  2. 消费者(也称 RPC 服务端)从发送队列获取消息并处理业务,解析标签的信息将业务结果发送到指定的回调队列;
  3. 生产者(也称 RPC 客户端)从回调队列中根据标签的信息(检查correlationId 属性,如果与request中匹配)获取发送消息的返回结果。

可能你看到的不是很清楚,我再画一张原创的图,通过这张图看下面的代码你就一目了然了:

image-20210410192353105

实现代码如下所示,摘自官网。

RPC服务端代码:

#! /usr/local/bin/python3
# -*- coding:utf-8 -*-

import pika

credentials = pika.PlainCredentials("username", "password")
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost', credentials=credentials))
channel = connection.channel()

# rpc_queue是专门供RPC客户端发送调用请求的队列
channel.queue_declare(queue='rpc_queue')


def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)


def on_request(ch, method, props, body):
    # props.reply_to就是回复消息的队列
    # props.correlation_id是RPC客户端的编号

    n = int(body)

    print(" [.] fib(%s)" % n)
    response = fib(n)

    ch.basic_publish(exchange='',
                     routing_key=props.reply_to,
                     properties=pika.BasicProperties(correlation_id= \
                                                         props.correlation_id),
                     body=str(response))
    ch.basic_ack(delivery_tag=method.delivery_tag)


channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='rpc_queue', on_message_callback=on_request)

print(" [x] Awaiting RPC requests")
channel.start_consuming()

RPC客户端代码:

#! /usr/local/bin/python3
# -*- coding:utf-8 -*-

import pika
import uuid


class FibonacciRpcClient(object):

    def __init__(self):
        """负责链接处理"""

        self.credentials = pika.PlainCredentials("username", "password")
        self.connection = pika.BlockingConnection(
            pika.ConnectionParameters('localhost', credentials=self.credentials))
        self.channel = self.connection.channel()

        result = self.channel.queue_declare(queue='', exclusive=True)

        # 这个队列是专门用于收到RPC服务端的返回结果的
        self.callback_queue = result.method.queue

        self.channel.basic_consume(
            queue=self.callback_queue,
            on_message_callback=self.on_response,
            auto_ack=True)

    def on_response(self, ch, method, props, body):
        """回调函数"""

        if self.corr_id == props.correlation_id:
            self.response = body

    def call(self, n):
        """负责调用"""

        self.response = None

        # 记录当前调用者的id
        self.corr_id = str(uuid.uuid4())

        # 发送调用,通知RPC服务端,返回的结果存放在callback_queue中
        # 并且还要携带上corr_id,这是因为可能有多个RPC客户端,自己的拿自己的调用结果即可
        self.channel.basic_publish(
            exchange='',
            routing_key='rpc_queue',
            properties=pika.BasicProperties(
                reply_to=self.callback_queue,
                correlation_id=self.corr_id,
            ),
            body=str(n))
        while self.response is None:
            self.connection.process_data_events()
        return int(self.response)


fibonacci_rpc = FibonacciRpcClient()

# 外界看上去,就像调用本地的call()函数一样
print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30)
print(" [.] Got %r" % response)

SimpleXMLRPCServer

SimpleXMLRPCServer框架是Python内置库。

相较于RabbitMQ,它的实现更为简单,依赖于HTTP协议进行通信,我记得好像是通过XML文档传递信息:

RPC服务端代码如下:

#! /usr/local/bin/python3
# -*- coding:utf-8 -*-

from xmlrpc.server import SimpleXMLRPCServer


class RPCServer(object):

    def __init__(self):
        super(RPCServer, self).__init__()
        print(self)
        self.send_data = {'server:' + str(i): i for i in range(100)}
        self.recv_data = None

    def getObj(self):
        print('get data')
        return self.send_data

    def sendObj(self, data):
        print('send data')
        self.recv_data = data
        print(self.recv_data)


# SimpleXMLRPCServer
server = SimpleXMLRPCServer(('localhost', 4242), allow_none=True)
server.register_introspection_functions()
server.register_instance(RPCServer())
server.serve_forever()

RPC客户端代码如下:

#! /usr/local/bin/python3
# -*- coding:utf-8 -*-

import time
from xmlrpc.client import ServerProxy


# SimpleXMLRPCServer
def xmlrpc_client():
    print('xmlrpc client')
    c = ServerProxy('http://localhost:4242')
    data = {'client:' + str(i): i for i in range(100)}
    start = time.clock()
    for i in range(50):
        a = c.getObj()
        print(a)
    for i in range(50):
        c.sendObj(data)
    print('xmlrpc total time %s' % (time.clock() - start))


if __name__ == '__main__':
    xmlrpc_client()

ZeroRPC

ZeroRPC框架是Python的第三方库。

底层是通过TCP协议实现,相较于SimpleXMLRPCServer更加迅速,因为采用TCP协议通信,故可以使用JSON格式进行数据交互,这比XML轻量级的多。

RPC服务端代码:

#! /usr/local/bin/python3
# -*- coding:utf-8 -*-

import zerorpc


class RPCServer(object):

    def __init__(self):
        super(RPCServer, self).__init__()
        print(self)
        self.send_data = {'server:' + str(i): i for i in range(100)}
        self.recv_data = None

    def getObj(self):
        print('get data')
        return self.send_data

    def sendObj(self, data):
        print('send data')
        self.recv_data = data
        print(self.recv_data)


# zerorpc
s = zerorpc.Server(RPCServer())
s.bind('tcp://0.0.0.0:4243')
s.run()

RPC客户端代码如下:

#! /usr/local/bin/python3
# -*- coding:utf-8 -*-

import zerorpc
import time


# zerorpc
def zerorpc_client():
    print('zerorpc client')
    c = zerorpc.Client()
    c.connect('tcp://127.0.0.1:4243')
    data = {'client:' + str(i): i for i in range(100)}
    start = time.clock()
    for i in range(500):
        a = c.getObj()
        print(a)
    for i in range(500):
        c.sendObj(data)

    print('total time %s' % (time.clock() - start))


if __name__ == '__main__':
    zerorpc_client()

常见的RPC框架

上面的RPC介绍除了RabbitMQ,其他的2个都只能在Python中使用。

即RPC客户端与RPC服务端都必须使用Python语言,而下面的RPC框架则五花八门:

功能 Hessian Montan rpcx gRPC Thrift Dubbo Dubbox Spring Cloud
开发语言 跨语言 Java Go 跨语言 跨语言 Java Java Java
分布式(服务治理) × × ×
多序列化框架支持 hessian √(支持Hessian2、Json,可扩展) × 只支持protobuf) ×(thrift格式)
多种注册中心 × × ×
管理中心 × × ×
跨编程语言 ×(支持php client和C server) × × × ×
支持REST × × × × × ×
关注度
上手难度
运维成本
开源机构 Caucho Weibo Apache Google Apache Alibaba Dangdang Apache

以下是一些介绍,打了星的都是比较出名的。

有的人说Spring Cloud不是RPC框架,我也不是很了解Java,也不知道是不是这样...

  • *Spring Cloud: Spring全家桶,用起来很舒服,只有你想不到,没有它做不到。可惜因为发布的比较晚,国内还没出现比较成功的案例,大部分都是试水,不过毕竟有Spring作背书,还是比较看好。
  • *Dubbox: 相对于Dubbo支持了REST,估计是很多公司选择Dubbox的一个重要原因之一,但如果使用Dubbo的RPC调用方式,服务间仍然会存在API强依赖,各有利弊,懂的取舍吧。
  • Thrift: 如果你比较高冷,完全可以基于Thrift自己搞一套抽象的自定义框架吧。
  • Montan: 可能因为出来的比较晚,目前除了新浪微博16年初发布的貌似也没别的
  • *Hessian: 如果是初创公司或系统数量还没有超过5个,推荐选择这个,毕竟在开发速度、运维成本、上手难度等都是比较轻量、简单的,即使在以后迁移至SOA,也是无缝迁移。
  • **rpcx/gRPC: 在服务没有出现严重性能的问题下,或技术栈没有变更的情况下,可能一直不会引入,即使引入也只是小部分模块优化使用。
posted @ 2021-04-10 19:50  云崖君  阅读(66)  评论(0编辑  收藏  举报