Rabbitmq:java客户端编写RPC远程过程调用

我们的RPC将这样工作:

  • 对于RPC请求,客户端发送带有两个属性的消息: replyTo(设置为仅为请求创建的匿名独占队列)和correlationId(设置为每个请求的唯一值)。
  • 请求被发送到rpc_queue队列。
  • RPC worker(aka:server)正在等待该队列上的请求。当出现请求时,它会执行该作业,并使用来自replyTo字段的队列将带有结果的消息发送回客户端。
  • 客户端等待回复队列上的数据。出现消息时,它会检查correlationId属性。如果它与请求中的值匹配,则将响应返回给应用程序。

 

1:导入依赖包

2:编写消息rpc请求端:RPCClient.java

     客户端代码稍微复杂一些:

  • 我们建立了一个连接和渠道。
  • 我们的调用方法生成实际的RPC请求。
  • 在这里,我们首先生成一个唯一的correlationId 数并保存它 - 我们的消费者回调将使用此值来匹配相应的响应。
  • 然后,我们为回复创建一个专用的独占队列并订阅它。
  • 接下来,我们发布请求消息,其中包含两个属性: replyTocorrelationId
  • 在这一点上,我们可以坐下来等待正确的响应到来。
  • 由于我们的消费者交付处理是在一个单独的线程中进行的,因此我们需要在响应到达之前暂停线程。使用BlockingQueue是一种可能的解决方案。这里我们创建了ArrayBlockingQueue ,容量设置为1,因为我们只需要等待一个响应。
  • 消费者正在做一个非常简单的工作,对于每个消费的响应消息,它检查correlationId 是否是我们正在寻找的那个。如果是这样,它会将响应置于BlockingQueue。response.offer(new String(delivery.getBody(), "UTF-8");
  • 同时线程正在等待响应从BlockingQueue获取它。response.take()
  • 最后,我们将响应返回给用户。

3::编写消息rpc服务端:RPCServer.java

     服务器代码非常简单:

  • 像往常一样,我们首先建立连接,信道和声明队列。
  • 我们可能希望运行多个服务器进程。为了在多个服务器上平均分配负载,我们需要在channel.basicQos中设置 prefetchCount设置。
  • 我们使用basicConsume来访问队列,我们​​以对象(DeliverCallback)的形式提供回调,它将完成工作并发回响应。

4:测试

RPCClient.java


public class RPCClient implements AutoCloseable  {
    private Connection connection;
    private Channel channel;
    private String requestQueueName = "rpc_queue";

    public RPCClient() throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.163.131");

        connection = factory.newConnection();
        channel = connection.createChannel();
    }

    public static void main(String[] argv) {
        try (RPCClient fibonacciRpc = new RPCClient()) {
            for (int i = 0; i < 32; i++) {
                String i_str = Integer.toString(i);
                System.out.println(" [x] Requesting fib(" + i_str + ")");
                String response = fibonacciRpc.call(i_str);
                System.out.println(" [.] Got '" + response + "'");
            }
        } catch (IOException | TimeoutException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String call(String message) throws IOException, InterruptedException {
        final String corrId = UUID.randomUUID().toString();
//一般来说,通过RabbitMQ进行RPC很容易。客户端发送请求消息,服务器回复响应消息。
// 为了接收响应,我们需要发送带有请求的“回调”队列地址。我们可以使用默认队列(在Java客户端中是独占的)
        String replyQueueName = channel.queueDeclare().getQueue();
        //预定义了一组带有消息的14个属性在BasicProperties中。大多数属性很少使用,但以下情况除外:
        //deliveryMode:将消息标记为持久性(值为2)或瞬态(任何其他值)。你可能还记得第二篇教程中的这个属性。
        //contentType:用于描述编码的mime类型。例如,对于经常使用的JSON编码,将此属性设置为:application / json是一种很好的做法。
        //replyTo:通常用于命名回调队列。
        //correlationId:用于将RPC响应与请求相关联。
        AMQP.BasicProperties props = new AMQP.BasicProperties
                .Builder()
                .correlationId(corrId)
                .replyTo(replyQueueName)
                .build();

        channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));

        final BlockingQueue<String> response = new ArrayBlockingQueue<>(1);

        String ctag = channel.basicConsume(replyQueueName, true, (consumerTag, delivery) -> {
            if (delivery.getProperties().getCorrelationId().equals(corrId)) {
                response.offer(new String(delivery.getBody(), "UTF-8"));
            }
        }, consumerTag -> {
        });

        String result = response.take();
        channel.basicCancel(ctag);
        return result;
    }

    public void close() throws IOException {
        connection.close();
    }
}

 RPCServer.java


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[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.163.131");

        try (Connection 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(" [x] Awaiting RPC requests");

            Object monitor = new Object();
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                AMQP.BasicProperties replyProps = new AMQP.BasicProperties
                        .Builder()
                        .correlationId(delivery.getProperties().getCorrelationId())
                        .build();

                String response = "";

                try {
                    String message = new String(delivery.getBody(), "UTF-8");
                    int n = Integer.parseInt(message);

                    System.out.println(" [.] fib(" + message + ")");
                    response += fib(n);
                } catch (RuntimeException e) {
                    System.out.println(" [.] " + e.toString());
                } finally {
                    channel.basicPublish("", delivery.getProperties().getReplyTo(), replyProps, response.getBytes("UTF-8"));
                    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                    // RabbitMq consumer worker thread notifies the RPC server owner thread
                    synchronized (monitor) {
                        monitor.notify();
                    }
                }
            };

            channel.basicConsume(RPC_QUEUE_NAME, false, deliverCallback, (consumerTag -> { }));
            // Wait and be prepared to consume the message from RPC client.
            while (true) {
                synchronized (monitor) {
                    try {
                        monitor.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

可以看到,我们的RPCServer里面就是定义了一个消息消费者,并且在回调函数中调用计算斐波那契数列的方法来计算值,然后在处理完毕之后发送消息给默认exchange,并且RoutingKey是使用客户端发送消息时候设置的BasicProperties中的replyTo也就是接收服务端应答消息的临时队列。

可以看到我们在客户端发送请求以及服务端发送运算结果中,都传递了correlationId这个随机数,这是为了防止出现多次调用call方法,返回的结果消息出现错乱,每次调用都传递一个随机UUID,然后通过比对运算结果消息传回的CorrelationId,从而确定这次收到的运算结果消息是否是真正需要的。(在本测试示例中可能不需要)
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值