假如我们想要调用远程的一个方法或函数并等待执行结果,也就是我们通常说的远程过程调用(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. 利用NET HUNTER建立一个自动文件下载的网络接入点

    免责声明:本文旨在分享技术进行安全学习,禁止非法利用. 本文中我将完整的阐述如何通过建立一个非常邪恶的网络接入点来使得用户进行自动文件下载.整个过程中我将使用 Nexus 9 来运行Kali NetH ...

  2. python第四课——运算符

    一.python中的运算符: 什么是运算符? 就是计算机语言中用来参与运算的符号!! 1.算数运算符: 符号:+ - * / %(取余,取模) //(取整) **(开方) 2.比较运算符: 特点:比较 ...

  3. php四种基础排序算法的运行时间比较!

    /** * php四种基础排序算法的运行时间比较 * @authors Jesse (jesse152@163.com) * @date 2016-08-11 07:12:14 */ //冒泡排序法 ...

  4. 1066. [SCOI2007]蜥蜴【最大流】

    Description 在一个r行c列的网格地图中有一些高度不同的石柱,一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥蜴逃 到边界外. 每行每列中相邻石柱的距离为1,蜥蜴的跳跃距离是d,即蜥蜴可以跳到 ...

  5. Core WebAPI 入门

    官方文档地址 https://docs.microsoft.com/zh-cn/aspnet/?view=aspnetcore-2.2#pivot=core 使用 ASP.NET Core 构建 We ...

  6. layui弹出层之应用实例讲解

    从酒店管理系统到智能门锁及其现在的资源共享平台,layui框架,我们团队用的比较多的就是这个layui弹出层. layui弹出层,除了页面iframe层我们比较常用还有就是表单校验和其他相关的友好提示 ...

  7. 关于lora标配SPDT大功率射频开关

    SPDT大功率的UltraCMOS ™DC - 3.0 GHz射频开关              PE4259的UltraCMOS ™射频开关被设计为覆盖广泛的,通过3000兆赫从近DC应用.这种反射 ...

  8. LeetCode872. Leaf-Similar Trees

    自己的代码: # Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = ...

  9. 澄清以及半AOer的日常

    我是不是应该澄清什么事情-- 首先--我这个傻狗退役了--指的是退本赛季而不是本奥赛-- 其次--我喜欢天文是真的喜欢--但是至于为什么又滚回来OI了--大概是因为本校只对所谓"五大学科奥赛 ...

  10. vue.js数据绑定

      语法         插值             双大括号:{{text}} {{*text}}之渲染第一次 {{{html}}}        表达式(各种数值,变量,运算符的综合体)     ...