RabbitMQ之RPC实现

什么是RPC?

RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

为什么RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯。由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用,

RPC的协议有很多,比如最早的CORBA,Java RMI,Web Service的RPC风格,Hessian,Thrift,甚至Rest API。


RabbitMQ怎么实现RPC调用?

Callback Queue

一般在RabbitMQ中做RPC是很简单的。客户端发送请求消息,服务器回复响应的消息。为了接受响应的消息,我们需要在请求消息中发送一个回调队列。可以使用默认的队列(which is exclusive in the java client.):

callbackQueueName = channel.queueDeclare().getQueue();
BasicProperties props = new BasicProperties.Builder().replyTo(callbackQueueName).build();
channel.basicPublish("", "rpc_queue",props,message.getBytes());
// then code to read a response message from the callback_queue...

Message properties

AMQP协议为消息预定义了一组14个属性。

private String contentType;
private String contentEncoding;
private Map<String,Object> headers;
private Integer deliveryMode;
private Integer priority;
private String correlationId;
private String replyTo;
private String expiration;
private String messageId;
private Date timestamp;
private String type;
private String userId;
private String appId;
private String clusterId;

大部分的属性是很少使用的。除了以下几种(其余有兴趣可以自行查看):

  • deliveryMode: 标记消息传递模式,2-消息持久化,其他值-瞬态。
  • contentType:内容类型,用于描述编码的mime-type. 例如经常为该属性设置JSON编码。
  • replyTo:应答,通用的回调队列名称,
  • correlationId:关联ID,方便RPC相应与请求关联。

Correlation Id

在上述方法中为每个RPC请求创建一个回调队列。这是很低效的。幸运的是,一个解决方案:可以为每个客户端创建一个单一的回调队列。

新的问题被提出,队列收到一条回复消息,但是不清楚是那条请求的回复。这是就需要使用correlationId属性了。我们要为每个请求设置唯一的值。然后,在回调队列中获取消息,查看这个属性,关联response和request就是基于这个属性值的。如果我们看到一个未知的correlationId属性值的消息,可以放心的无视它——它不是我们发送的请求。

你可能问道,为什么要忽略回调队列中未知的信息,而不是当作一个失败?这是由于在服务器端竞争条件的导致的。虽然不太可能,但是如果RPC服务器在发送给我们结果后,发送请求反馈前就挂掉了,这有可能会发送未知correlationId属性值的消息。如果发生了这种情况,重启RPC服务器将会重新处理该请求。这就是为什么在客户端必须很好的处理重复响应,RPC应该是幂等的。

Summary


RPC的处理流程:

  1. 当客户端启动时,创建一个匿名的回调队列。
  2. 客户端为RPC请求设置2个属性:replyTo,设置回调队列名字;correlationId,标记request。
  3. 请求被发送到rpc_queue队列中。
  4. RPC服务器端监听rpc_queue队列中的请求,当请求到来时,服务器端会处理并且把带有结果的消息发送给客户端。接收的队列就是replyTo设定的回调队列。
  5. 客户端监听回调队列,当有消息时,检查correlationId属性,如果与request中匹配,那就是结果了。

Demo Code

这里采用官网的一个例子来说明,RPC客户端通过RPC调用服务器来计算斐波那契额值。
首先是服务端的代码:

public class RPCServer {
    private static final String RPC_QUEUE_NAME = "rpc_queue";

    public static void main(String args[]) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RabbitConfig.ip);
        factory.setPort(RabbitConfig.port);
        factory.setUsername(RabbitConfig.username);
        factory.setPassword(RabbitConfig.password);

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(RPC_QUEUE_NAME,false,false,false,null);
        channel.basicQos(1);

        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
        System.out.println(" [x] Awaiting RPC requests");

        while(true){
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            BasicProperties props = delivery.getProperties();
            BasicProperties replyProps = new BasicProperties.Builder().correlationId(props.getCorrelationId()).build();
            String message = new String(delivery.getBody());
            int n = Integer.parseInt(message);
            System.out.println(" [.] fib("+message+")");
            String repsonse = ""+fib(n);
            channel.basicPublish("", props.getReplyTo(), replyProps, repsonse.getBytes());
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }

    private static int fib(int n) throws Exception {
        if (n == 0) return 0;
        if (n == 1) return 1;
        return fib(n-1) + fib(n-2);
    }
}

RPC客户端:

public class RPCClient {
    private Connection connection;
    private Channel channel;
    private String requestQueueName = "rpc_queue";
    private String replyQueueName;
    private QueueingConsumer consumer;

    public RPCClient() throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RabbitConfig.ip);
        factory.setPort(RabbitConfig.port);
        factory.setUsername(RabbitConfig.username);
        factory.setPassword(RabbitConfig.password);

        connection = factory.newConnection();
        channel = connection.createChannel();

        replyQueueName = channel.queueDeclare().getQueue();
        consumer = new QueueingConsumer(channel);
        channel.basicConsume(replyQueueName, true,consumer);
    }

    public String call(String message) throws IOException,
            ShutdownSignalException, ConsumerCancelledException,
            InterruptedException {
        String response = null;
        String corrId = UUID.randomUUID().toString();

        BasicProperties props = new BasicProperties.Builder()
                .correlationId(corrId)
                .replyTo(replyQueueName)
                .build();
        channel.basicPublish("", requestQueueName, props, message.getBytes());

        while(true){
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            if(delivery.getProperties().getCorrelationId().equals(corrId)){
                response = new String(delivery.getBody());
                break;
            }
        }

        return response;
    }

    public void close() throws Exception{
        connection.close();
    }

    public static void main(String args[]) throws Exception{
        RPCClient fibRpc = new RPCClient();
        System.out.println(" [x] Requesting fib(30)");
        String response = fibRpc.call("30");
        System.out.println(" [.] Got '"+response+"'");
        fibRpc.close();

    }
}

