RabbitMQ入门(2)——工作队列中,我们学习了如何使用工作队列处理在多个工作者之间分配耗时任务。如果我们需要运行远程主机上的某个方法并等待结果怎么办呢?这种模式就是常说的远程过程调用(Remote Procedure Call),简称RPC。

RPC

尽管RPC在计算机中是一种常见的模式,却经常饱受诟病。当程序员不知道方法的调用是本地的还是速度慢的RPC时,可能导致系统不可控、代码难以调试。因此,滥用RPC可能导致代码无法维护。

使用RPC的建议:

  • 明确方法的调用时本地的还是远程的。
  • 提供文档记录,让组件之间的依赖关系清楚明了。
  • 记录错误情况,当RPC服务器长时间宕机时,客户端如何处理

当有疑问是,要避免使用RPC,如果可以,尽量使用异步管道结果被异步推到下一个阶段,而不是像RPC那样阻塞。

这一篇我们将使用RabbitMQ建立一个PRC系统:一个客户端和一个可伸缩的PRC服务器。我们将创建一个返回Fibonacci数的虚拟服务。

客户端接口

为了说明RPC如何使用,首先创建一个客户端的类,它暴露一个名为call的方法发送RPC请求,直到收到应答。

  1. FibonacciRpcClient fibonacciRpc = new FibonacciRpcClient();
  2. String result = fibonacciRpc.call("4");
  3. System.out.println( "fib(4) is " + result);

回调队列(Callback queue)

一般来说,在RabbitMQ上执行RPC是比较简单的。客户端发送请求消息,服务器响应消息。为了接收响应,我们需要发送一个带有“回调”队列地址的请求。可以使用默认的队列,它在java客户端具有唯一性。

  1. callbackQueueName = channel.queueDeclare().getQueue();
  2. BasicProperties props = new BasicProperties
  3. .Builder()
  4. .replyTo(callbackQueueName)
  5. .build();
  6. channel.basicPublish("", "rpc_queue", props, message.getBytes());

消息属性(Message properties)

AMQP 0-9-1协议中预先定义了一组14个带有消息的属性。以下几个是比较常用的:

  • deliveryMode :将消息标记为持久的(值为2)或瞬态(任何其他值)。
  • contentType : 用于描述编码的mime类型。例如,对于经常使用的JSON编码,将该属性设置为:application/JSON是一种很好的做法。
  • replyTo : 通常用来命名一个回调队列
  • correlationId : 有助于将RPC响应与请求关联起来

关联ID(Correlation Id)

上面的方法中为每一个RPC请求创建一个回调队列的方式效率很低。幸好我们可以为每个客户端创建一个回调队列。然而这又引出了一个新的问题:回调队列中收到的响应我们并不知道是哪个请求发出的。这就是correlationId 属性的作用,我们将为每个请求设置一个唯一的correlationId 。当我们从回调队列收到一条消息时,首先查看correlationId 属性,这样我们就能将请求和响应对应起来了。如果发现一个未知的correlationId,说明它与我们的请求无关,就可以安全地丢弃这条消息了。

也许你会问为什么忽略回调队列的未知消息而不是处理错误?这可能是服务端的竞态条件导致的。RPC服务器可能在发送响应之后,在发送请求的确认消息之前宕机。这就是为什么我们在客户端必须优雅地处理重复的响应,而RPC在理想情况下应该是幂等的。

总结

RPC工作流程

  • 当客户端启动时,它将创建一个匿名的唯一回调队列
  • 对于RPC请求,客户端会发送一个包含两个属性的消息,replyTo属性设置回调队列,correlationId为每一个请求设置唯一的值
  • 请求被发送到rpc_queue队列
  • RPC服务器正在等待该队列上的请求,当请求到达时,服务器执行任务并使用replyTo字段的队列将结果返回给客户端。
  • 客户端等待回调队列。当消息到达时,首先检查correlationId属性值,如果它和请求的值匹配,它将返回对应用程序的响应

代码清单

