假如我们想要调用远程的一个方法或函数并等待执行结果,也就是我们通常说的远程过程调用(Remote Procedure Call)。怎么办?

今天我们就用RabbitMQ来实现一个简单的RPC系统:客户端发送一个请求消息,服务端以一个响应消息回应。为了能够接收到响应,客户端在发送消息的同时发送一个回调队列用来告诉服务端响应消息发送到哪个队列里面。也就是说每个消息一个回调队列,在此基础上我们变下,将回调队列定义成类的属性,这个每个客户端一个队列,同一个客户端的请求共用一个队列。那么接下来有个问题,怎么知道这个队列里面的响应消息是属于哪个队列的呢?

我们会用到关联标识(correlationId),每个请求我们都会生成一个唯一的值作为correlationId,这样每次有响应消息来的时候,我们就去看correlationId来确定到底是哪个请求的响应消息,将请求和响应关联起来。如果收到一个不知道的correlationId,就可以确定不是这个客户端的请求的响应,可以直接丢弃掉。

一、工作模型

  1. 客户端发送启动后,会创建独特的回调队列。对于一个请求发送配置了两个属性的消息:一个是回调队列(图中的replay_to),一个是correlation。 这个请求会发送到rpc_queue队列,然后到达服务端处理。
  2. 服务端等待rpc_queue队列的请求。当有请求到来时,它就会开始干活并将结果通过发送消息来返回,该返回消息发送到replyTo指定的队列。
  3. 客户端将等待回调队列返回数据。当返回的消息到达时,它将检查correlation id属性。如果该属性值和请求匹配,就将响应返回给程序。

二、代码实现

接下来看代码实现:

  1. 客户端

    public class RpcClient {
    
        Connection connection = null;
    Channel channel = null;
    //回调队列:用来接收服务端的响应消息
    String queueName = ""; // 定义RpcClient
    public RpcClient() throws IOException, TimeoutException {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    connection = factory.newConnection();
    channel = connection.createChannel();
    queueName = channel.queueDeclare().getQueue();
    } // 真正的处理逻辑
    public String call(String msg) throws IOException, InterruptedException {
    final String uuid = UUID.randomUUID().toString();
    //后续,服务端根据"replyTo"来指定将返回信息写入到哪个队列
    //后续,服务端根据关联标识"correlationId"来指定返回的响应是哪个请求的
    AMQP.BasicProperties prop = new AMQP.BasicProperties().builder().replyTo(queueName).correlationId(uuid).build(); channel.basicPublish("", RpcServer.QUEUE_NAME, prop, msg.getBytes());
    final BlockingQueue<String> blockQueue = new ArrayBlockingQueue<String>(1);
    channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
    com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException { if (properties.getCorrelationId().equals(uuid)) {
    String msg = new String(body, "UTF-8"); blockQueue.offer(msg);
    System.out.println("**** rpc client reciver response :[" + msg + "]");
    }
    } }); return blockQueue.take();
    } //关闭连接
    public void close() throws IOException {
    connection.close();
    } public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
    RpcClient client = new RpcClient();
    client.call("4");
    client.close();
    }
    }

    发送请求的时候,它是生产者;接受响应的时候,它是消费者。

  2. 服务端
    public class RpcServer {
    
        //RPC队列名
    public static final String QUEUE_NAME = "rpc_queue"; //斐波那契数列,用来模拟工作任务
    public static int fib(int num) {
    if (num == 0)
    return 0;
    if (num == 1)
    return 1;
    return fib(num - 1) + fib(num - 2);
    } public static void main(String[] args) throws InterruptedException { ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = null;
    try {
    // 1.connection & channel
    connection = factory.newConnection();
    final Channel channel = connection.createChannel(); // 2.declare queue
    channel.queueDeclare(QUEUE_NAME, false, false, false, null); System.out.println("****** rpc server waiting for client request ......"); // 3.每次只接收一个消息(任务)
    channel.basicQos(1);
    //4.获取消费实例
    Consumer consumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
    byte[] body) throws IOException {
    BasicProperties prop = new BasicProperties().builder().correlationId(properties.getCorrelationId())
    .build();
    String resp = "";
    try {
    String msg = new String(body, "UTF-8");
    resp = fib(Integer.valueOf(msg)) + "";
    System.out.println("*** will response to rpc client :" + resp);
    } catch (Exception ex) {
    ex.printStackTrace();
    } finally {
    channel.basicPublish("", properties.getReplyTo(), prop, resp.getBytes());
    channel.basicAck(envelope.getDeliveryTag(), false);
    } }
    };
    // 5.消费消息(处理任务)
    channel.basicConsume(QUEUE_NAME, false, consumer);
    } catch (IOException e) {
    e.printStackTrace();
    } catch (TimeoutException e) {
    e.printStackTrace();
    }
    } }

    接受请求的时候,它是消费者;发送响应的时候,它是生产者。

  3. 运行服务端,开始等待请求
  4. 然后运行客户端,控制台log:
    服务端(多了一条打印):
    ****** rpc server waiting for client request ......
    *** will response to rpc client :3 客户端:
    **** rpc client reciver response :[3]

