RabbitMQ使用教程(七):远程调用

一、远程调用说明

关于远程调用,Remote Procedure Call 简称 RPC,大家并不陌生,有时候我们需要远程的一个程序去处理数据,再把处理结果传给我们。这里我们就来实现RabbitMQ的远程调用:

客户端请求远程服务器计算斐波拉契数列和,并回传计算结果。

实现算法如下:

    //计算斐波拉契和
    private static int fib(int n) {
        if (n == 0)
            return 0;
        if (n == 1)
            return 1;
        return fib(n - 1) + fib(n - 2);
    }

我们打算将在客户端中写一个call方法,实现调用并返回结果,形式如下:

FibonacciRpcClient fibonacciRpc = new FibonacciRpcClient();
String result = fibonacciRpc.call("4");
System.out.println( "fib(4) is " + result);
远程调用也存在一些问题:如程序不知道调用的function是本地的,还是缓慢的RPC。再者,使用远程调用会给调试带来困难。故对于远程调用,RabbitMQ给出了一些建议:弄清楚方法是本地的,还是远程调用的;通过文档标记展示清楚组件间的依赖关系;能够使用异步的尽量使用异步操作。
二、Callback Queue

既然是远程调用的队列,那么我们需要回传处理结果,就需要创建用于请求远程调用且能够带出处理结果的队列,我们使用默认队列(独占的)

String callbackQueueName = channel.queueDeclare().getQueue();
BasicProperties props = new BasicProperties
                            .Builder()
                            .replyTo(callbackQueueName)
                            .build();
channel.basicPublish("", "rpc_queue", props, message.getBytes());
三、消息属性

在上面的代码中,我们发送消息时设置了一些属性,回顾在Work模式的讲解中,我们给消息设置为持久属性的代码:

channel.basicPublish("", "task_queue",MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());

由于远程调用是基于AMQP 0-9-1协议实现的,在远程调用的时候,我们同样需要设置一些AMQP 0-9-1要求的属性,一共有14个属性,不过由于大部分都不常用,我们只列举4中最常用的属性:
① deliveryMode:标记消息是持久,还是暂时消息,对应我们Work模式中所用到的。
② contentType:指定编码的MIME类型,比如要使用json数据,便可指定为:application/json
③ replyTo:通常用于指定Callback Queue的名称
④ correlationId:用于远程调用的request与response之间的关系标识

四、详解correlationId

为了提高程序的效率,我们打算给每一个客户端都创建一个Callback Queue,但是这会带来新的问题:服务器并不知到某个request属于哪一个客户端。

所以我们就必须给每个request一个唯一标识,这个标识便是correlationId,也就是request与服务器之间的关系标识。

这样我们便可以大胆的使用多个客户端,并且也不会与服务器之间造成紊乱了。

由于服务器
五、过程总结

我们将远程调用的过程总结一下:
这里写图片描述
① 一个客户端发起请求,请求中带有两个标识:replyTo用于指定一个匿名的,独占的队列用于发起请求;correlationId用于标识该请求的唯一身份。

② 请求发送到rpc_queue队列。

③ 服务器等待接收请求,当接收到请求时便进行处理,并将处理结果返回给客户端,使用replyTo指定的队列名。

④ 客户端等待计算结果,一旦收到消息便检查,消息中的属性:correlationId,如果匹配,则取出结果数据。

六、代码实现

服务端:


import com.rabbitmq.client.*;

import java.io.IOException;


public class RPCServer {
    private static final String RPC_QUEUE_NAME = "rpc_queue";

    //计算斐波拉契和
    private static int fib(int n) {
        if (n == 0)
            return 0;
        if (n == 1)
            return 1;
        return fib(n - 1) + fib(n - 2);
    }

