目录
7.1 队列的类型
一、MQ的作用
(1)流量消峰
(2)应用解耦
(3)异步处理
二、MQ的重要概念
(1)生成者:产生数据发送消息的程序是生产者
(2)交换机:接收生成者的消息并转发到合适的队列里
(3)队列:消息缓存区
(4)消费者:消费消息
三、 MQ的工作原理
(1)broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker
(2)connection:publisher/consumer 和 broker 之间的 TCP 连接
(3)Channel:Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread 创建单独的 channel 进行通讯
(4)exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到 queue 中去
(5)queue:消息最终被送到这里等待 consumer 取走,一个队列里的元素只能被消费一次。
(6)binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保 存到 exchange 中的查询表中,用于 message 的分发依据
四、MQ 消息应答
4.1 消息应答
为了保证消息在发送过程中不丢失,rabbitmq 引入消息应答机制,消息应答就是: 消费者在接收 到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了自动应答:消息发送后立即被认为已经发送成功,无需等待消费者的ack,这种模式仅适用在消费者可以高效并以 某种速率能够处理这些消息的情况下使用
手动应答:消息发出需要消费者手动应答
(1)肯定应答:
Channel.basicAck
(肯定确认应答):basicAck(long deliveryTag, boolean multiple)
第一个参数是消息的标记,第二个参数表示是否应用于批量应答。
(2)否定确认应答:
Channel.basicReject
(否定确认应答)basicReject(long deliveryTag, boolean requeue);
(3)否定确认应答:
Channel.basicNack
(用于否定确认)basicNack(long deliveryTag, boolean multiple, boolean requeue);
4.2 分发类型
(1)轮询分发:消息被消费者轮询消费
(2)不公平分发:多劳多得,消费快的消费者获得更多信息。在消费者中消费消息之前,设置参数
channel.basicQos(1)
(3)
预取值分发:
带权的消息分发。注意:不公平分发和预取值分发都用到
basic.qos
方法,如果取值为 1,代表不公平分发,取值不为1,代表预取值分发
五、可靠消息
保证消息不丢失 = 队列持久化 + 消息持久化 + 发布确认
5.1 持久化
队列持久化
队列实现持久化需要在声明队列的时候把 durable 参数设置为true,代表开启持久化
注意:如果之前声明的队列不是持久化的,需要把原先队列先删除,或者重新创建一个持久化的队列
消息持久化
需要在消息生产者发布消息的时候,开启消息的持久化,在 basicPublish 方法的第二个参数添加这个属性:
MessageProperties.PERSISTENT_TEXT_PLAIN
5.2 发布确认
生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,broker 就会发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号,此外 broker 也可以设置 basic.ack 的 multiple 域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm 模式最大的好处在于是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack 消息, 生产者应用程序同样可以在回调方法中处理该 nack 消息。
发布确认默认是没有开启的,如果要开启需要调用方法 confirmSelect,每当你要想使用发布确认,都需要在 channel 上调用该方法。
5.3 发布确认模式
单个确认发布
发一个消息之后只有它被确认发布了,后续的消息才能继续发布,这种方式发布速度特别慢。
批量发布确认
先发布一批消息然后一起确认,这种方式可以极大地提高吞吐量,但很难确认是哪一条消息出现问题。
异步批量确认
发布一条消息,通过回调函数来保证消息的可靠性
注意:对于异步未确认的消息,应该将未确认的消息放到一个基于内存的能被发布线程访问的队列,例如使用ConcurrentLinkedQueue
5.4 发布确认高级
前面的发布确认都是当消息成功发送到队列才收到确认消息,但在生产环境中由于一些不明原因,导致 rabbitmq 重启,在 RabbitMQ 重启期间生产者消息投递失败, 导致消息丢失,需要手动处理和恢复。
交换机确认机制
(1)配置文件设置spring.rabbitmq.publisher-confirm-type
NONE 禁用发布确认模式,是默认值
CORRELATED 发布消息成功到交换器后会触发回调方法SIMPLE 值经测试有两种效果,其一效果和 CORRELATED 值一样会触发回调方法,其二在发布消息成功后使用 rabbitTemplate 调用 waitForConfirms 或 waitForConfirmsOrDie 方法等待 broker 节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是 waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel,则接下来无法发送消息到 broker。(2)实现交换机回调接口RabbitTemplate.ConfirmCallback,实现confirm方法
(3)注入给RabbitTemplate类
回退消息
在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如 果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个事件的。通过设置 mandatory参数可以在当消息传递过程中不可达目的地时将消息返回给生产者。
(1)配置文件设置spring.rabbitmq.publisher-return=true
(2)实现交换机回调接口RabbitTemplate.ReturnCallback ,实现returnedMessage方法
(3)注入给RabbitTemplate类
备份交换机
当我们为某一个交换机声明一个对应的备份交换机时,就是为它创建一个备胎,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理,通常备份交换机的类型为 Fanout ,这样就能把所有消息都投递到与其绑定 的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都进入这个队列了。当然,我们还可以建立一个报警队列,用独立的消费者来进行监测和报警。
Mandatory 参数与备份交换机可以一起使用的时候,如果两者同时开启,消息究竟何去何从?备份交换机优先级高。
六、MQ交换机
RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递传递到了哪些队列中。
相反,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将它们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列还是说把他们到许多队列中还是说应该丢弃它们。这就的由交换机的类型来决定。
6.1 交换机的类型
无名交换机
使用空字符串来表示无名交换机,也叫默认交换机
直接交换机(direct)
消息只去到它绑定的 routingKey 队列中去,队列只对它绑定的交换机的消息感兴趣 。![]()
主题交换机(topic)
主题交换机可以在routingKey 使用通配符
topic 交换机的消息的 routing_key 不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。*可以代替一个单词,#可以代替零个或多个单词
当一个队列绑定键是 #,那么这个队列将接收所有数据,就有点像 fanout 了,如果队列绑定键当中没有 # 和 * 出现,那么该队列绑定类型就是 direct 了
扇出交换机(fanout)
fanout交换机会将接收到的所有消息广播到它知道的所有队列之中。
6.2 交换机与队列的bindings
什么是 bingding 呢,binding 其实是 exchange 和 queue 之间的桥梁,它告诉我们 exchange 和那个队列进行了绑定关系。
七、mq的队列
7.1 队列的类型
死信队列
死信,顾名思义就是无法被消费的消息,当消息消费发生异常时,将消息投入死信队列中。
死信队列的来源
消息 TTL 过期
TTL是 Time To Live 的缩写, 也就是生存时间
队列达到最大长度
队列满了,无法再添加数据到 MQ 中
消息被拒绝
(basic.reject 或 basic.nack) 并且 requeue = false
延时队列
延时队列就是用来存放需要在指定时间被处理的元素的队列。
设置TTL的三种方式
(1)在创建队列的时候设置队列的 x-message-ttl 属性
Map<String, Object> params = new HashMap<>(); params.put("x-message-ttl",5000); return QueueBuilder.durable("QA").withArguments(args).build();
这种方式缺点是每增加一个新的时间需求,就要新增一个队列。
(2)针对每条消息设置 TTL
rabbitTemplate.converAndSend("X","XC",message,correlationData -> { correlationData.getMessageProperties().setExpiration("5000"); });
这种方式缺点是RabbitMQ 只会检查第一个消息是否过期,如果过期则丢到死信队列,
如果第一个消息的延时时长很长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行。(3)使用mq插件实现rabbitmq_delayed_message_exchange这种方式是基于交换机实现的如果设置了队列的 TTL 属性,那么一旦消息过期,就会被队列丢弃(如果配置了死信队列被丢到死信队列中),而第二种方式,消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间。
另外,还需要注意的一点是,如果不设置 TTL,表示消息永远不会过期,如果将 TTL 设置为 0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。
优先级队列
可以给队列中的消息设置优先级,队列需要设置为优先级队列,消息需要设置消息的优先 级,消费者需要等待消息已经发送到队列中才去消费,因为,这样才有机会对消息进行排序。
惰性队列
惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中。
Map<String, Object> args = new HashMap<String, Object>(); args.put("x-queue-mode", "lazy"); channel.queueDeclare("myqueue", false, false, false, args);
临时队列
在创建队列使用服务器产生随机名称的队列,一旦我们断开了消费者的连接,队列将被自动删除。
String queueName = channel.queueDeclare().getQueue();