RabbitMQ知识体系
1.MQ消息队列
1.1.什么是MQ
消息队列(MQ):生产者/消费者模型
生产者:生产者不断向消息队列中生产消息
消费者:消费者不断从消息队列获取消息
1.2.实现方式
MQ是通信模型,实现MQ两种主流方式:AMQP、JMS
两者区别/联系:
- JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式。
- JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
- JMS规定了两种消息模型;而AMQP的消息模型更加丰富
1.3 常见产品
- ActiveMQ:基于JMS
- RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
- RocketMQ:基于JMS,阿里巴巴产品,目前交由Apache基金会
- Kafka:分布式消息系统,高吞吐量
2.RabbitMQ
2.1 RabbitMQ概述
2.1.1 什么是RabbitMQ
RabbitMQ是由erlang语言开发,基于AMQP协议实现的消息队列
2.1.2 RabbitMQ工作原理
组成部分说明如下
- Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue。
- Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
- Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。
- Producer:消息生产者,即生产方客户端,生产方客户端将消费发送到MQ。
- Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。
2.1.3 消息流程
- 发送消息流程
a.生产者和Broker建立TCP连接。
b.生产者和Broker建立通道。
c.生产者通过通道消息发送Broker,由Exchange将消息进行转发。
d.Exchange将消息转发到指定的Queue(队列)。
- 接收消息流程
a.消费者和Broker建立TCP连接。
b.消费者和Broker建立通道。
c.消费者监听指定的Queue(队列)。
d.当有消息到达Queue时Broker默认将消息推送给消费者。
2.1.4 工作模式(六种)
1、Simple简单模式:
一个队列中一条消息,只能被一个消费者消费
2、Work queues:
工作队列(一个生产者,多个消费者,每个消费者获取到的消息唯一)
a)rabbit采用轮询的方式将消息是平均发送给消费者的。
3、Publish/Subscribe:
发布订阅模式(一个生产者发送的消息被多个消费者获取)
a)每个消费者监听自己的队列。
b)生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息
4、Routing:
路由模式(生产者发送的消息主要根据定义的路由规则决定往哪个队列发送)
a)每个消费者监听自己的队列,并且设置routingkey。
b)生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列。
5、Topics:
主题(模糊匹配路由规则,多个队列,多个消费者)
a)每个消费者监听自己的队列,并且设置带统配符的routingkey。
b)生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列。
6、RPC:
客户端远程调用服务端的方法 ,使用MQ可以实现RPC的异步调用,基于Direct交换机实现
a)客户端即是生产者就是消费者,向RPC请求队列发送RPC调用消息,同时监听RPC响应队列。
b)服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果
c)服务端将RPC方法 的结果发送到RPC响应队列
d)客户端(RPC调用方)监听RPC响应队列,接收到RPC调用结果。
2.1.5 交换机类型(四种)
1.Direct Exchange(直连交换机)
处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “abc”,则只有被标记为“abc”的消息才被转发,不会转发abc.def,也不会转发dog.ghi,只会转发abc。
2.Fanout Exchange(扇出交换机,广播模式)
不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。
3.Topic Exchange(主题交换机)
将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配一个或多个词,符号“”匹配不多不少一个词。因此“abc.#”能够匹配到“abc.def.ghi”,但是“abc.” 只会匹配到“abc.def”。
4.Header Exchenge(头交换机)
不处理路由键。而是根据发送的消息内容中的headers属性进行匹配。在绑定Queue与Exchange时指定一组键值对;当消息发送到RabbitMQ时会取到该消息的headers与Exchange绑定时指定的键值对进行匹配;如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers属性是一个键值对,可以是Hashtable,键值对的值可以是任何类型。而fanout,direct,topic 的路由键都需要要字符串形式的。
匹配规则x-match有下列两种类型:
x-match = all :表示所有的键值对都匹配才能接受到消息
x-match = any :表示只要有键值对匹配就能接受到消息
2.2 Spingboot整合RabbitMQ
2.2.0 YML配置RabbitMQ
#配置 rabbitMq 服务器
rabbitmq:
# 虚拟主机 virtual-host 可以理解为每指定一个 virtual-host 就相当于设置了一个 RabbitMQ 服务器,不同的服务器是分离执行的,不同的 virtual-host 拥有的权限和其他配置也不同
virtualHost: /
#设置RabbitMQ的IP地址
host: 192.168.1.4
#设置rabbitmq服务器连接端口(应用访问端口号是5672,不是控制面板端口号15672)
port: 5672
#设置rabbitmq服务器用户名 本地搭建对应的账户密码都是 guest
username: guest
password: guest
#确认消息已发送到交换机(Exchange)
publisher-confirms-type: correlated
#确认消息已发送到队列(Queue)
publisher-returns: true
2.2.1 直连模式
配置类
package com.asher.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectRabbitConfig {
//队列 起名:TestDirectQueue
@Bean
public Queue TestDirectQueue() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
// return new Queue("TestDirectQueue",true,true,false);
//一般设置一下队列的持久化就好,其余两个就是默认false
return new Queue("TestDirectQueue", true);
}
//Direct交换机 起名:TestDirectExchange
@Bean
DirectExchange TestDirectExchange() {
// return new DirectExchange("TestDirectExchange",true,true);
return new DirectExchange("TestDirectExchange", true, false);
}
//绑定 将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
// (队列与交换机是通过路由来进行绑定的,所以在传值的时候,只需要穿交换机的名称以及绑定路由的名称,交换机会自动通过路由来将消息分配到指定队列)
@Bean
Binding bindingDirect() {
// 路由键routing-key为TestDirectRouting
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
}
}
生产者
@Resource
private RabbitTemplate rabbitTemplate;
/**
* direct exchange(直连型交换机) 生产者
*/
@RequestMapping("/directSend")
@ResponseBody
public void directSend() {
String body = "消息体内容";
rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", body);
System.out.println("rabbitmq发送直连模式消息成功。。。");
}
消费者
package com.asher.controller.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "TestDirectQueue")
public class DirectReceiver {
@RabbitHandler
public void process(String message) {
System.out.println("第一个DirectReceiver消费者收到消息 : " + message);
}
}
2.2.2 广播模式
配置类
package com.asher.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutRabbitConfig {
@Bean
public Queue queueA() {
return new Queue("fanout.A");
}
@Bean
public Queue queueB() {
return new Queue("fanout.B");
}
@Bean
public Queue queueC() {
return new Queue("fanout.C");
}
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");
}
@Bean
Binding bindingExchangeA() {
return BindingBuilder.bind(queueA()).to(fanoutExchange());
}
@Bean
Binding bindingExchangeB() {
return BindingBuilder.bind(queueB()).to(fanoutExchange());
}
@Bean
Binding bindingExchangeC() {
return BindingBuilder.bind(queueC()).to(fanoutExchange());
}
}
生产者
@Resource
private RabbitTemplate rabbitTemplate;
/**
* fanout exchange(扇形交换机,广播模式) 生产者
*/
@RequestMapping("/fanoutSend")
@ResponseBody
public void fanoutSend() {
String body = "消息体内容";
// 将数据添加进队列(根据性别,使用不同的routeKey)
rabbitTemplate.convertAndSend("fanoutExchange", null, body);
System.out.println("rabbitmq发送广播模式消息成功。。。");
}
消费者1
package com.asher.controller.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "fanout.A")
public class FanoutReceiverA {
@RabbitHandler
public void process(String message) {
System.out.println("FanoutReceiverA消费者收到消息 :" + message);
}
}
消费者2
package com.asher.controller.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "fanout.B")
public class FanoutReceiverB {
@RabbitHandler
public void process(String message) {
System.out.println("FanoutReceiverB消费者收到消息 :" + message);
}
}
2.2.3 主题模式
配置类
package com.asher.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TopicRabbitConfig {
public final static String MAN = "topic.man";
public final static String WOMAN = "topic.woman";
@Bean
public Queue firstQueue() {
return new Queue(TopicRabbitConfig.MAN);
}
@Bean
public Queue secondQueue() {
return new Queue(TopicRabbitConfig.WOMAN);
}
@Bean
TopicExchange exchange() {
return new TopicExchange("topicExchange");
}
//将firstQueue和topicExchange绑定,而且绑定的键值为topic.man
//这样只要是消息携带的路由键是topic.man,才会分发到该队列
@Bean
Binding bindingExchangeMessage() {
return BindingBuilder.bind(firstQueue()).to(exchange()).with(MAN);
}
//将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#
// 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列
@Bean
Binding bindingExchangeMessage2() {
return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");
}
}
生产者
@Resource
private RabbitTemplate rabbitTemplate;
/**
* topic exchange(主题交换机) 生产者
*/
@RequestMapping("/topicSend")
@ResponseBody
public void topicSend() {
String body = "消息体内容";
//随机获取true 或 false
Random random = new Random();
if (random.nextBoolean()) {
rabbitTemplate.convertAndSend("topicExchange", TopicRabbitConfig.MAN, body);
} else {
rabbitTemplate.convertAndSend("topicExchange", TopicRabbitConfig.WOMAN, body);
}
System.out.println("rabbitmq发送主题模式消息成功。。。");
}
消费者1
package com.asher.controller.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "topic.man")
public class TopicManReceiver {
@RabbitHandler
public void process(String message) {
System.out.println("TopicManReceiver消费者收到消息 : " + message);
}
}
消费者2
package com.asher.controller.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "topic.woman")
public class TopicWoManReceiver {
@RabbitHandler
public void process(String message) {
System.out.println("TopicWoManReceiver消费者收到消息 : " + message);
}
}