    public static void main(String[] args) {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = null;
        try {
            connection = factory.newConnection();
            Channel channel = connection.createChannel();
            channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
            //清空队列
            channel.queuePurge(RPC_QUEUE_NAME);
            channel.basicQos(1);
            System.out.println("wait for request...");
            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String correlationId = properties.getCorrelationId();
                    AMQP.BasicProperties replyProps = new AMQP.BasicProperties
                            .Builder()
                            .correlationId(correlationId)
                            .build();
                    StringBuffer response = new StringBuffer();
                    String message = new String(body, "utf-8");
                    int n = Integer.parseInt(message);
                    System.out.println("fib(" + message + ")...");
                    response.append(fib(n));
                    channel.basicPublish("", properties.getReplyTo(), replyProps, response.toString().getBytes("UTF-8"));
                    //手动回复
                    channel.basicAck(envelope.getDeliveryTag(), false);
                    synchronized (this) {
                        this.notify();
                    }

                }
            };
            channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
            while (true) {
                synchronized (consumer) {
                    try {
                        consumer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
            }
        }
    }
}

客户端:


import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;

public class RpcClient {
    private Connection connection;
    private Channel channel;
    private String requestQueueName = "rpc_queue";

    public RpcClient() throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        connection = factory.newConnection();
        channel = connection.createChannel();
    }

    public String call(String message) throws IOException, InterruptedException {
        String correlateId = UUID.randomUUID().toString();
        String replyQueueName = channel.queueDeclare().getQueue();
        AMQP.BasicProperties props = new AMQP.BasicProperties
                .Builder()
                .correlationId(correlateId)
                .replyTo(replyQueueName)
                .build();
        channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));
        //阻塞队列,大小为1
        BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //如果唯一标识对应,则以非阻塞的形式向阻塞队列中加入服务器返回的结果
                if (properties.getCorrelationId().equals(correlateId)) {
                    response.offer(new String(body, "UTF-8"));
                }
            }
        };
        String consumerTag = channel.basicConsume(replyQueueName, true, consumer);
        //以阻塞的形式在阻塞队列中取出结果
        String result = response.take();
        //清除消费者,下次不会再往该消费者发送消息
        channel.basicCancel(consumerTag);
        return result;
    }

    public void close() throws IOException, TimeoutException {
        if(channel!=null){
            channel.close();
        }
        if(connection!=null){
            connection.close();
        }
    }

    public static void main(String[] args) {
        RpcClient client = null;
        String response = null;
        try {
            client = new RpcClient();
            for (int i = 0; i < 32; i++) {
                String i_str = Integer.toString(i);
                System.out.println("请求计算:fib(" + i + ")");
                response = client.call(i_str);
                System.out.println("得到计算结果:" + response);
            }
        } catch (IOException | TimeoutException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (client != null) {
                try {
                    client.close();
                } catch (Exception e) {
                }
            }
        }
    }
}

运行结果:
这里写图片描述

这里写图片描述
服务端:由于我们可能有多个服务端,为了保证负载均衡,我们要设置basicQos的prefetchCount 为1。

客户端端:首先我们需要产生一个唯一的标识,我们需要这个值去匹配正确的response;然后我们需要创建一个独占的Callback Queue去发送请求与等待回复;接下来我们带着两个属性replyTo与correlationId去发起请求;这个时候,只需要等待服务器的返回结果。

由于我们是使用在回调函数handleDelivery中去接收服务器传回的结果,是在子线程中进行的,而我们在主线程处理得到的结果,这里使用BlockQueue来进行处理,就是十分方便。

handleDelivery方法只需要做一件简单的事情:对于服务器传回的每一个结果,检查其correlationId是否是我们期望的,如果是期望的就把该结果放入到BlockingQueue中。

而主线程需要做的则是:从BlockingQueue取出结果,展现给用户。

关于BlockingQueue的用法,建议大家参考博客:
https://www.cnblogs.com/jackyuj/archive/2010/11/24/1886553.html

大家也可以自行尝试使用多个客户端运行。


ok,到此我们RabbitMQ远程调用就结束了,

还有更多的关于RabbitMQ的用法:linux下的搭建,以及其余Spring的整合,就需要大家自行查资料了,也推荐大家使用RabbitMQ的官方网站:http://www.rabbitmq.com/

谢谢大家的观看!

猜你喜欢

转载自blog.csdn.net/qq_35890572/article/details/81981535