假如我们想要调用远程的一个方法或函数并等待执行结果,也就是我们通常说的远程过程调用(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. 【错误记录】flask mysql 死锁

    最近使用flask-sqlalchemy时,进行测试的时候发现日志中打印出了MySql死锁错误,查看Mysql日志发现是因为有俩条sql出现了死锁: Deadlock found when tryin ...

  2. 【原创】python requests 库底层Sockets处于close_wait状态

    以前对于Requests库只是简单是使用,在现在公司的后台中,有多个接口是直接使用requests.get .post之类的方法来做的,进行过一段时间的压力测试,发现性能低的可怜,且linux服务器有 ...

  3. debian 7 终端上无法调出输出法

    debian 7 终端konsole上无法调出输出法,无法输入汉字的问题解决方案, export GTK_IM_MODULE=fcitxexport QT_IM_MODULE=fcitxexport ...

  4. log4jnet不记录日志的问题解决

    背景:dll文件从别人项目里复制过来的,配置文件是从别的项目里配置过来的.然后就是不写日志. 最后经过搜索原项目,发现需要在AssemblyInfo.cs文件里加一段话, [assembly: log ...

  5. 如何动态调用 C 函数

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

  6. List集合的子类ArrayList和LinkedList

    一: 我们常用对集合的操作,查询.增删等操作. 由于集合的存储的方式的不同,导致有些集合查询快但是增删慢.有些集合增删快.但是查询慢. ArrayList:由于ArrayList存储的方式为数组形式. ...

  7. P1470 最长前缀 Longest Prefix

    题目描述 在生物学中,一些生物的结构是用包含其要素的大写字母序列来表示的.生物学家对于把长的序列分解成较短的序列(即元素)很感兴趣. 如果一个集合 P 中的元素可以通过串联(元素可以重复使用,相当于 ...

  8. Html+Css实现梯形选项卡

    1,先看一下效果图 2,梯形通过定位和设置Border来实现的,平行四边形通过旋转来实现的. 3,代码如下 (1)HTML代码 <html lang="en" xmlns=& ...

  9. Kafka系列文章

    Kafka系列文章 Kafka设计解析(一)- Kafka背景及架构介绍 Kafka设计解析(二)- Kafka High Availability (上) Kafka设计解析(三)- Kafka H ...

  10. Visual Studio 2015 正式版镜像下载(含专业版/企业版KEY)

    Visual Studio Community 2015简体中文版(社区版,针对个人免费): 在线安装exe:http://download.microsoft.com/download/B/4/8/ ...