假如我们想要调用远程的一个方法或函数并等待执行结果,也就是我们通常说的远程过程调用(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. CSS3 Transform变形理解与应用

    CSS3 Transform变形理解与应用 Transform:对元素进行变形:Transition:对元素某个属性或多个属性的变化,进行控制(时间等),类似flash的补间动画.但只有两个关键贞.开 ...

  2. 如何动态调用 C 函数

    JSPatch 支持了动态调用 C 函数,无需在编译前桥接每个要调用的 C 函数,只需要在 JS 里调用前声明下这个函数,就可以直接调用: require('JPEngine').addExtensi ...

  3. Linux - 常用归档、压缩命令

    1. ncompress compress命令是一个相当古老的 unix 档案压缩指令,压缩后的文件会加上一个 .Z 的后缀名,默认不会保留源文件.compress只能对文件进行压缩,若要压缩目录,先 ...

  4. Hive学习之路 (十五)Hive分析窗口函数(三) CUME_DIST和PERCENT_RANK

    这两个序列分析函数不是很常用,这里也练习一下. 数据准备 数据格式 cookie3.txt d1,user1, d1,user2, d1,user3, d2,user4, d2,user5, 创建表 ...

  5. HBase学习之路 (八)HBase大牛博客

    主要是记录一下链接 http://hbasefly.com

  6. 随手练——HDU 5015 矩阵快速幂

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5015 看到这个限时,我就知道这题不简单~~矩阵快速幂,找递推关系 我们假设第一列为: 23 a1 a2 ...

  7. php后台+前端开发过程整理

    一.PHP后台从数据库中获取数据 1. 建立数据库连接: //在本项目中封装了数据库的各种操作 $dbConn = $this->_createMysqlConn(); 2. 执行sql语句 $ ...

  8. java基础 三 概念和java程序的结构.

    一.java的一些概念: JRE(java  runtime environment):java程序运行环境,如果要运行java程序,需要jre支持.jre里包含jvm JDK(java  devel ...

  9. P1414 又是毕业季II

    题目描述 彩排了一次,老师不太满意.当然啦,取每位同学的号数来找最大公约数显然不太合理.于是老师给每位同学评了一个能力值.于是现在问题变为,从n个学生中挑出k个人使得他们的默契程度(即能力值的最大公约 ...

  10. 1.1《想成为黑客,不知道这些命令行可不行》(Learn Enough Command Line to Be Dangerous)——运行终端

    终端是个允许我们运行命令行的程序,运行命令前,先打开它.在MacOS系统上,可以使用macOS应用 Spotlight来打开终端窗口,Spotlight也有其他两种方式触发,一种是键入⌘␣(comma ...