RabbitMQ入门:远程过程调用(RPC)

假如我们想要调用远程的一个方法或函数并等待执行结果,也就是我们通常说的远程过程调用(RemoteProcedureCall)。怎么办?今天我们就用RabbitMQ来实现一个简单的RPC系统:客户

大家好,又见面了,我是全栈君。

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

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

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

一、工作模型

RabbitMQ入门:远程过程调用(RPC)

  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. 运行服务端,开始等待请求RabbitMQ入门:远程过程调用(RPC)

     

  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()方法,问题就解决了。

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/120894.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 重启MySQL服务(怎么重启mysql服务)

    重启MySQL服务(怎么重启mysql服务)一、MYSQL服务我的电脑——(右键)管理——服务与应用程序——服务——MYSQL——开启(停止、重启动)二、命令行方式Windows1.点击“开始”->“运行”(快捷键Win+R)。2.启动:输入netstopmysql3.停止:输入netstartmysql提示*RedhatLinux也支持servicecommand,启动:#servicemysqldstar…

    2022年4月18日
  • ListView之多种类型Item

    ListView之多种类型Item一、概述一般而言,listview每个item的样式是一样的,但也有很多应用场景下不同位置的item需要不同的样式。拿微信举例,前者的代表作是消息列表,而后者的典型则是聊天会话界面。本文重点介绍

    2022年7月4日
  • kali漏扫软件_kali渗透web网站

    kali漏扫软件_kali渗透web网站一、漏洞扫描器漏洞扫描器通常由两个部分组成①进行扫描的引擎部分 ②包含了世界上大多数系统和软件漏洞特征的特征库比较优秀的漏洞扫描器:Rapid7Nexpose:(商用软件)适合较大的网络 TenableNessus:(商用软件)价格相当更经济一些 OpenVas:免费使用的工具,更适合个人使用商用软件使用起来比较容易,只要给出一个IP地址就能完成所有的扫描任务,但…

    2022年9月13日
  • 补码运算中的溢出_二进制补码运算溢出判断

    补码运算中的溢出_二进制补码运算溢出判断当两个以补码表示的负数相加时,会遇到两个问题。第一是两个负数的符号位相加,1+1后,本位为零,似乎负数相加变成了正数;其二是两个负数的数值部分之和,如果不向符号位进位,是不是就说明运算结果没有溢出?但不进位最终将导致两个负数相加成了正数,显然是错误的,这该怎么解释?如果两个以补码表示的负数的数值部分之和向符号位进位,会使运算结果依然为负数,那么这个运算结果是正确的吗?下面我们分析一下这个问题:

    2022年9月22日
  • 在线聊天系统源码

    在线聊天系统源码在线聊天系统:https://www.lmcjl.com/index/program/view在线聊天系统系统采用了Lumen为框架,通过Nodejs实现在线多客服对多用户的对接。配置信息:linux(centos系统或者是ubuntu系统)+php7+mysql+nodejs演示地址:https://www.lmcjl.com/index/program/view解释id…

    2022年5月14日
  • goc课程介绍_演示

    goc课程介绍_演示第一课GoC简介和演示【C/C++程序设计语言】“程序设计语言用于书写计算机程序的语言。语言的基础是一组记号和一组规则。根据规则由记号构成的记号串的总体就是语言。在程序设计语言中,这些记号串就是程序。”(这是百度百科上的程序设计语言定义)通俗的讲,程序是由一些“命令”组成,这些“命令”是一些符合指定规则的符号。根据不同的需求和规则,现今有近百种有不同的编程语言,C/C++是最流…

    2022年4月19日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号