三、小插曲

刚开始我在写demo的时候,client中没有用到阻塞队列final BlockingQueue<String> blockQueue = new ArrayBlockingQueue<String>(1);,而是直接这样写:

@Override
public void handleDelivery(String consumerTag, Envelope envelope,
com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException { if (properties.getCorrelationId().equals(uuid)) {
String msg = new String(body, "UTF-8"); //blockQueue.offer(msg);
System.out.println("**** rpc client reciver response :[" + msg + "]");
}
}

期望能打印出结果来,但是运行后发现并没有打印:**** rpc client reciver response :[" + msg + "]的值。

原因是handleDelivery()这个方法是在子线程中运行的,这个子线程运行的时候,主线程会继续往后执行直到执行了client.close();方法而结束了。

由于主线程终止了,导致没有打印出结果。加了阻塞队列之后将主线程阻塞不执行close()方法,问题就解决了。

RabbitMQ入门:远程过程调用(RPC)的更多相关文章

  1. RabbitMQ之远程过程调用(RPC)【译】

    在第二个教程中,我们学习了如何使用工作队列在多个worker之间分配耗时的任务. 但是如果我们需要在远程计算机上运行功能并等待结果呢?嗯,这是另外一件事情,这种模式通常被称为远程过程调用(RPC). ...

  2. RabbitMQ入门(6)——远程过程调用(RPC)

    在RabbitMQ入门(2)--工作队列中,我们学习了如何使用工作队列处理在多个工作者之间分配耗时任务.如果我们需要运行远程主机上的某个方法并等待结果怎么办呢?这种模式就是常说的远程过程调用(Remo ...

  3. RabbitMQ入门教程(八):远程过程调用RPC

    原文:RabbitMQ入门教程(八):远程过程调用RPC 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.cs ...

  4. (转) RabbitMQ学习之远程过程调用(RPC)(java)

    http://blog.csdn.net/zhu_tianwei/article/details/40887885 在一般使用RabbitMQ做RPC很容易.客户端发送一个请求消息然后服务器回复一个响 ...

  5. RabbitMQ入门:总结

    随着上一篇博文的发布,RabbitMQ的基础内容我也学习完了,RabbitMQ入门系列的博客跟着收官了,以后有机会的话再写一些在实战中的应用分享,多谢大家一直以来的支持和认可. RabbitMQ入门系 ...

  6. .NET 环境中使用RabbitMQ RabbitMQ与Redis队列对比 RabbitMQ入门与使用篇

    .NET 环境中使用RabbitMQ   在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的 ...

  7. 转载RabbitMQ入门(6)--远程调用

    远程过程调用(RPC) (使用Java客户端) 在指南的第二部分,我们学习了如何使用工作队列将耗时的任务分布到多个工作者中. 但是假如我们需要调用远端计算机的函数,等待结果呢?好吧,这又是另一个故事了 ...

  8. RabbitMq入门以及使用教程

    祭出原帖:https://blog.csdn.net/lyhkmm/article/details/78772919 原文转载:http://blog.csdn.net/whycold/article ...

  9. RabbitMQ入门-理论

    目录 RabbitMQ简介 RabbitMQ原理简介 RabbitMQ安装 .NET Core 使用 RabbitMQ Hello World 工作队列 扇型交换机 直连交换机 主题交换机 远程过程调 ...

  10. 消息中间件 RabbitMQ 入门篇

    消息中间件 RabbitMQ 入门篇 五月君 K8S中文社区 今天   作者:五月君,来源:Nodejs技术栈 从不浪费时间的人,没有工夫抱怨时间不够.—— 杰弗逊 RabbitMQ 是一套开源(MP ...

随机推荐

  1. ubuntu下以16进制形式查看class文件、反编译class文件

    参考:https://blog.csdn.net/xyh930929/article/details/83860464 1.vim HelloWorld.class 2.输入英文状态下的冒号:,然后输 ...

  2. 1864. [ZJOI2006]三色二叉树【树形DP】

    Description Input 仅有一行,不超过500000个字符,表示一个二叉树序列. Output 输出文件也只有一行,包含两个数,依次表示最多和最少有多少个点能够被染成绿色. Sample ...

  3. Ansible--01

    一.ansible是什么: 类似puppet之类的运维自动化工具 二.为什么选择ansible: 1. ansible是python语言开发的,python语言进入门槛低,方便基于pytnon对ans ...

  4. 解决Mac外接显示器经常没反应

    问题描述 最近在使用绿联扩展坞的时候,发现连接显示器时,有时插上后可以正常使用,有时插上后显示器没反应. 解决办法 经过一段时间的尝试,发现这样操作可以避免出现这种问题: 关机的时候: 从转换器拔掉H ...

  5. SSM框架之关于使用JSP作为视图展示问题解决方案

    JSP作为视图层展示数据,已经有很长一段时间了,不管是在校学习还是企业工作,总会或多或少接触这个.特别是对于一些传统中小型或者一些几年前的企业而言,有很多使用JSP作为视图展示层. JSP本质是就是S ...

  6. Windows下基于Python3安装Ipython Notebook(即Jupyter)。python –m pip install XXX

    1.安装Python3.x,注意修改环境变量path(追加上python安装目录,如:D:\Program Files\Python\Python36-32) 2.查看当前安装的第三方包:python ...

  7. 关于VS2010 C#使用DirectX的问题[英]

    转载的,就不翻译了…微软把精力放到xna去了.所以推荐大家用XNA,如果非要用托管的DirectX也可以,只不过版本一直是2006年的了. 具体方法: 安装SDK之后 他默认的位置在C:\WINDOW ...

  8. 轻松构建 基于docker的 redis 集群

    下面跟着我来 一步一步构建redis 集群吧. 集群的目录结构见GitHub源码(文章末尾) 1,安装docker环境,根据自身的操作系统,google下即可. 2,我们在服务器上,搭建所需目录结构. ...

  9. 分子量 (Molar Mass,ACM/ICPC Seoul 2005,UVa1586)

    习题 3-3 分子量 (Molar Mass,ACM/ICPC Seoul 2005,UVa1586) 给出一种物质的分子式(不带括号),求分子量.本题中的分子式只包含4种原子,分别为C,H,O,N, ...

  10. 马尔可夫毯(Markov blanket)

    马尔可夫毯(Markov blanket) 马尔科夫毯,是满足如下特性的一个最小特征子集:一个特征在其马尔科夫毯条件下,与特征域中所有其他特征条件独立.设特征T的马尔科夫毯为MB(T),则上述可表示为 ...