在项目中引入RabbitMQ通常会考虑它会带来的好处:解耦应用程序,实现不同编程语言之间的互通,解除对特定通信协议的依赖,解除应用程序在时序上执行的依赖(异步).落实到代码层面就是两种常用应用模式:"发后即忘"(fire-and-forget)和RPC.

fire-and-forget

  RabbitMQ 解决的是应用程序之间互联(connect)和规模(scale)的问题,消息发送和接收是隔离,发送方不知道消息最终由谁接收,接收方也不必关心消息是谁步发出的;发送和接收是隔离的,消息本质上就是异步的.这种隔离也就解耦了应用程序之间的依赖.RabbitMQ的角色就是应用程序中间的路由器.对于消息的发布方来讲这是一种"发后即忘"(fire-and_forget)的发布方式.

RPC

  RPC需要双向通信,或者说RPC Server需要明确知道要把消息发送给谁.我们可以在payload的数据部分附加"发给谁" 这种EndPoint信息. RabbitMQ提供的解决方案:在每一个AMQP的消息头上有一个reply_to字段.这样消息的producer就可以指定Queue name,RPC Server接受到消息检查reply_to字段,创建一个消息包含Response并把queue name作为routing key,订阅了这个队列的Client就拿到了消息.

  这里有两件事情要保证:

  1. 要为队列创建随机Name
  2. 即使Name随机还是有可能冲突,还需要保证消息通信的独占性。

  看看RabbitMQ是怎么满足这两点的:

  1. 如果创建的队列不指定queue name,RabbitMQ就会创建一个随机的Name.
  2. 独占只需要exclusive参数即可

  总而言之,需要做的就是Client创建一个temporary,exclusive,anonymou的queue,并把queue name设置在RPC 消息的reply_to字段即可.注意这里RPC Server已经知道要投递到哪个Queue,所以不需要指定Exchange(后面我们会提到在实现层面Queue和Exchange的不同,简单讲 queue会有对应的Erlang进程,而exchang只是执行一些模式匹配的检查并没有进程实体对应).看下图:

  Our RPC will work like this:

  • When the Client starts up, it creates an anonymous exclusive callback queue.
  • For an RPC request, the Client sends a message with two properties: replyTo, which is set to the callback queue and correlationId, which is set to a unique value for every request.
  • The request is sent to an rpc_queue queue.
  • The RPC worker (aka: server) is waiting for requests on that queue. When a request appears, it does the job and sends a message with the result back to the Client, using the queue from the replyTo field.
  • The client waits for data on the callback queue. When a message appears, it checks the correlationId property. If it matches the value from the request it returns the response to the application

RPC Client端的代码如下:

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.AMQP.BasicProperties;
import java.util.UUID; public class RPCClient {
private Connection connection;
private Channel channel;
private String requestQueueName = "rpc_queue";
private String replyQueueName;
private QueueingConsumer consumer; public RPCClient() throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
connection = factory.newConnection();
channel = connection.createChannel(); replyQueueName = channel.queueDeclare().getQueue();
consumer = new QueueingConsumer(channel);
channel.basicConsume(replyQueueName, true, consumer);
} public String call(String message) throws Exception {
String response = null;
String corrId = UUID.randomUUID().toString();
BasicProperties props = new BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build(); channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
if (delivery.getProperties().getCorrelationId().equals(corrId)) {
response = new String(delivery.getBody(),"UTF-8");
break;
}
}
return response;
} public void close() throws Exception {
connection.close();
} public static void main(String[] argv) {
RPCClient fibonacciRpc = null;
String response = null;
try {
fibonacciRpc = new RPCClient(); System.out.println(" [x] Requesting fib(30)");
response = fibonacciRpc.call("30");
System.out.println(" [.] Got '" + response + "'");
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (fibonacciRpc!= null) {
try {
fibonacciRpc.close();
}
catch (Exception ignore) {}
}
}
}
}

RPC SEVER端代码如下:

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.AMQP.BasicProperties; 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) {
Connection connection = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
connection = factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
channel.basicQos(1); QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
System.out.println(" [x] Awaiting RPC requests"); while (true) {
String response = null;
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
BasicProperties props = delivery.getProperties();
BasicProperties replyProps = new BasicProperties.Builder()
.correlationId(props.getCorrelationId()).build(); try {
String message = new String(delivery.getBody(), "UTF-8");
int n = Integer.parseInt(message);
System.out.println(" [.] fib(" + message + ")");
response = "" + fib(n);
} catch (Exception e) {
System.out.println(" [.] " + e.toString());
response = "";
} finally {
channel.basicPublish("", props.getReplyTo(), replyProps,
response.getBytes("UTF-8"));
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),
false);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (Exception ignore) {
}
}
}
}
}

略有不同

  传统的RPC调用Client和Server紧密依赖,客户端连接上服务器,发送一个请求然后阻塞等待服务器响应.这样的做的特点是客户端和服务器端是知道对方的.如果RPC Server崩溃掉,客户端需要重连,如果Server彻底崩掉就要重新找一个提供同样服务的Server,然后客户端重连过去.

  用RabbitMQ来实现RPC,依然保持Client Server信息隐藏的特点,Client依赖的不是特定的Server而是特定的消息,在有多个等效Server的情况下,一个Server的状态是否正常不会影响到客户端的状态.

  总结一下,使用RabbitMQ是先RPC,客观上还实现了下面的效果:

  1. 容错 一个Server崩溃不影响 Client
  2. 解耦了对特定通信协议和接口的依赖,统一走AMQP消息.
  3. 在多个RPC Server之间的负载均衡由RabbitMQ完成