RPCServer:

  1. package com.xxyh.rabbitmq;
  2. import com.rabbitmq.client.*;
  3. import java.io.IOException;
  4. import java.util.concurrent.TimeoutException;
  5. public class RPCServer {
  6. private static final String RPC_QUEUE_NAME = "rpc_queue";
  7. public static void main(String[] args) {
  8. ConnectionFactory factory = new ConnectionFactory();
  9. factory.setHost("localhost");
  10. Connection connection = null;
  11. try {
  12. connection = factory.newConnection();
  13. Channel channel = connection.createChannel();
  14. channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
  15. channel.basicQos(1);
  16. System.out.println("等待RPC请求......");
  17. Consumer consumer = new DefaultConsumer(channel) {
  18. @Override
  19. public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
  20. AMQP.BasicProperties replyProps = new AMQP.BasicProperties
  21. .Builder()
  22. .correlationId(properties.getCorrelationId())
  23. .build();
  24. String response = "";
  25. try {
  26. String message = new String(body,"UTF-8");
  27. int n = Integer.parseInt(message);
  28. System.out.println(" 计算fib(" + message + ")");
  29. response += fib(n);
  30. }
  31. catch (RuntimeException e){
  32. System.out.println(" [.] " + e.toString());
  33. }
  34. finally {
  35. channel.basicPublish( "", properties.getReplyTo(), replyProps, response.getBytes("UTF-8"));
  36. channel.basicAck(envelope.getDeliveryTag(), false);
  37. }
  38. }
  39. };
  40. channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
  41. while (true) {
  42. try {
  43. Thread.sleep(100);
  44. } catch (InterruptedException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48. } catch (IOException e) {
  49. e.printStackTrace();
  50. } catch (TimeoutException e) {
  51. e.printStackTrace();
  52. } finally {
  53. if (connection != null) {
  54. try {
  55. connection.close();
  56. } catch (IOException e) {
  57. e.printStackTrace();
  58. }
  59. }
  60. }
  61. }
  62. private static int fib(int n) {
  63. if (n == 0) {
  64. return 0;
  65. }
  66. if (n == 1) {
  67. return 1;
  68. }
  69. return fib(n - 1) + fib(n - 2);
  70. }
  71. }

RPCClient:

  1. package com.xxyh.rabbitmq;
  2. import com.rabbitmq.client.*;
  3. import java.io.IOException;
  4. import java.io.UnsupportedEncodingException;
  5. import java.util.UUID;
  6. import java.util.concurrent.ArrayBlockingQueue;
  7. import java.util.concurrent.BlockingQueue;
  8. import java.util.concurrent.TimeoutException;
  9. public class RPCClient {
  10. private static final String RPC_QUEUE_NAME = "rpc_queue";
  11. private Connection connection;
  12. private Channel channel;
  13. private String replyQueueName;
  14. public RPCClient() throws IOException, TimeoutException {
  15. ConnectionFactory factory = new ConnectionFactory();
  16. factory.setHost("localhost");
  17. connection = factory.newConnection();
  18. channel = connection.createChannel();
  19. replyQueueName = channel.queueDeclare().getQueue();
  20. }
  21. public String call(String message) throws IOException, InterruptedException {
  22. String corrId = UUID.randomUUID().toString();
  23. AMQP.BasicProperties props = new AMQP.BasicProperties
  24. .Builder()
  25. .correlationId(corrId)
  26. .replyTo(replyQueueName)
  27. .build();
  28. channel.basicPublish("", RPC_QUEUE_NAME, props, message.getBytes("utf-8"));
  29. final BlockingQueue<String> response = new ArrayBlockingQueue<>(1);
  30. channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) {
  31. @Override
  32. public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
  33. if (properties.getCorrelationId().equals(corrId)) {
  34. response.offer(new String(body, "utf-8"));
  35. }
  36. }
  37. });
  38. return response.take();
  39. }
  40. public void close() throws IOException {
  41. connection.close();
  42. }
  43. public static void main(String[] args) {
  44. RPCClient fibonacciRpc = null;
  45. String response = null;
  46. try {
  47. fibonacciRpc = new RPCClient();
  48. System.out.println(Thread.currentThread().getName() + " 请求 fib(30)");
  49. response = fibonacciRpc.call("30");
  50. System.out.println(Thread.currentThread().getName() + " 获得结果: " + response);
  51. } catch (IOException e) {
  52. e.printStackTrace();
  53. } catch (TimeoutException e) {
  54. e.printStackTrace();
  55. } catch (InterruptedException e) {
  56. e.printStackTrace();
  57. } finally {
  58. if (fibonacciRpc != null) {
  59. try {
  60. fibonacciRpc.close();
  61. } catch (IOException e) {
  62. e.printStackTrace();
  63. }
  64. }
  65. }
  66. }
  67. }

