假如我们想要调用远程的一个方法或函数并等待执行结果,也就是我们通常说的远程过程调用(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. 鲜为人知的 Python 语法

    所有人(好吧,不是所有人)都知道 python 是一门用途广泛.易读.而且容易入门的编程语言.   但同时 python 语法也允许我们做一些很奇怪的事情.   使用 lambda 表达式重写多行函数 ...

  2. 【洛谷】【线段树+位运算】P2574 XOR的艺术

    [题目描述:] AKN觉得第一题太水了,不屑于写第一题,所以他又玩起了新的游戏.在游戏中,他发现,这个游戏的伤害计算有一个规律,规律如下 1. 拥有一个伤害串为长度为n的01串. 2. 给定一个范围[ ...

  3. Day18 (二)反射

    反射机制是什么 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java ...

  4. BigDecimalUtil 工具类

    一.为什么要用BigDecimal? 涉及到加减乘除,用int,double 会出现数据丢失,这个时候就要用BigDecimal. 注意:在new BigDecimal(Double.toString ...

  5. GeeTest 极验验证

    前台Html页面 <script src="http://libs.baidu.com/jquery/1.9.0/jquery.js"></script> ...

  6. Parameter server(参数服务器)

    特殊点在于参数服务器是节点存储参数的地方. 用于配置参数, 全局共享参数. 参数服务器使用互联网传输, 在节点管理器中运行, 实现整个通信过程. 1.命令行维护 load&&dump文 ...

  7. c++—— 函数重载(Overroad)

    5 函数重载(Overroad) 函数重载概念 1 函数重载概念 函数重载(Function Overload) 用同一个函数名定义不同的函数 当函数名和不同的参数搭配时函数的含义不同 2 函数重载的 ...

  8. html中radio、checkbox选中状态研究

    我们在web页面开发中经常需要让单选框.复选框进行选中或者不选中的操作, 我们可以在元素中添加checked属性 或者添加checked="checked" 都可以让某个选项默认选 ...

  9. RabbitMQ如何保证发送端消息的可靠投递-发生镜像队列发生故障转移时

    上一篇最后提到了mandatory这个参数,对于设置mandatory参数个人感觉还是很重要的,尤其在RabbitMQ镜像队列发生故障转移时. 模拟个测试环境如下: 首先在集群队列中增加两个镜像队列的 ...

  10. 【vue知识点】1)vue生命周期

    [vue知识点]2)vue登录认证