RabbitMQ4--发后即忘和RPC的更多相关文章

  1. 【转】 JavaScript:history.go() 的妙用(转) 处理post回发后返回

    在Web开发中,会遇到从一页(父页)导向另一页(子页),并且要求“返回”父页的情况,在这里如果用ASP.NET提供的 Response.Redirect()方法,往往不会达到理想的效果,例如:返回后, ...

  2. Error-ASP.NET:由于未能找到 id 为“FileUpload1$gvFiles$ctl02$lnkBtnRemoveFile”的控件或在回发后将同一 ID 分配给另一个控件,导致发生错误。如果未分配 ID,请显式设置引发回发事件的控件的 ID 属性以避免此错误。

    ylbtech-Error-ASP.NET:由于未能找到 id 为“FileUpload1$gvFiles$ctl02$lnkBtnRemoveFile”的控件或在回发后将同一 ID 分配给另一个控件 ...

  3. Salesforce Integration 概览(三) Remote Process Invocation—Fire and Forget(远程进程调用-发后即弃)

    本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_practices.pdf 我们在上一篇讲了远 ...

  4. asp.net页面刷新或者回发后DIV的滚动条位置不变!(转)

    源文件:http://www.cnblogs.com/nyth/archive/2011/06/10/2077868.html 当把数据放在div里面,然后给div设置Scroll显示,在页面刷新后或 ...

  5. 安装完成Dynamics 365 CE后别忘了更改维护作业的运行时间

    摘要: 微软动态CRM专家罗勇 ,回复309或者20190308可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me . 安装完毕Dy ...

  6. RabbitMQ(四):RPC的实现

    原文:RabbitMQ(四):RPC的实现 一.RPC RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议. ...

  7. 运行和管理Rabbit

    节点描述的是一个Erlang节点运行着一个Erlang应用程序.Erlang虚拟机的每个实例我们称之为节点.多个Erlang应用程序可以运行在同一个节点之上.节点之间可以进行本地通信.在RabbitM ...

  8. 消息中间件的研究(二) RabbitMQ应用场景分析

    分析一下六个场景下RabbitMQ的应用: 1.爬虫 2.智能家居云平台 3.电子商务系统 4.实时监控系统 5.海量日志的分布式处理 6. 智能交通管控平台中数据分析子系统     1.爬虫     ...

  9. ActiveMQ、RabbitMQ、RocketMQ、Kafka四种消息中间件分析介绍

    ActiveMQ.RabbitMQ.RocketMQ.Kafka四种消息中间件分析介绍 我们从四种消息中间件的介绍到基本使用,以及高可用,消息重复性,消息丢失,消息顺序性能方面进行分析介绍! 一.消息 ...

随机推荐

  1. Python之路-python介绍

    一.Python及其他语言 有很多种分类方法,其中一种是按照解释型和编译型来划分的. 编译型:例如C,C++ 优点:运行效率高 缺点:依赖编译平台 (不能跨平台,开发效率低) 解释型:例如shell, ...

  2. 1.Maven的安装及配置

    1 Maven 介绍 Maven这个词可以翻译为“知识的积累”,也可以翻译为“专家”或“内行”.本书将介绍Maven这一跨平台的项目管理工具.作为Apache组织中的一个颇为成功的开源项目,Maven ...

  3. Android 用 camera2 API 自定义相机

    前言 笔者因为项目需要自定义相机,所以了解了一下 Android 关于 camera 这块的 API.Android SDK 21(LOLLIPOP) 开始已经弃用了之前的 Camera 类,提供了 ...

  4. linux之shell编程基本语法

    Shell是用户与内核进行交互操作的一种接口,目前最流行的Shell称为bash Shell.Shell也是一门编程语言<解释型的编程语言>,即shell脚本<就是在用linux的s ...

  5. 通过一个小游戏开始接触Python!

    之前就一直嚷嚷着要找视频看学习Python,可是一直拖到今晚才开始....好好加油吧骚年,坚持不一定就能有好的结果,但是不坚持就一定是不好的!! 看着视频学习1: 首先,打开IDLE,在IDLE中新建 ...

  6. tolua#代码简要分析

    简介 tolua#是Unity静态绑定lua的一个解决方案,它通过C#提供的反射信息分析代码并生成包装的类.它是一个用来简化在C#中集成lua的插件,可以自动生成用于在lua中访问Unity的绑定代码 ...

  7. JDK8-十大新特性-附demo

    JDK原计划17年上半年就发版,但未成功发版.才发现JDK8的特性还没总结过,特此总结. 一.十大特性. 1.Lambda表达式 2.Stream函数式操作流元素集合 3.接口新增:默认方法与静态方法 ...

  8. 记因PHP的内存溢出导致的事故之解决

    如果对您有用记得关注,更多干货. 今天上午刚到公司,就有同事在公司群里反映某个计划任务出现问题了.我就怀着刨根问底的心,去查看了log.发现挺有意思的一个问题,PHP内存溢出导致脚本执行失败.那就一起 ...

  9. TDD 中关于mock一些理解

    最近在写代码的UT时case注意到: 在写某个类的test suit时,如果这个类既有组合(Composition),又有聚合关系(Aggregation). 组合关系(Composition):部分 ...

  10. 大麦盒子(domybox)无法进入系统解决方案!【简单几步】

    大麦无法进入系统解决方案![简单几步]前提准备:电脑一台盒子控制台软件盒子开机并联网并且盒子和电脑处于同一个路由器下的网络! 前提准备:电脑一台盒子控制台软件盒子开机并联网并且盒子和电脑处于同一个路由 ...