RabbitMQ入门(6)——远程过程调用(RPC)的更多相关文章

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

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

  2. (转) RabbitMQ学习之远程过程调用(RPC)(java)

    http://blog.csdn.net/zhu_tianwei/article/details/40887885 在一般使用RabbitMQ做RPC很容易.客户端发送一个请求消息然后服务器回复一个响 ...

  3. RabbitMQ入门:总结

    随着上一篇博文的发布,RabbitMQ的基础内容我也学习完了,RabbitMQ入门系列的博客跟着收官了,以后有机会的话再写一些在实战中的应用分享,多谢大家一直以来的支持和认可. RabbitMQ入门系 ...

  4. RabbitMQ入门教程(八):远程过程调用RPC

    原文:RabbitMQ入门教程(八):远程过程调用RPC 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.cs ...

  5. [译]RabbitMQ教程C#版 - 远程过程调用(RPC)

    先决条件 本教程假定 RabbitMQ 已经安装,并运行在localhost标准端口(5672).如果你使用不同的主机.端口或证书,则需要调整连接设置. 从哪里获得帮助 如果您在阅读本教程时遇到困难, ...

  6. .NET 环境中使用RabbitMQ RabbitMQ与Redis队列对比 RabbitMQ入门与使用篇

    .NET 环境中使用RabbitMQ   在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的 ...

  7. 转载RabbitMQ入门(6)--远程调用

    远程过程调用(RPC) (使用Java客户端) 在指南的第二部分,我们学习了如何使用工作队列将耗时的任务分布到多个工作者中. 但是假如我们需要调用远端计算机的函数,等待结果呢?好吧,这又是另一个故事了 ...

  8. RabbitMQ入门-理论

    目录 RabbitMQ简介 RabbitMQ原理简介 RabbitMQ安装 .NET Core 使用 RabbitMQ Hello World 工作队列 扇型交换机 直连交换机 主题交换机 远程过程调 ...

  9. 消息中间件 RabbitMQ 入门篇

    消息中间件 RabbitMQ 入门篇 五月君 K8S中文社区 今天   作者:五月君,来源:Nodejs技术栈 从不浪费时间的人,没有工夫抱怨时间不够.—— 杰弗逊 RabbitMQ 是一套开源(MP ...

随机推荐

  1. python学习笔记(六)— 模块

    一.os.sys模块 import os print(os.getcwd())#取当前工作目录,绝对路径 print(os.chdir("../"))#更改当前目录 print(o ...

  2. mongodb批量插入数据

    年前由于公司业务需要,后台需要获取流水记录,需要每天定时跑脚本,将流水记录跑入库里边,每天大概有个一百万左右,使用的数据库是mongodb,考虑到一条一条录入数据,100多万会跑断,就想着批量录入数据 ...

  3. python基础-第十二篇-12.1jQuery基础与实例

    一.查找元素 1.选择器 基本选择器 $("*") $("#id") $(".class") $("element") ...

  4. LeetCode之Symmetric Tree

    </pre>Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its ce ...

  5. AndroidStudio修改常用快捷键

    近期公司开发工具要从eclipse转向Androidstudio,安装好as后当然迫不及待地要将快捷键修改为eclipse中的快捷键啦,下面是个人的一些小的总结. 1.首先当然要打开快捷键的设置界面啦 ...

  6. DevExpress控件学习总结

    1.Navigation & Layout 1.1 Bar Manager 如果想在窗体或用户控件(user control)上添加工具条(bars)或弹出菜单(popup menus),我们 ...

  7. OCR技术浅探:特征提取(1)

    研究背景 关于光学字符识别(Optical Character Recognition, 下面都简称OCR),是指将图像上的文字转化为计算机可编辑的文字内容,众多的研究人员对相关的技术研究已久,也有不 ...

  8. python学习笔记(二十一)构造函数和析构函数

    python中的特殊方法,其中两个,构造函数和析构函数的作用: 比说“__init__”这个构造函数,具有初始化的作用,也就是当该类被实例化的时候就会执行该函数.那么我们就可以把要先初始化的属性放到这 ...

  9. 开启mysql的远程访问

    1.登陆mysql数据库 mysql -u root -p 查看user表 mysql> use mysql;Database changedmysql> select host,user ...

  10. Linux系统——进程和计划任务管理

    进程和计划任务管理 一.进程和程序的关系 进程:在CPU及内存中运行的程序代码:动态执行的代码:每个进程可以创建一个或多个进程 程序:保存在硬盘.光盘等介质中的可执行代码和数据:静态保存的代码 二.基 ...