四大核心概念
- 生产者:产生数据发送消息的程序是生产者
- 交换机:交换机是rabbitmq非常重要的一个部件,一方面接收来自生产者的消息,另一方面将消息推送到队列中。交换机必须明确知道该如何处理它接收的消息,是将消息推送到指定队列还是推送到多个队列,或者是把消息丢弃,这由交换机类型决定
- 消费者:消费与接收含义相似,消费者大多是等待接收消息的程序。注意生产者、消费者和消息中间件很多时候并不在同一机器上。同一应用程序既可以是生产者也可以是消费者
- broker:接收和分发消息,rabbitmq server就是message broker
- virtual host:在多个不同用户使用rabbitmq server提供的服务时,可以划分出多个vhost,每个vhost创建exchange/queue等
- connection:如果每一次访问rabbitmq都创建一个connection,在消息大的时候建立TCP connection的开销将是巨大的,效率也较低。channel实在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯,AMQP method包含了channel id帮助客户端和message broker识别channel,所以channel之间是完全隔开的。channel作为轻量级connection极大减少操作系统建立TCP connection的开销。
- exchange:message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。常见的类型有:direct(point-to-point)、topic(publish-subscribe)and fanout(multicast)
- queue:消息最终被送到这里等待consumer取走
- binding:exchange和queue之间的虚拟连接,binding中可以包含routing key,Binding信息被保存到exchange中的查询表中,用于message的分发依据
MQ的作用
- 流量肖峰
- 应用解耦
- 异步处理
MQ的分类
- kafka:追求高吞吐量,用于日志收集和传输,适合大量数据互联网数据采集,如有日志采集功能首选Kafka
- rocketmq:金融互联网领域,尤其电商订单扣款,以及业务削峰,适合高并发场景
- rabbitmq:结合erlang语言的并发优势,性能时效性较好,如果数据量不大,中小型选择功能完备的rabbitmq
安装
- rabbitmq-plugins enable rabbitmq_management
Work Queues
- 工作队列(又称为任务队列)主要是为了避免立即执行资源密集型任务,而不得不等待它完成执行。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时,这些工作线程将一起处理这些任务。
消息应答
- 消费者在处理一条消息时突然挂掉,该消息将会被删除并且我们将丢失正在处理的消息。为保证消息在传输过程中不丢失,rabbitmq引入应答机制,消息应答就是:消费者在接收到消息并且处理该消息后,会告诉rabbitmq消息已经处理完,rabbitmq可以删除消息了。
- 自动应答:消息发送出去后立即认为已经传输成功
- 手动应答:Channel.basicAck(用于肯定确认:rabbitmq已知道该消息并且成功的处理消息,可以将其丢弃)、Channel.basicNack(用于否定确认)、Channel.basicReject(用于否定确认)与Channel.basicNack相比少一个参数,不处理该消息了直接拒绝,可以将其丢弃。
- 手动应答的参数multiple的true和false代表:true代表批量应答channel上未应答的消息,比如channel上有传送tag的消息5,6,7,8 当前的tag为8那么此时5-8的这些还未应答的消息都会被确认收到消息应答;false同上面相比只会应答tag=8,其它的都不会被确认收到应答
消息重新入队
- 当消费者失去连接,导致信息未发送ack确认,rabbitmq会将消息重新入队。如果此时其它消费者可以处理,它将重新分发给另一个消费者。
消息持久化
- 将队列和消息标记为持久化,如果原来队列不是持久化需要删除后重新建立新的
不公平分发
1.消费者设置 channel.basicQos(1)
预取值
- 指定消费者消费的数量(prefectch = 2)
消息发布确认
- 单个发布确认
- 批量发布确认
- 异步发布确认
- 解决异步未确认消息:最好的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列,比如说用ConcurrentLinkedQueue这个队列在comfirm callbacks与发布线程之间的消息的传递。
//异步发布确认
public static void publicMessageAsync() throws Exception{
Channel channel = RabbitmqUtils.Channel();
//声明队列名称
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName,false,false,false,null);
//开启发布确认
channel.confirmSelect();
/**
* 线程安全有序的一个哈希表,适用于高并发情况
* 1.轻松的将序号与消息进行关联
* 2.轻松批量删除条目 只要给到序号
* 3.支持高并发(多线程)
*/
ConcurrentSkipListMap<Long,String> outstandComfirms = new ConcurrentSkipListMap<>();
//消息确认成功,回调函数
ConfirmCallback ackCallback = (deliveryTag,multiple)->{
if(multiple){ //判断是否批量删除
//2.删除以确认的消息,剩下的就是未确认的消息
ConcurrentNavigableMap<Long, String> comfirmed = outstandComfirms.headMap(deliveryTag);
comfirmed.clear();
}else{
outstandComfirms.remove(deliveryTag);
}
System.out.println("确认的消息:" + deliveryTag);
};
//消息确认失败,回调函数
ConfirmCallback nackCallback = (deliveryTag,multiple)->{
//3.打印未确认的消息都有那些
String message = outstandComfirms.get(deliveryTag);
System.out.println("未确认的消息:" + message + " 未确认消息的号码" + deliveryTag);
};
//准备消息的监听器,异步监听那些发布成功,那些发布失败
channel.addConfirmListener(ackCallback,nackCallback);
//开始时间
long beginTime = System.currentTimeMillis();
//批量发送消息
for (int i = 0; i < MESSAGE_TIME; i++) {
String message = i+"";
channel.basicPublish("",queueName,null,message.getBytes());
//1.记录所有要发送的消息 消息总和
outstandComfirms.put(channel.getNextPublishSeqNo(),message);
}
//结束时间
long endTime = System.currentTimeMillis();
System.out.println("发布"+MESSAGE_TIME+"个批量确认消息,耗时"+(endTime-beginTime)+"ms");
}
路由器
- 一个队列的消息只能被消费一次。在有路由器的情况下,绑定两个队列,消息经路由器到两个队列从而实现一个消息消费两次。
- 概念:生产者生产的消息不会直接发送到队列,通常生产者是不知道消息发送到那个队列。生产者只能将消息发送到交换机
- fnaout:不需要路由键,消息直接发布的与交换机绑定的队列中
- direct:交换机根据路由键将消息发布到指定的队列中
- topics:* 代表一个单词,# 代表零个或多个单词
死信
- 概念:生产者将消息投递到broker或者队列中,而消费者从队列中取出消息进行消费,由于某些原因这些消息无法被消费,这样的消息如果没有后续的处理,就会变成死信。
- 应用场景:为保证订单业务的消息数据不丢失,需要使用到rabbitmq的死信队列机制,当消息消费发生异常时,将消息投入死信队列中,还比如说:用户在商城下单成功并点击去支付后在指定时间未支付时自动失效。
- 产生死信的情况:消息被拒绝(basic.reject或basic.nack)、消息的TTL过期、队列达到最大长度
延时队列(基于死信)
-
延时队列中的元素是希望在指定时间到了以后或之前取出处理,简单来说,延时队列是用来存放需要在指定时间被处理的元素队列。如果第一条延时队列未处理完毕,第二条延时队列是不会执行的
-
自定义延迟时间:rabbitmq只会检查第一个消息是否过期,如果过期则丢到死信队列中。如果第一个消息的延时时长很长,而第二个延时很短,第二个消息并不会优先得到执行。(会在第一条执行完之后再执行第二条)
-
使用场景:订单在十分钟内未支付则自动消失;新创建的商店,如果在十天内未上传商品,则自动发送消息提醒;用户发起退款,如果三天内没有处理则通知相关的运营人员;预定会议后,需要在预定点前十分钟通知各个与会人员参加会议
-
该场景都有一个特点,就是在某个事件发生的前后完成某一项任务,蜀国数据量小可以用定时任务轮询完成。但对于时效性强,如订单十分钟内未支付则关闭,短期未支付的订单会很多,很可能在一秒内无法完成所有订单的检查,同时给数据库带来压力。
延时队列(基于插件)
发布确认高级
- 在生产环境中由于一些不明原因,导致rabbitmq重启,在rabbitmq重启期间生产者消息投递失败,导致消息丢失,需要手动处理和恢复。(新增缓存对未成功发送得消息重新投递)
- 发布成功或失败后调用回调需要配置:spring.rabbitmq.publisher-confirm-type=correlated(
- NONE禁用发布模式;
- CORRELATED发布消息成功到交换器后会触发回调方法;
- SIMPLE经测试有两种效果,第一种和CORRELATED一样会触发回调。第二种在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点发送返回结果,根据返回结果来判定下一步逻辑,如果waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker)
回退消息
- 在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消费生产者发送确认消息,如果发现该消息为不可路由,那么消息则会被直接丢弃,此时生产者是不知道该消息被丢弃的。通过设置mandatory参数可以在当消息传递过程中不可达目的地时候将消息返回给生产者。
备份交换机
- 无法投递的消息将交给备份交换机
- mandatory参数可以与备份交换机一起使用的时候,如果两者同时开启,谁优先级高遵守谁,从上看出备份交换机优先级高
消费端幂等性保障
- 当业务达到高峰期,生产端可能会重复发送消息。这时候需要消费端保证幂等性,这就意味我们的消息不会被消费多次。有两种幂等性操作:a.唯一ID+指纹码机制,利用数据库主键去重;b.利用redis原子性实现
- redis原子性:利用redis执行setnx命令,天然具有幂等性,从而实现不重复消费
优先级队列
- 场景:订单催付,例如客户在天猫下的订单,淘宝会及时将订单推送,如果用户在设定时间未支付,那么就会推送一条短信提醒。并且根据客户的重要性来设立推送消息的优先级。队列需要设置优先级队列,消息需要设置优先级消息,消费者需要等待消息已经发送到队列中才去消费,这样才能对消息进行排序
package com.qiang.rabbitmq.one;
import com.qiang.rabbitmq.utils.RabbitmqUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
//队列名称
public static final String QUEUE_NAME = "hello";
//接收消息
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitmqUtils.Channel();
//声明接收消息 回调函数
DeliverCallback deliverCallback = (consumerTag,message)->{
System.out.println(new String(message.getBody()));
};
//消息接收被取消时,调用的回调函数
CancelCallback cancelCallback = (consumerTag)->{
System.out.println(consumerTag);
};
/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功后是否自动应答 true代表自动应答 false代表手动应答
* 3.消费者未成功消费后的回调
* 4.消费这取消消费的回调
*/
channel.basicConsume(QUEUE_NAME,true, deliverCallback, cancelCallback);
}
}
惰性队列
- 消息的保存位置,正常消息是保存在内存中,惰性队列而是保存在磁盘中(内存中只剩下索引,当需要使用时会到磁盘中查找消息)
镜像队列
- 实现队列的复用。
实现高可用负载均衡
- Hproxy+keepalive
消息可靠性(消息发送到交换机、队列)
消费者确认
消费者失败重试机制
死信交换机