一、Message Broker与AMQP简介
1.1 Message Broker
Message Broker是一种消息验证、传输、路由的架构模式,其设计目标主要应用于下面这些场景:
- 消息路由到一个或多个目的地
- 消息转化为其他的表现方式
- 执行消息的聚集、消息的分解,并将结果发送到他们的目的地,然后重新组合相应返回给消息用户
- 调用Web服务来检索数据
- 响应事件或错误
- 使用发布-订阅模式来提供内容或基于主题的消息路由
1.2 AMQP
AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
AMQP定义了这些特性:
- 消息方向
- 消息队列
- 消息路由(包括:点到点和发布-订阅模式)
- 可靠性
- 安全性
二、Rabbit MQ 简介
Rabbit MQ中文文档:http://rabbitmq.mr-ping.com/
RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。RabbitMQ主要是为了实现系统之间的双向解耦而实现的。当生产者大量产生数据时,消费者无法快速消费,那么需要一个中间层。保存这个数据。
RabbitMQ,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
RabbitMQ 即一个消息队列,主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲,消息分发的作用。
一个Message的处理流程类似于下图:
Exchange接收消息生产者(MessageProducer)发送的消息根据不同的路由算法将消息发送往Message queue。Messagequeue会在消息不能被正常消费时缓存这些消息,具体的缓存策略由实现者决定,当message queue与消息消费者(Messageconsumer)之间的连接通畅时,Message queue有将消息转发到consumer的责任。
交换机有四种类型,分别为Direct,topic,headers,Fanout.
Direct是RabbitMQ默认的交换机模式,也是最简单的模式.即创建消息队列的时候,指定一个BindingKey.当发送者发送消息的时候,指定对应的Key.当Key和消息队列的BindingKey一致的时候,消息将会被发送到该消息队列中.
topic转发信息主要是依据通配符,队列和交换机的绑定主要是依据一种模式(通配符+字符串),而当发送消息的时候,只有指定的Key和该模式相匹配的时候,消息才会被发送到该消息队列中.
headers也是根据一个规则进行匹配,在消息队列和交换机绑定的时候会指定一组键值对规则,而发送消息的时候也会指定一组键值对规则,当两组键值对规则相匹配的时候,消息会被发送到匹配的消息队列中.
Fanout是路由广播的形式,将会把消息发给绑定它的全部队列,即便设置了key,也会被忽略.不处理route key 全网发送,所有绑定的队列都发送
三、springboot集成RabbitMQ
3.1 简单使用
1、配置pom包,主要是添加spring-boot-starter-amqp的支持
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、配置文件
配置rabbitmq的安装地址、端口以及账户信息
spring.application.name=spirng-boot-rabbitmq
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
3、队列配置
@Configuration
public class RabbitConfig {
@Bean
public Queue Queue() {
return new Queue("hello");
}
}
3、发送者
RabbitTemplate是springboot 提供的默认实现
@Component
public class HelloSender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void send(String context) {
System.out.println("Sender : " + context);
this.rabbitTemplate.convertAndSend("hello", context);
}
}
3、接收者
添加RabbitListener注解,监听hello(发送者和接收者的queue name必须一致)队列;RabbitHandler具体消息处理(消费)方法。
@Component
@RabbitListener(queues = "hello")
public class HelloReceiver {
@RabbitHandler
public void process(String hello) {
System.out.println("Receiver : " + hello);
}
}
5、测试
编写HelloController或者HelloSenderTest进行测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloSenderTest {
@Autowired
private HelloSender helloSender;
@Test
public void sendHello() {
helloSender.send("Test By Payne");
}
}
控制台输出结果:
Sender : Test By Payne
Receiver : Test By Payne
3.2 一对多发送
1、添加2个接收者
@Component
@RabbitListener(queues = "neo")
public class NeoReceiver1 {
@RabbitHandler
public void process(String neo) {
System.out.println("Receiver 1: " + neo);
}
}
@Component
@RabbitListener(queues = "neo")
public class NeoReceiver2 {
@RabbitHandler
public void process(String neo) {
System.out.println("Receiver 2: " + neo);
}
}
2、发送者:
@Component
public class NeoSender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void send(int i) {
String context = "spirng boot neo queue"+" ****** "+i;
System.out.println("Sender1 : " + context);
this.rabbitTemplate.convertAndSend("neo", context);
}
}
3、测试类
@Test
public void oneToMany() throws Exception {
for (int i=0;i<100;i++){
neoSender.send(i);
}
}
4、结果
Receiver 1: spirng boot neo queue ****** 70
Receiver 1: spirng boot neo queue ****** 72
Receiver 1: spirng boot neo queue ****** 74
Receiver 2: spirng boot neo queue ****** 71
Receiver 1: spirng boot neo queue ****** 76
Receiver 2: spirng boot neo queue ****** 73
Receiver 1: spirng boot neo queue ****** 78
Receiver 2: spirng boot neo queue ****** 75
Receiver 2: spirng boot neo queue ****** 77
Receiver 2: spirng boot neo queue ****** 79
结论:
一个发送者,N个接受者,经过测试会均匀的将消息发送到N个接收者中
3.3 多对多发送
@Test
public void manyToMany() throws Exception {
for (int i=0;i<100;i++){
neoSender.send(i);
neoSender2.send(i);
}
}
结论:和一对多一样,接收端仍然会均匀接收到消息
3.4 Object的支持
1、添加Object
public class User implements Serializable{
private String name;
private String pass;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
2、添加Sender
@Component
public class ObjectSender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void send(User user) {
System.out.println("Sender object: " + user.toString());
this.rabbitTemplate.convertAndSend("object", user);
}
}
3、添加Receiver
@Component
@RabbitListener(queues = "object")
public class ObjectReceiver {
@RabbitHandler
public void process(User user) {
System.out.println("Receiver object : " + user);
}
}
4、添加Test
@RunWith(SpringRunner.class)
@SpringBootTest
public class ObjectTest {
@Autowired
private ObjectSender sender;
@Test
public void sendObject() throws Exception {
User user=new User();
user.setName("neo");
user.setPass("123456");
sender.send(user);
}
}
5、结果
Sender object: com.payne.model.User@6b530eb9[name=neo,pass=123456]
Receiver object : com.payne.model.User@1c465f9c[name=neo,pass=123456]
3.5 Topic Exchange
topic 是RabbitMQ中最灵活的一种方式,可以根据routing_key自由的绑定不同的队列
首先对topic规则配置,这里使用两个队列来测试
1、添加config
@Configuration
public class TopicRabbitConfig {
public static final String message = "topic.message";
public static final String messages = "topic.messages";
@Bean
public Queue queueMessage() {
return new Queue(TopicRabbitConfig.message);
}
@Bean
public Queue queueMessages() {
return new Queue(TopicRabbitConfig.messages);
}
@Bean
TopicExchange exchange() {
return new TopicExchange("exchange");
}
@Bean
Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) {
return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message");
}
@Bean
Binding bindingExchangeMessages(Queue queueMessages, TopicExchange exchange) {
return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#");
}
}
2、添加TopicSender
public void send1() {
String context = "hi, i am message 1";
System.out.println("Sender : " + context);
this.rabbitTemplate.convertAndSend("exchange", "topic.message", context);
}
public void send2() {
String context = "hi, i am messages 2";
System.out.println("Sender : " + context);
this.rabbitTemplate.convertAndSend("exchange", "topic.messages", context);
}
3、添加TopicReceiver 1 和 2
@Component
@RabbitListener(queues = "topic.message")
public class TopicReceiver {
@RabbitHandler
public void process(String message) {
System.out.println("Topic Receiver1 : " + message);
}
}
@Component
@RabbitListener(queues = "topic.messages")
public class TopicReceiver2 {
@RabbitHandler
public void process(String message) {
System.out.println("Topic Receiver2 : " + message);
}
}
结论:
发送send1会匹配到topic.#和topic.message 两个Receiver都可以收到消息,发送send2只有topic.#可以匹配所有只有Receiver2监听到消息
topic 和 direct 类似, 只是匹配上支持了”模式”, 在”点分”的 routing_key 形式中, 可以使用两个通配符:
- *表示一个词.
- #表示零个或多个词.
3.6 Fanout Exchange
Fanout 就是我们熟悉的广播模式或者订阅模式,给Fanout交换机发送消息,绑定了这个交换机的所有队列都收到这个消息。
1、添加config
@Configuration
public class FanoutRabbitConfig {
@Bean
public Queue AMessage() {
return new Queue("fanout.A");
}
@Bean
public Queue BMessage() {
return new Queue("fanout.B");
}
@Bean
public Queue CMessage() {
return new Queue("fanout.C");
}
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");
}
@Bean
Binding bindingExchangeA(Queue AMessage,FanoutExchange fanoutExchange) {
return BindingBuilder.bind(AMessage).to(fanoutExchange);
}
@Bean
Binding bindingExchangeB(Queue BMessage, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(BMessage).to(fanoutExchange);
}
@Bean
Binding bindingExchangeC(Queue CMessage, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(CMessage).to(fanoutExchange);
}
}
2、添加FanoutSender
@Component
public class FanoutSender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void send() {
String context = "hi, fanout msg ";
System.out.println("Sender : " + context);
this.rabbitTemplate.convertAndSend("fanoutExchange","", context);
}
}
2、添加Receiver A , B和C
FanoutReceiverA 使用@RabbitListener(queues = “fanout.A”)
FanoutReceiverB 使用@RabbitListener(queues = “fanout.B”)
FanoutReceiverC 使用@RabbitListener(queues = “fanout.C”)
@Component
@RabbitListener(queues = "fanout.A")
public class FanoutReceiverA {
@RabbitHandler
public void process(String message) {
System.out.println("fanout Receiver A : " + message);
}
}
3、测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class FanoutTest {
@Autowired
private FanoutSender sender;
@Test
public void fanoutSender() throws Exception {
sender.send();
}
}
5、结果
fanout Receiver B: hi, fanout msg
fanout Receiver C: hi, fanout msg
fanout Receiver A: hi, fanout msg
不处理route key 全网发送,只需要指定交换机,所有绑定的队列都发送。不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。