超级详细的RabbitMQ入门篇(有代码!有代码!有代码!)
1.1. MQ的相关概念
1.1.1 什么是MQ
本质就是一个队列,只不过队列里存放的是message而已,一种跨进程的通信机制,用于上下游传递消息,是一种"逻辑解耦+物理解耦"的消息通信服务,使用其后,消息发送上游只需要依赖MQ,不用依赖服务本身。
1.1.2 为什么要用MQ
1. 流量削峰
利用消息队列来做缓冲,将某一秒的大量请求放在队列中做缓冲,分散成一段时间来进行处理,这样做可能影响用户的使用体验,因为客户的请求也许一段时间后才会被真正的去进行业务逻辑处理,但是也比系统崩掉强。
2. 应用解耦
例如,用户登录A系统后,需要像B系统发送请求,记录当前登录用户的信息日志,如果这个时候B挂掉了,那这个调用保存日志的接口就会报错,
如果使用了MQ,信息就会保存在队列中,过了几分钟后,当B正常启动后,B系统监听消息队列的类或者方法依然能够正常的获取到刚刚发送过来的消息,进行日志保存的操作。并且不会影响实施监督其他操作的正常执行。
3. 异步处理
有些服务间调用是异步的,例如A调用B,B需要花很长时间去执行,但是A需要知道B什么时候能执行完,以前一般都有俩种方式,A每隔一段时间去调用B的查询api去查询,或者A提供一个callback api,B执行完之后调用api通知A服务,这俩种方式都处理不是很优雅。
使用MQ可以解决这个问题,A调用B服务后,只需要监听B处理完成的消息,当B处理完成后,会发送一条消息给MQ,MQ会将此消息转发给A服务,这样A服务就不哟循环调用B的查询API,也不用提供一个回调函数,同样B也不需要这些操作,A服务还能及时的得到异步处理成功的消息。
1.1.3. MQ的分类
1.MQ对比图
(网上找的图,大家自行对比一下各个消息中间件的优劣好处,根据自己业务进行技术选型)
1.2 RabbitMQ
1.2.1. RabbitMQ的概念
基于AMQP 协议,erlang语言开发,是部署最广泛的开源消息中间件,是最受欢迎的开源消息中间件之一。
1.2.2. 四大核心概念
1. 生产者
产生数据发送消息的程序时生产者。
2. 交换机(Exchange)
消息枢纽,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。交换机的一类一般会有4种
2.1 Direct Exchange
直连交换机:将一个或多个队列绑定到交换机上,该交换机会通过你设置的路由key(routingKey)去往指定的队列种投送消息,消费者再通过监听指定的某个队列去获取到消息。实现消息的传递。
2.2 fanout交换机
扇出型交换机:生产者将消息发送给交换机后,fanout交换机 会以广播的形式,把消息发送给绑定该交换机上的每一个队列,例如:用户注册了账号密码后,需要将注册成功的消息发送到用户的手机和邮件上,只需要将手机这个队列以及邮件这个队列绑定到该交换机上,消费者监听这俩个队列后,就知道用户注册了信息,就需要去做出相应的业务操作。
2.3 topic交换机
主题交换机:该交换机与direcrt交换机有点类似,也是通过匹配路由键的形式往指定的消息的队列去发送消息,但是它与
direcrt交换机不同的地方在于,它的匹配原则更加的灵活多变,有点类似与模糊匹配。路由键(routeing key)种有3种特殊符号分别为 : ***** ,# , .(点)。
其中:*****指代的 就是有且只有一个占位符,# 代表这0个或者多个占位符, **.**用于分割路由键。
例如: 现在有三个队列绑定在topic交换机中,绑定的路由键分别为: #.sms.# , #.blog.#,#.email.*
现在生产者需要发送消息。如图
根据匹配原则, #.sms.# , #.blog.#,#.email.* 三者队列都会拿到交换机投递的 消息。
那如果routingkey变成com.sms.blog.email了呢 。这种情况下,那么能拿到的消息的队列就是 #.sms.#, #.blog.#,而#.email.*在交换机那拿不到信息,因为 * 号代表email后面必须指定一段路由。而email后面没有多余的路由了,所以就导致了交换机与该队列并没有绑定成功。
2.4 Header交换机
头交换机,不处理路由键。而是根据发送的消息内容中的headers属性进行匹配。在绑定Queue与Exchange时指定一组键值对;当消息发送到RabbitMQ时会取到该消息的headers与Exchange绑定时指定的键值对进行匹配;如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers属性是一个键值对,可以是Hashtable,键值对的值可以是任何类型。而fanout,direct,topic 的路由键都需要要字符串形式的。
匹配规则x-match有下列两种类型:
x-match = all :表示所有的键值对都匹配才能接受到消息
x-match = any :表示只要有键值对匹配就能接受到消息
3. 队列(queue)
RabbitMQ内部使用的一种数据结构,本质上是一个消息缓冲区域,可以接收交换机派发的多个生产者的消息,也可以将消息发送给多个消费者。同时一个队列也可以绑定多个不同类型的交换机。
4. 消费者
大多时候是一个等待接收消息的程序,生产者,消费者,消息中间件很多时候都不是在同一台机器上,同一个应用程序既可以是生产者也可以是消费者。
1.2.2. RabbitMQ的消息模型
1. 简单模式
一个队列一个或多个消费者,当多个消费者同时监听一个队列时,他们不能同时消费一条消息,而是随机消费消息,即一个队列中的一条消息,只能被一个消费者消费。
1.1 配置类
java
@Configuration
public class RabbitSimpleConfig {
public static final String SIMPLE_QUEUE = "simple_queue";
@Bean
public Queue simpleQueue(){
/**
* 参数明细
* 1.队列的名称
* 2.是否持久化
*/
return new Queue(SIMPLE_QUEUE, true, false, false);
}
}
1.2 生产者
java
@Component
public class RabbitSimpleService {
@Autowired
RabbitTemplate rabbitTemplate;
public void sendSimpleMessage(){
String msg = "简单模式------------当前时间为:" + new Date().getTime();
//这里交换机为空,但是rabbitmq会选择一个direct默认交换机。
rabbitTemplate.convertAndSend("",RabbitSimpleConfig.SIMPLE_QUEUE,msg);
System.out.println("简单模式-----信息发送成功");
}
}
1.3 消费者
java
@Component
public class RabbitSimpleConsumer {
@RabbitListener(queues = "simple_queue")
public void simpleReceiveMsg(String msg){
System.out.println("simple模式----消费者1接收到消息为:" + msg);
}
}
1.4 结果图
注意:俩个消费者同时监听的一个队列,但是只有一个消费者能拿到消息去消费。
2. 工作模式(Work Queues)
Work Queues与简单模式相比,多了一个或一些消费端,多个消费端共同消费一个队列中的消息,默认情况下,RabbitMQ将按顺序将每个消息发送给 下一个使用者。平均而言,每个消费者都会收到相同数量 的消息。这种分发消息的方式称为循环
应用场景:对于任务国中或任务较多情况使用工作队列可以提高任务处理的速度。
2.1 配置类
java
@Configuration
public class RabbitWorkConfig {
public static final String WORK_QUEUE = "work_queue";
@Bean
public Queue workQueue(){
/**
* 参数明细
* 1.队列的名称
* 2.是否持久化
* 3.
* 4.是否自动删除
*/
return new Queue(WORK_QUEUE, true, false, false);
}
}
2.2 生产者
java
@Component
public class RabbitWorkService {
@Autowired
RabbitTemplate rabbitTemplate;
public void sendWorkMessage() {
for (int i = 0; i < 10; i++) {
String msg = i + "--工作模式------------当前时间为:" + new Date().getTime();
rabbitTemplate.convertAndSend("", RabbitWorkConfig.WORK_QUEUE, msg);
System.out.println("工作模式-----信息发送成功--" + i);
}
}
}
2.3 消费者
默认为消费者轮询消费信息
java
@Component
public class RabbitWorkConsumer {
@RabbitListener(queues = "work_queue")
public void simpleReceiveMsg(String msg){
System.out.println("work模式----消费者1接收到消息为:" + msg);
}
@RabbitListener(queues = "work_queue")
public void simpleReceiveMsg2(String msg){
System.out.println("work模式----消费者2接收到消息为:" + msg);
}
}
2.4 效果图
3. 发布订阅模式
发布订阅模式: 1.每个消费者监听自己的队列。
2.生产者将消息发给交换机,由交换机发给指定绑定的队列,这样保证每个绑定交换机队列 都可以收到消息
相比与前面的模型,多了一个exhange交换机的角色,但是实际上前面也用到了交换机,只不过我们并没有指定出来,如果没有去指定就会去使用rabbitmq提供的默认交换机default.direct
一般发布订阅模式都会使用fanout交换机去进行使用。
3.1 配置类
java
@Configuration
public class RabbitMQFanoutConfig {
//1.声明创建交换机
@Bean
public FanoutExchange fanoutExchange(){
/**
* 参数明细
* 1.交换机的名称
* 2.是否持久化
* 3.是否自动删除
*/
return new FanoutExchange(RabbitConstant.FANOUT_EXCHANGES, true, false);
}
//2.声明队列
//发送短信的队列
@Bean
public Queue smsQueue(){
return new Queue(RabbitConstant.QUEUE_FORM_SMS,true);
}
//发送博客的队列
@Bean
public Queue blogQueue(){
return new Queue(RabbitConstant.QUEUE_FORM_BLOG,true);
}
//发送邮件的队列
@Bean
public Queue emailQueue(){
return new Queue(RabbitConstant.QUEUE_FORM_EMAIL,true);
}
//3.绑定交换机和队列
@Bean
public Binding smsBinding(){
return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
}
@Bean
public Binding blogBinding(){
return BindingBuilder.bind(blogQueue()).to(fanoutExchange());
}
@Bean
public Binding emailBinding(){
return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
}
}
3.2 生产者
java
public void sendFanoutMsg(){
String msg = "注册成功了,发起fanout模式的消息,您的用户是张三,id是"+ UUID.randomUUID();
rabbitTemplate.convertAndSend(RabbitConstant.FANOUT_EXCHANGES,"",msg);
System.out.println("发送消息成功");
}
3.3 消费者
java
@Service
public class RabbitFanoutComsumer {
@RabbitListener(queues = {RabbitConstant.QUEUE_FORM_BLOG})
public void blogReceiveMsg(String message){
System.out.println("博客消费者收到了消息:" + message);
}
//1.@RabbitListener标注在方法上,直接监听指定的队列,此时接收的参数需要与发送市类型一致
//2.@RabbitListener 标注在类上面表示当有收到消息的时候,就交给 @RabbitHandler 的方法处理,根据接受的参数类型进入具体的方法中
@RabbitListener(queues = {RabbitConstant.QUEUE_FORM_SMS})
public void ssmReceiveMsg(String message){
System.out.println("短信消费者收到了消息:" + message);
}
@RabbitListener(queues = {RabbitConstant.QUEUE_FORM_EMAIL})
public void emailReceiveMsg(String message){
System.out.println("邮件消费者收到了消息:" + message);
}
}
3.4 效果图
4. routing模式
路由模式:一般用于direct交换机,队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey 。当生产者向交换机递交消息的时候,会传递一个RoutingKey,交换机再根据路由键去进行匹配接收消息的队列。
4.1 配置类
java
@Configuration
public class RabbitMQDirectConfig {
//1.声明创建交换机
@Bean
public DirectExchange directExchange(){
/**
* 参数明细
* 1.交换机的名称
* 2.是否持久化
* 3.是否自动删除
*/
return new DirectExchange(RabbitConstant.DIRECT_EXCHANGES, true, false);
}
//2.声明队列
//发送短信的队列
@Bean
public Queue smsDirectQueue(){
return new Queue(RabbitConstant.QUEUE_FORM_SMS,true);
}
//发送博客的队列
@Bean
public Queue blogDirectQueue(){
return new Queue(RabbitConstant.QUEUE_FORM_BLOG,true);
}
//发送邮件的队列
@Bean
public Queue emailDirectQueue(){
/**
* 参数明细
* 1.队列的名称
* 2.是否持久化
*/
return new Queue(RabbitConstant.QUEUE_FORM_EMAIL,true);
}
//3.绑定交换机和队列
@Bean
public Binding smsDirectBinding(){
return BindingBuilder.bind(smsDirectQueue()).to(directExchange()).with("sms");
}
@Bean
public Binding blogDirectBinding(){
return BindingBuilder.bind(blogDirectQueue()).to(directExchange()).with("blog");
}
@Bean
public Binding emailDirectBinding(){
return BindingBuilder.bind(emailDirectQueue()).to(directExchange()).with("email");
}
}
4.2 生产者
java
//发送字符串类型的数据
public void sendDirectMsg(){
String msg = "注册成功了,发起direct模式的消息,您的用户是张三,id是"+ UUID.randomUUID();
//给短信发送消息
rabbitTemplate.convertAndSend(RabbitConstant.DIRECT_EXCHANGES,"sms",msg);
//给博客发送消息
rabbitTemplate.convertAndSend(RabbitConstant.DIRECT_EXCHANGES,"blog",msg);
System.out.println("发送消息成功");
}
4.3 消费者
java
@RabbitListener(queues = {RabbitConstant.QUEUE_FORM_BLOG})
public class RabbitBlogDirectConsumer {
@RabbitHandler
public void receiveMsg(String msg){
System.out.println("博客消费者接收到消息:" + msg);
}
@RabbitHandler
public void receiveMsg(byte[] msg){
System.out.println("博客消费者接收到消息:" + new String(msg));
}
}
@RabbitListener(queues = {RabbitConstant.QUEUE_FORM_EMAIL})
public class RabbitEmailDirectConsumer {
@RabbitHandler
public void receiveMsg(String msg){
System.out.println("邮件消费者接收到消息:" + msg);
}
@RabbitHandler
public void receiveMsg(byte[] msg){
System.out.println("邮件消费者接收到消息:" + new String(msg));
}
}
@RabbitListener(queues = {RabbitConstant.QUEUE_FORM_SMS})
public class RabbitSMSDirectConsumer {
@RabbitHandler
public void receiveMsg(String msg){
System.out.println("短信消费者接收到消息:" + msg);
}
@RabbitHandler
public void receiveMsg(byte[] msg){
System.out.println("短信消费者接收到消息:" + new String(msg));
}
}
4.4 效果图
5. topic模式
主题模式:一般使用主题交换机,Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型Exchange 可以让队列在绑定 Routing key 的时候使用通配符
Routingkey 一般都是有一个或多个单词组成,多个单词之间以"."分割,例如: sms.blog
通配符规则: 1.# :匹配一个或多个词 2.* :匹配不多不少恰好1个词
5.1 配置类
java
@Configuration
public class RabbitMQTopicConfig {
@Bean
public TopicExchange topicExchange(){
return new TopicExchange(RabbitConstant.TOPIC_EXCHANGES);
}
//2.声明队列
//发送短信的队列
@Bean
public Queue smsTopicQueue(){
return new Queue(RabbitConstant.QUEUE_FORM_SMS,true);
}
//发送博客的队列
@Bean
public Queue blogTopicQueue(){
return new Queue(RabbitConstant.QUEUE_FORM_BLOG,true);
}
//发送邮件的队列
@Bean
public Queue emailTopicQueue(){
return new Queue(RabbitConstant.QUEUE_FORM_EMAIL,true);
}
//3.绑定交换机和队列
@Bean
public Binding smsTopicBinding(){
//其中 # 代表这匹配0个,一个或者多个, *代表有且只能一个
return BindingBuilder.bind(smsTopicQueue()).to(topicExchange()).with("#.sms.#");
}
@Bean
public Binding blogTopicBinding(){
return BindingBuilder.bind(blogTopicQueue()).to(topicExchange()).with("#.blog.#");
}
@Bean
public Binding emailTopicBinding(){
return BindingBuilder.bind(emailTopicQueue()).to(topicExchange()).with("#.email.*");
}
}
5.2 生产者
java
public void sendTopicMsg(){
String msg = "注册成功了,发起Topic模式的消息,您的用户是张三,id是"+ UUID.randomUUID();
//给短信发送消息
rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_EXCHANGES,"com.blog.email.sms",msg.getBytes());
System.out.println("发送消息成功");
}
5.3 消费者
java
@RabbitListener(queues = {RabbitConstant.QUEUE_FORM_BLOG})
public class RabbitBlogTopicConsumer {
@RabbitHandler
public void receiveMsg(String msg){
System.out.println("博客消费者接收到topic消息:" + msg);
}
@RabbitHandler
public void receiveMsg(byte[] msg){
System.out.println("博客消费者接收到topic消息:" + new String(msg));
}
}
@RabbitListener(queues = {RabbitConstant.QUEUE_FORM_EMAIL})
public class RabbitEmailTopicConsumer {
@RabbitHandler
public void receiveMsg(String msg){
System.out.println("邮件消费者接收到topic消息:" + msg);
}
@RabbitHandler
public void receiveMsg(byte[] msg){
System.out.println("邮件消费者接收到topic消息:" + new String(msg));
}
}
@RabbitListener(queues = {RabbitConstant.QUEUE_FORM_SMS})
public class RabbitSMSTopicConsumer {
@RabbitHandler
public void receiveMsg(String msg){
System.out.println("短信消费者接收到topic消息:" + msg);
}
@RabbitHandler
public void receiveMsg(byte[] msg){
System.out.println("短信消费者接收到topic消息:" + new String(msg));
}
}
5.4 效果图
6.RPC模式
对于某些业务场景,我们希望在使用rabbitMQ后,不仅仅只是需要通知到服务端,也需要服务端给客户端一个返回的结果,这里就需要使用到RPC模式。(这个事实上工作会用的很少,要了解的就自力更生了哦~)
1.2.3 消息确认机制
为了保证消息队列的可靠的达到消费者,RabbitMQ提供了消息的确认机制,
(1)手动确认
消费者在订阅队列中,可以指定autoAck的参数,如果该参数为false,则表示为手动确认,rabbitMq会等待消费者显式地回复确认信号才从内存(或者磁盘中)移除。
采用消息确认机制后,只要设置autoaAck参数为false,消费者就会有足够的时间去处理业务逻辑,不用担心消费者再处理消息的时候突然挂掉导致消失丢失的问题,因为rabbitMQ会一直等待消费者去显示的调用确认的方法,并且rabbitMQ也不会为未确认的消息设置过期的时间
(2)自动确认
如果为true,RabbitMQ会自动把消息设置为确认,然后在内存中删除,而不管消费者是否真的消费完成。这就是自动自动确认
最后
大致就介绍这么多了,大部分都是一些对rabbitMQ一些基础的使用和了解,其实实际工作中需要了解的不仅于此,后续有空还会补充一些rabbitMQ一些更全面的功能,以及一些实际中功能的实践。