Spring消息之AMQP.
一、AMQP 概述
AMQP(Advanced Message Queuing Protocol),高级消息队列协议。
简单回忆一下JMS的消息模型,可能会有助于理解AMQP的消息模型。在JMS中,有三个主要的参与者:消息的生产者、消息的消费者以及在生产者和消费者之间传递消息的通道(队列或主题)。在JMS中,通道有助于解耦消息的生产者和消费者,但是这两者依然会与通道相耦合。与之不同的是,AMQP的生产者并不会直接将消息发布到队列中。AMQP在消息的生产者以及传递信息的队列之间引入了一种间接的机制:Exchange。如下图:
哈哈,笔主从今天开始也要学着自己画图了。
来看看 AMQP 消息的通信过程。首先,生产者把消息发给 Exchange,并带有一个 routing key。其次,Exchange 和 队列 之间 通过 binging 通信,binging 上也有 一个 routing key,AMQP定义了四种不同类型的Exchange,每一种都有不同的路由算法,根据Exchange的算法不同,它可能会使用消息的routing key或参数,并与 binding 的routing key或参数进行对比,来决定是否要将信息放到队列中。然后,消费者从每个队列中取出消息。
Exchange 的路由算法:
- Direct:如果 消息的routing key 与 binding的routing key 直接匹配的话,消息将会路由到该队列上;
- Topic:如果 消息的routing key 与 binding的routing key 符合通配符匹配的话,消息将会路由到该队列上;
- Headers:如果 消息参数表中的头信息和值 都与 bingding参数表中 相匹配,消息将会路由到该队列上;
- Fanout:不管消息的routing key和参数表的头信息/值是什么,消息将会路由到所有队列上。
AMQP 与 JMS 的区别:
1、AMQP为消息定义了线路层(wire-level protocol)的协议,而JMS所定义的是API规范。JMS的API协议能够确保所有的实现都能通过通用的API来使用,但是并不能保证某个JMS实现所发送的消息能够被另外不同的JMS实现所使用。而AMQP的线路层协议规范了消息的格式,消息在生产者和消费者间传送的时候会遵循这个格式。这样AMQP在互相协作方面就要优于JMS——它不仅能跨不同的AMQP实现,还能跨语言和平台。
2、JMS 支持TextMessage、MapMessage 等复杂的消息类型;而AMQP 仅支持 byte[] 消息类型(复杂的类型可序列化后发送),个人认为这也是它能够跨平台和跨语言使用的原因之一。
3、由于Exchange 提供的路由算法,AMQP可以提供多样化的路由方式来传递消息到消息队列,而 JMS 仅支持 队列 和 主题/订阅 方式两种。
二、Spring 集成 RabbitMQ
RabbitMQ是一个流行的开源消息代理,它实现了AMQP。这里先介绍一下关于 RabbitMQ 的基本概念:
- Broker:可以理解为消息队列服务器的实体,它是一个中间件应用,负责接收生产者的消息,然后将消息发送至消息接收者或者其他的 Broker
- Exchange:消息交换机,是消息第一个到达的地方,消息通过它指定的路由规则,分发到不同的消息队列中去。
- Queue:消息队列,消息通过发送和路由之后最终到达的地方,到达Queue的消息即进入逻辑上等待消费的状态。每个消息都会被发送到一个或多个队列。
- Binding:绑定,它的作用就是把 Exchange 和 Queue 按照路由规则绑定起来,也就是 Exchange 和 Queue 之间的虚拟连接。
- RoutingKey:路由关键字,Exchange 根据这个关键字 进行消息投递。
- Virtual host:虚拟主机,它是对Broker的虚拟划分,将消费者、生产者和它们依赖的AMQP相关结构进行隔离,一般都是为了安全考虚。比如,我们可以在一个Broker中设置多个虚拟主机,对不同用户进行权限的分离 。
- Connection:连接,代表生产者、消费者、Broker之间进行通信的物理网络。
- Channel:消息通道,用于连接生产者和消费者的逻辑结构。在客户端的每个连接里,可建立多个Channel, 每个Channel代表一个会话任务,通过Channel可以隔离同一连接中的不同交互内容。
- Producer:消息生产者,制造消息并发送消息的程序。
- Consumer:消息消费者,接收消息并处理消息的程序。
RabbitMQ 支持消息的持久化,也就是将数据写在磁盘上。为了数据安全考虑,大多数情况下都会选择持久化。消息队列持久化包括三个部分:
- Exchange 持久化,在声明时指定durable => 1 。
- Queue 待久化,在声明时指定durable => 1。
- 消息持久化,在投递时指定delivery_mode => 2 (1是非持久化)。
如果 Exchange 和 Queue 都是持久化的,那么它们之间的 Binding 也是持久化的。如果 Exchange 和 Queue 两者之间有一个是持久化的,一个是非持久化的,就不允许建立绑定。
Spring Data AMQP 为 RabbitMQ 提供了支持,包括 RabbitMQ连接工厂、模板以及Spring配置命名空间。
首先,需要安装 RabbitMQ,我们可以在 http://www.rabbitmq.com/download.html 上找到安装指南,具体怎么安装,不是这篇博文的重点,请笔友们自行解决。
接下来,让我们一起来看看,Spring 和 RabbitMQ 的集成:
1、pom 依赖
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
2、连接工厂 和 admin
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.userName}"
password="${rabbitmq.password}"/> <rabbit:admin connection-factory="connectionFactory"/>
admin 元素会自动创建一个RabbitMQ管理组件,它会自动创建队列、Exchange以及binding
3、声明队列、Exchange以及binding
声明队列:
<rabbit:queue name="queue1"/>
<rabbit:queue name="queue2"/>
<rabbit:queue name="queue3"/>
<rabbit:queue name="queue4"/>
<rabbit:queue name="queue5"/>
<rabbit:queue name="queue6"/>
声明 Exchange 以及 binding:
direct-exchange:
<rabbit:direct-exchange name="directExchange">
<rabbit:bindings>
<rabbit:binding key="queue1" queue="queue1"/>
<rabbit:binding key="queue2" queue="queue2"/>
<rabbit:binding key="queue3" queue="queue3"/>
</rabbit:bindings>
</rabbit:direct-exchange>
如果消息的routing key 与 routing key 直接匹配的话,消息将会路由到该队列上。
topic-exchange
<rabbit:topic-exchange name="topicExchange">
<rabbit:bindings>
<rabbit:binding pattern="routing.*" queue="queue2"/>
<rabbit:binding pattern="routing.*" queue="queue3"/>
</rabbit:bindings>
</rabbit:topic-exchange>
消息的 routing key 与 binding的routing key 符合通配符匹配的话,消息将会路由到该队列上。
这个通配符匹配特别坑,贼坑!我本来写了个 "routing*" ,自以为能匹配 "routingrrr" 这样的字符,不行!然后我又写了个"routing?"、"rounting.",预想着能不能匹配单个任意字符,不行!
终于我得出了一个结论,只能使用 "*"(匹配 0 个或任意多个)通配符,并且,并且!"*" 前面一定要有 个 "." ! 太可怕了,不知道我总结的对不对哈!
headers-exchange
<rabbit:headers-exchange name="headersExchange">
<rabbit:bindings>
<rabbit:binding queue="queue4" key="betty" value="rubble" />
<rabbit:binding queue="queue5" key="barney" value="rubble" />
</rabbit:bindings>
</rabbit:headers-exchange>
消息参数表中的头信息和值都与bingding参数表中相匹配,消息将会路由到该队列上。
这个用法比较少用,也比较难用,原因是因为它仅支持 发送 byte[] 的消息类型。
fanout-exchange
<rabbit:fanout-exchange name="fanoutExchange">
<rabbit:bindings>
<rabbit:binding queue="queue5"/>
<rabbit:binding queue="queue6"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
这个是最简单粗暴的匹配规则,不管消息的routing key和参数表的头信息/值是什么,消息将会路由到所有队列上。
4、发送和接收消息
还是Spring的那一套,Spring 为我们提供了一个模板 bean(rabbitTemplate) 来发送和接收消息。其中,像前文提到的 jmsTemplate 那样,rabbitTemplate 也为我们 提供了 convertAndSend() 方法来自动转换和发送消息,提供了receiveAndConvret() 方法来接收和自动转换成对象(消息和对象之间默认的消息转换器是SimpleMessageConverter,它适用于String、Serializable实例以及字节数组)。另外,rabbitTemplate 也照常提供了 send() 和 receive() 方法来发送和接收消息,不过貌似仅支持发送字节数组...
配置 rabbitTemplate:
<rabbit:template id="rabbitTemplate"
connection-factory="connectionFactory"
exchange="directExchange"
routing-key="queue1"/>
下面仅演示 通配符路由方式 和 header 路由方式 发送和接收消息。其他具体详细的内容可参考我下面附上的源码:
通配符路由方式:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TopicExchange { @Autowired
private RabbitTemplate rabbitTemplate; @Test
public void convertAndSend(){
List<String> list = new ArrayList<>();
list.add("java");
list.add("python");
list.add("c++");
rabbitTemplate.convertAndSend("topicExchange","routing.123", list);
} @Test
public void receiveAndConvert(){
List<String> queue2List =(List) rabbitTemplate.receiveAndConvert("queue2");
printList(queue2List); System.out.println("----------------华丽的分隔符-----------------"); List<String> queue3List =(List) rabbitTemplate.receiveAndConvert("queue3");
printList(queue3List); } private <E> void printList(List<E> list){
if (list != null && list.size() > 0){
for (Object o : list){
System.out.println("-----------------"+ o +"---------------");
}
}
}
}
header 路由方式:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class HeadersExchangeTest { @Autowired
private RabbitTemplate rabbitTemplate; @Test
public void convertAndSend(){
MessageProperties messageProperties = new MessageProperties();
messageProperties.setHeader("betty", "rubble");
messageProperties.setHeader("fred", "flintstone");
messageProperties.setHeader("barney", "rubble"); String str = new String("Hello RabbitMQ");
Message message = new Message(str.getBytes(), messageProperties);
rabbitTemplate.convertAndSend("headersExchange","",message);
} @Test
public void receiveAndConvert(){ Message queue4 = rabbitTemplate.receive("queue4");
System.out.println("第一个输出:" + new String(queue4.getBody()));
Message queue5 = rabbitTemplate.receive("queue5");
System.out.println("第三个输出:" + new String(queue5.getBody())); } }
5、定义消息驱动的AMQP POJO
用 receive()和 receiveAndConvert()方法都会立即返回,如果队列中没有等待的消息时,将会得到 null。Spring AMQP提供了消息驱动POJO的支持,也就是相当于一个监听器,监听某些队列,当消息到达指定队列的时候,可以立即调用方法处理该消息。
listener-container 配置:
<rabbit:listener-container connection-factory="connectionFactory" concurrency="2" prefetch="3" type="direct">
<rabbit:listener ref="handlerListener" method="handler" queue-names="queue5,queue6"/>
</rabbit:listener-container>
其中,ref 指定Spring bean 的 id,method 指定 该bean中处理队列中消息的方法,queue-names 指定要监听哪些队列,队列之间用 "," 分隔。
三、结语
祝大家五一节快乐!
演示源码下载链接:https://github.com/JMCuixy/SpringMessageRabbitMQ
参考资料:1、《Spring 实战第四版》
Spring消息之AMQP.的更多相关文章
- 第17章-Spring消息
1 异步消息简介 像RMI和Hessian/Burlap这样的远程调用机制是同步的.如图17.1所示,当客户端调用远程方法时,客户端必须等到远程方法完成后,才能继续执行.即使远程方法不向客户端返回任何 ...
- Spring集成RabbiMQ-Spring AMQP新特性
上一篇<Spring集成RabbitMQ-使用RabbitMQ更方便>中,我们只需要添加响应jar的依赖,就可以写一个Spring集成RabbitMQ下非常简单收发消息的程序. 我们使用的 ...
- Spring 消息
RMI.Hessian/Burlap的远程调用机制是同步的.当客户端调用远程方法时,客户端必须等到远程方法完成之后,才能继续执行.即使远程方法不向客户端返回任何消息,客户端也要被阻塞知道服务完成. 消 ...
- 2015年12月10日 spring初级知识讲解(三)Spring消息之activeMQ消息队列
基础 JMS消息 一.下载ActiveMQ并安装 地址:http://activemq.apache.org/ 最新版本:5.13.0 下载完后解压缩到本地硬盘中,解压目录中activemq-core ...
- Spring消息之JMS.
一.概念 异步消息简介 与远程调用机制以及REST接口类似,异步消息也是用于应用程序之间通信的. RMI.Hessian.Burlap.HTTP invoker和Web服务在应用程序之间的通信机制是同 ...
- Spring消息之WebSocket
一.WebSocket简介 WebSocket 的定义?WebSocket是HTML5下一种全双工通信协议.在建立连接后,WebSocket服务器端和客户端都能主动的向对方发送和接收数据,就像Sock ...
- Spring消息之STOMP
一.STOMP 简介 直接使用WebSocket(或SockJS)就很类似于使用TCP套接字来编写Web应用.因为没有高层级的线路协议(wire protocol),因此就需要我们定义应用之间所发送消 ...
- 消息中间件 | 消息协议 | AMQP -- 《分布式 消息中间件实践》笔记
04年,AMQP开放标准被开发 06年,AMQP规范被发布 基本概念 Message:与平台无相关的数据. Publisher:向交换器发布消息的客户端应用程序 Excha ...
- 【Java Web开发学习】Spring消息-ActiveMQ发送消息
ActiveMQ发送消息 转载:http://www.cnblogs.com/yangchongxing/p/9042401.html Java消息服务(Java Message Service, J ...
随机推荐
- MFC基础
入门博客:http://www.cnblogs.com/qinfengxiaoyue/category/451679.html 消息机制:http://www.cnblogs.com/qinfengx ...
- Cookie、Session登陆验证相关介绍和用法
一.Cookie和Session 首先.HTTP协议是无状态的:所谓的无状态是指每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应直接影响,也不会直接 ...
- 推荐几个IDEA插件,Java开发者撸码利器。
这里只是推荐一下好用的插件,具体的使用方法不一一详细介绍. JRebel for IntelliJ 一款热部署插件,只要不是修改了项目的配置文件,用它都可以实现热部署.收费的,破解比较麻烦.不过功能确 ...
- Java线程池是如何诞生的?
时间回到2003年,那时我还是一个名不见经传的程序员,但是上级却非常看好我,他们把整个并发模块,都交给了我一个人开发. 这个星期,我必须要完成并发模块中非常重要的一个功能--线程池. 注:文末有福利 ...
- vue2与vue1的区别
在前面的学习过程中我们已经对vue1有了一定的了解,下面我们来学习一下vue2,看一下vue1与vue2有什么区别. 区别1: 在vue2中使用v-for指令时它可以添加重复的内容,就像可以添加相同的 ...
- Havel-Hakimi定理---通过度数列判断是否可图化
0.可图:一个非负整数组成的序列如果是某个无向图的度序列,则该序列是可图的. 1.度序列:Sequence Degree,若把图G所有顶点的度数排成一个序列,责成该序列为图G的一个序列.该序列可以是非 ...
- html5之一些通用属性
dir属性:定义元素内容排序方式(rtl,ltr,auto)contentEditable属性:内容是否可编辑tabindex属性:按tab键激活元素 <!DOCTYPE html>< ...
- HTML的各种基本标签
一 .head中的各种标签 1. <!DOCTYPE html><html>文档类型声明 声明当前文件是一个HTML5文件文档 ...
- 华为防火墙USG5500-企业双ISP出口
需求:(1)技术部IP地址自动获取,网段为192.168.10.0/24,该部门访问Internet的报文正常情况下流入链路ISP1. 总经办IP地址自动获取,网段为192.168.20.0/24,该 ...
- JVMGC机制
GC 是JVM的垃圾回收器.与C/C++不同,java程序员无需考虑太多内存分配的位置,更不用考虑内存释放的机制,java对象内存的申请和释放都有JVM托管.JVM的内存释放机制就是GC. GC的过程 ...