今年公司开始转型微服务架构,消息队列的选型是RabbitMQ,因此趁这个机会把RabbitMQ好好了解一下。
对于当前的消息队列的选型
当前可供选择的 消息队列有很多:rabbitMQ、kafka、rocketMQ 、zeroMQ、ActiveMQ
对于公司选择了rabbitMQ的理由如下:
使用rabbitMQ的目的大部分是子系统间的数据传递与同步,同时系统是toB的系统,对tps的要求不高
rabbitMQ 是用erlang语言编写
是实现了高级消息队列协议(AMQP)的消息中间件
优点:1.社区活跃度高
2.成熟度高: 可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统等等。
3.由于是erlang语言开发的,因此高并发支持较好
等等等
缺点:1.对消息堆积的支持并不好
2.对消息队列的性能要求非常高的场景 不适合
3.二次开发困难
等等等
我安装的版本是3.7.18,需要依赖erlang20.3以上的版本
安装步骤可参考:
https://www.cnblogs.com/dreasky/p/9146494.html
安装遇到的问题:
https://blog.51cto.com/10950710/2139572
安装控制台插件
rabbitmq-plugins enable rabbitmq_management
rabbitMQ的默认端口为5672
控制台的默认访问端口为15672
默认账号密码为guest/guest
如果登录有问题:
将ebin目录下rabbit.app中loopback_users里的<<"guest">>删除
消息生产者、channel、exchange、queue、消息消费者
mq的持久化意义在于当消息队列由于某些原因发生服务停止时,重启服务后,由于持久化的原因,交换器、队列以及消息依旧存在。持久化对于消息队列来说,十分重要。rabbitmq的持久化分为 队列持久化 和 消息持久化
队列持久化
查看Queue的源码,存在属性durable,代表队列是否持久化
对应控制台可以看到Features上显示了队列的durable属性已开启
交换器持久化
与队列一致
当队列与交换器持久化后,当重启rabbitMQ后,队列与交换器依旧存在
消息持久化
以下所有的列子都是Spring AMQP实现,Spring AMQP 是对原生的 RabbitMQ 客户端的封装。
Spring AMQP实现 默认是消息持久化,可参考如下:
http://blog.720ui.com/2017/rabbitmq_action_durable/
将消息标记为持久性并不能完全保证不会丢失消息。尽管它告诉RabbitMQ将消息保存到磁盘,但是RabbitMQ接受消息并且尚未保存消息时,还有很短的时间,它可能只是保存到缓存中,而没有真正写入磁盘。如果需要确保消息发送成功,则可以使用发布者确认。
发送确认分为两步,一是确认是否到达交换器,二是确认是否到达队列。
配置文件增加:
spring.rabbitmq.publisher-confirms=true
创建回调函数类,实现RabbitTemplate.ConfirmCallback接口
设置RabbitTemplate的回调函数属性
以上即可实现生产者消息确认。
默认是消费者确认模式为自动,可以设置为手动模式,并通过api手动确认消息被正确收到。
配置文件
spring.rabbitmq.listener.simple.acknowledge-mode=manual // 将消费者确认模式设置为手动
消费者的处理函数中使用channel.basicAck来确认消息收到
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
对于入参的解释:
deliveryTag
它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel
对于rabbitmq的工作模式,官网介绍有七种:
https://www.rabbitmq.com/getstarted.html
1.简单模式
只使用队列 一个生产者发送消息至队列 一个消费者从队列消费消息
该模式较为简单,就不多介绍了
2.work模式
当一个生产者发送消息至队列,但存在多个消费者监听队列时,则消息会公平的分到每个消费者身上。分配消息的策略为 Round-Robin(轮询)
如果把这个work模式的场景想像成将一大批任务分配个多个工人,但是这种轮询的方式往往不是真正的公平,因为rabbitmq不清楚任务的难易程度,难易程度决定了工人完成这个任务所需的时间。从而可以引入手动确认和当前信道最大预获取(prefetchCount)。
手动确认上文已经说明过了,对于当前信道最大预获取(prefetchCount),说明了当前消费者的最大处理能力,当消费者收到消息后,正在处理,未手动确认后,当正在处理的数量等于prefetchCount时,则不再为消费者发送消息,直至正在处理的数量小于prefetchCount后,再发送消息。
3.发布/订阅模式
创建fanoutExchange
然后bind queue与exchange
所有监听这个队列的消费者都会收到消息
消息消费者代码:
4.路由模式
创建directExchange
然后bind queue与exchange,绑定时需要带上routingKey。
消息生产者在发送消息时,需要带上routingKey
因为当前队列与交换器绑定时,routingkey为topic.sdp,因此消息会发送到该队列上,若发送消息时,带上的routingkey不一致,则该队列收不到该消息。
5.主题模式
创建topicExchange
然后bind queue与exchange,绑定时需要带上routingKey
主题模式下,routingkey是支持通配的,规则如下:
*号可以代表任意一个单词,#可以代表零个或是多个单词
消息生产者在发送消息时,同样带上routingkey
由于发送消息的routingkey与binding绑定的routingkey匹配,因此队列能收到该消息,当有多个队列的routingkey匹配时,则这些队列都会收到消息。
6.RPC模式
以下例子为异步rpc模式
需要自行在配置类中添加AsyncRabbitTemplate的bean
消息生产者发送消息,需要设置回调函数:
消息的消费者:
消息的生产者获取消费者的返回值,并在回调函数中处理。
7.消费者确认模式
笔者不太清楚官网把消费者确认看做是一种工作模式,上面已经提到过了,不在赘述。
另外还有一块是官网中没有提到的,header exchange
header exchange
创建headerExchange
将exchange与queue绑定,这里可以选择匹配规则,是全部匹配还是部分匹配
消息生存者发送消息时:
当匹配规则为部分匹配时,消息的header中 部分匹配,则消息能够到达队列,若匹配规则为全部匹配,则消息不能够达到队列。
消息消费者获取到消息
另外,在测试中遇到,控制台报错输出:
Caused by: org.springframework.amqp.AmqpException: No method found for class [B
解决方法:
不要将@RabbitListener(queues = RabbitMqConfig.TOPIC_QUEUE)注解放在类上,放在方法上便可。
消息变成死信的几种情况
* 消息被拒绝(basic.reject / basic.nack),并且requeue = false
* 消息TTL过期
* 队列达到最大长度
死信处理过程
* DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。
* 当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。
* 可以监听这个队列中的消息做相应的处理。
示例如下:
创建死信队列
将正常的队列与死信队列绑定
当发送消息后,如果消费者拒绝这条消息
会发现这条消息进入了死信队列
此时监听死信队列,可以处理死信。