参考资料

  1. Remote procedure call (RPC)
  2. 轻松搞定RabbitMQ(七)——远程过程调用RPC
时间: 2024-08-30 16:43:48

RabbitMQ之RPC实现的相关文章

RabbitMQ消息队列(七):适用于云计算集群的远程调用(RPC)

        在云计算环境中,很多时候需要用它其他机器的计算资源,我们有可能会在接收到Message进行处理时,会把一部分计算任务分配到其他节点来完成.那么,RabbitMQ如何使用RPC呢?在本篇文章中,我们将会通过其它节点求来斐波纳契完成示例. 1. 客户端接口 Client interface         为了展示一个RPC服务是如何使用的,我们将创建一段很简单的客户端class. 它将会向外提供名字为call的函数,这个call会发送RPC请求并且阻塞知道收到RPC运算的结果.代码

【转载】低成本和高性能的MySQL云数据库的实现淘宝 MySQL

低成本和高性能的MySQL云数据库的实现 作者: 鸣嵩/曹伟(集团技术专家) 本文刊登于<程序员>杂志2012年12期上,转载请注明         UMP(Unified MySQL Platform)系统是淘宝核心系统数据库团队开发的低成本和高性能的MySQL云数据方案,关键模块采用Erlang语言实现.系统中包含了controller服务器.proxy服务器.agent服务器.API/Web服务器.日志分析服务器.信息统计服务器等组件,并且依赖于Mnesia.LVS.RabbitMQ.Z

消息中间件收录集

本篇主要整理工作中遇到的一些消息中间件的相关知识,包括Kafka, RabbitMQ, RocketMQ, ActiveMQ等,不排除收录其他消息中间件的可能. 这里会持续收录相关知识,包括安装.部署.使用示例.监控.运维.原理等. 所有新撰写的与中间件有关的文章都会收录与此,注意保存本文链接. Last Update Time: 2017-10-26 08:23 Update Content: RabbitMQ管理(5)--集群管理 通用 什么是Zero-Copy?(sendfile) 1.

浅谈微服务的来龙去脉

浅谈微服务的来龙去脉 背景介绍 微服务怎么来的 微服务是进化出来的 微服务不是银弹 作者:王清培(Plen wang) 沪江 公共业务平台 应用架构师 转载至沪江技术学院微信公众号 背景介绍 最近一段时间公共业务平台在进行大面积的重构,对原来的技术栈进行迁移,逐渐往java.go.node.js等开源.自由为主的技术体系中过度. 虽然这主要是替换技术框架,但也是我们应用系统进行重新设计.业务流程重新梳理的一个好机会,我们将利用这次机会来重构之前发现的一些问题. Martin Fowler大师<重

openstack 管理三十 - rpm 方式部署 openstack [cinder]

作用 1 cinder 需要连接 ceph 进行云硬盘存储 2 cinder 需要连接 keystone 进行用户验证 3 cinder 需要连接 rabbitmq 处理消息队列 默认下 openstack 提供卷驱动, 支持下面的块存储类型 LVM/iSCSI ThinLVM NFS NetAPP NFS Red Hat Storage (Gluster) Dell EqualLogic CEPH 配置 配置 keystone 验证 # openstack-config --set /etc/

轻松搞定RabbitMQ(七)——远程过程调用RPC

       翻译:http://www.rabbitmq.com/tutorials/tutorial-six-java.html       在第二篇博文中,我们已经了解到了如何使用工作队列来向多个消费者分散耗时任务.       但是付过我们需要在远程电脑上运行一个方法然后等待结果,该怎么办?这是不同的需求.这个模式通常叫做RPC.        本文我们将使用RabbitMQ构建一个RPC系统:一个客户端和一个可扩展的RPC服务器端.由于我们没有任何真实的耗时任务需要分配,所以我们将创建

RabbitMQ学习系列(五): RPC 远程过程调用

前面讲过一些RabbitMQ的安装和用法,也说了说RabbitMQ在一般的业务场景下如何使用.不知道的可以看我前面的博客,http://www.cnblogs.com/zhangweizhong/category/855479.html 不过,最近有朋友问我,RabbitMQ RPC 是干嘛的,有什么用. 其实,RabbitMQ RPC 就是通过消息队列(Message Queue)来实现rpc的功能,就是,客户端向服务端发送定义好的Queue消息,其中携带的消息就应该是服务端将要调用的方法的参

RabbitMQ管理(4)——应用管理

本文主要阐述应用与集群相关的一些操作管理命令,包括关闭.重置.开启服务,还有建立集群的一些信息.有关集群搭建更多的信息可以参考RabbitMQ的安装及集群搭建方法. rabbitmqctl stop [pid_file] 用于停止运行RabbitMQ的Erlang虚拟机和RabbitMQ服务应用.如果指定了pid_file,还需要等待指定进程的结束.其中pid_file是通过调用rabbitmq-server启动RabbitMQ服务时创建的,默认情况下存放于Mnesia目录中,可以通过RABBI

openstack rpc机制

一.概述: 在openstack项目中,api的调用规则: 跨项目:如nova调用keystone, glance,cinder等,使用rest api(通过相应的python-XXXclient 库) 项目内跨服务调用,使用RPC调用,通过服务提供的rpcapi.py文件,比如cinder内部,cinder-api与cinder-volume,cinder-scheduler服务之间使用RPC接口,即RabbitMQ消息: cinder系统结构图: cinder-api是cinder 服务的e