目录
TTL
过期时间,消息的寿命,存活时间,当消息到达设置的过期时间还没有被消费掉,就会被清除掉
设置队列的TTL
1.设置队列的TTL(存在该队列中的所有消息的TTL)
2.设置消息的TTL
两个min,假如队列TTL是20s,消息TTL是10s,那么消息的TTL取小值,也就是10s
package com.example.controller; import com.example.constant.Constants; import org.springframework.amqp.AmqpException; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.amqp.core.MessagePostProcessor; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RequestMapping("/producer") @RestController public class ProducterController { @Resource(name="rabbitTemplate") RabbitTemplate rabbitTemplate; @Resource(name="confirmrabbitTemplate") //会自己查找 private RabbitTemplate confirmrabbitTemplate; @RequestMapping("/ack") public String ack(){ rabbitTemplate.convertAndSend(Constants.ACK_EXCHANGE,"ack","consumer ack mode test ..."); return "消息发送成功"; } @RequestMapping("/pres") public String pres(){ Message message=new Message("Presistent test ...".getBytes(),new MessageProperties()); //非持久化 message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); rabbitTemplate.convertAndSend(Constants.PRES_EXCHANGE,"pres",message); return "消息发送成功"; } @RequestMapping("/confirm") public String confirm(){ //设置回调方法 CorrelationData correlationData=new CorrelationData("1"); confirmrabbitTemplate.convertAndSend(Constants.CONFIRM_EXCHANGE,"confirm,","confirm test...",correlationData); return "消息发送成功"; } @RequestMapping("/return") public String returns(){ //设置回调方法 CorrelationData correlationData=new CorrelationData("5"); confirmrabbitTemplate.convertAndSend(Constants.CONFIRM_EXCHANGE,"confirm,","returns test...",correlationData); return "消息发送成功"; } @RequestMapping("/retry") public String retry(){ rabbitTemplate.convertAndSend(Constants.RETRY_EXCHANGE,"retry","retry test ..."); return "消息发送成功"; } @RequestMapping("/ttl") public String ttl(){ System.out.println("ttl..."); MessagePostProcessor messagePostProcessor=new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { //设置消息的TTL message.getMessageProperties().setExpiration("10000"); //毫秒,过期时间为10s return message; } }; rabbitTemplate.convertAndSend(Constants.TTL_EXHCHANGE,"ttl","ttl test......",messagePostProcessor); return "消息发送成功"; } }
我们设置消息的过期时间为10s,可以观察到10s后消息自动消失
如果同时设置消息的ttl和队列的ttl,看看以哪个为主。
ttl未设置过期时间
ttl2设置过期时间20s ->共同发送一条ttl消息等于10s的消息
两者区别
设置队列TTL的属性方法,一旦消息过期,就会从队列中删除,
设置消息TTL的方法,即使消息过期,也不会马上从队列中删除,而是在即将投递到消费者之前进行判定
为啥两种方法不一样呢
因为设置队列过期时间,队列已经过期的消息在队列头部,RabbitMQ只需要定期检查队头,看是否有过期的元素即可
而设置消息TTL的方式,每条消息的过期时间不同,如果要删除所有过期消息,需要扫描整个队列因此,不如等到消息即将被消费时候,再去判定是否过期,如果过期再进行删除即可(假如消息1 30s过期,消息2 10s过期,他会先判断消息1,然后消息1的30s过去了,再看消息2.
死信:由于种种原因,无法被消费的消息就是死信,
有了死信,就有死信队列:当消息在一个队列中变成死信之后,他能重新被发送到另一个交换机中,这个就是死信交换机,绑定死信交换机的就叫死信队列。
消息变成死信的情况:
1.消息被拒绝,并且requeue(重新入队)false;
2.消息过期
3.队列达到最大长度
标签对应
原本是10,但是当前是none,原因是原先有队列,但是你改变队列的长度,所以无法改变.
解决方法:删除原先队列,重新声明
此时,这里面的lim就是最大长度
当队列到达最大长度时,其余会到达死信
死信队列的面试题
死信队列的概念:首先介绍什么是死信:消息队列中一种特殊消息,指无法被正常消费或者处理的消息
死信的来源:消息在队列中存活的时间超过了设定的TTL
消息被拒绝:消费者在处理消息时候,肯能因为消息内容有误,处理逻辑异常等原因拒绝处理该消息,如果拒绝时,指定不重新入队,消息也会成为死信
(NACK重新入队时候,出现问题,假如他一直入队,会导致后面一直停滞,生产者一直生产消息,导致消息积压,所以需要把他放入死信队列中,
队列满了,队列到达最大长度,无法容纳新的消息时候,新来的消息会变成死信
死信队列的应用场景:
如:用户支付订单后,支付系统会给订单系统返回当前订单的支付状态,为了保证支付信息不丢失,需要使用到死信队列机制,当消息消费异常时候,将消息投入到死信队列中,由订单系统的其他消费者来监听这个队列,并对数据进行处理(比如发送工单,进行人工确认等)
延迟队列
消息被发送之后,并不希望消费者立即拿到消息,而是等待特定时间后,消费者才能拿到这个信息进行消费(相当于写给十年后的自己?)
智能家居,指定时间工作
RabbitMQ本身没有直接支持延迟队列的功能,但是可以通过前面所介绍的TTL+死信队列的方式组合,模拟出延迟队列的功能。
比如消费者消费死信队列,然后正常队列收消息,30min过期进入死信,然后直接消费,正好当延迟的功能了。
一个小问题:
当后面的消息时10s过期,前面的是30s过期时候,延迟功能会出现畏难而退,先发送30s的TTL消息,再发送10s的TTL消息,那么10s的TTL消息,会在30s过期之后,才进行处理。
采用延迟队列插件:
出现发送文件给docker内部,但是又无法使用拖拽模式的时候的办法(以及出现Enabling plugins on node rabbit@91e7e7949d70:
rabbitmq_delayed_message_exchange
Error:
{:plugins_not_found, [:rabbitmq_delayed_message_exchange]}报错的解决方法
我们虽然不能本地scp直接传递到docker容器内部,那么不妨就拆开,拆成两步,一步是发送文件到服务器本地
怎么发使用scp,看我之前写的那个使用MAC上传本地文件到Linux的那个章节,然后传递完本地后,可以使用docker命令
这句话的意思是,复制服务器本地的,同步到docker内部位置
rabbitmqL是你用docker ps查看到的容器名称,后面跟着路径即可
docker cp rabbitmq_delayed_message_exchange-3.13.0.ez rabbitmq:/opt/rabbitmq/plugins
我们第一要检查版本号(上面放了3.13的,要是3.8左右,就去自己找吧)
第二就是要看你是要安装的docker里面,还是本机的rabbitmq里面,很关键的这个问题,因为目录不一样
如果你是本地的rabbitmq 大概率plugins文件夹在usr/lib/rabbitmq/plugins
但是你要是docker的话是发现lib里面是没有rabbitmq这个文件夹的,所以我尝试半天后,查找到了,原来我们是需要用opt这个文件夹,我是用ls查看后,发现里面有rabbitmq这个文件夹,然后往后走是plugins文件夹。这也就是他的恶心之处,最后我们是可以检查到插件列表中出现的
问题二:UI 界面不显示消息条数
在docker容器内部使用哈
cd /etc/rabbitmq/conf.d/
echo management_agent.disable_metrics_collector = false > management_agent.disable_metrics_collector.conf
问题三:Listener method could not be invoked with the incoming message和 Cannot convert from [java.lang.Strin
包引入错误,一般是Channel
import com.rabbitmq.client.Channel;
import com.xuecheng.rabbitmq.config.RabbitmqConfig;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
在官方给我们的延迟队列插件中,假如下面两个时间调换,那么也可以轻松变成以10s开始,30在后面。
@RequestMapping("/delay")
public String delay(){
System.out.println("delay");
rabbitTemplate.convertAndSend(Constants.DELAY_EXCHANGE,"delay","delay test 10s",message -> {
message.getMessageProperties().setDelay(10000); //设置延迟时间
return message;
});
rabbitTemplate.convertAndSend(Constants.DELAY_EXCHANGE,"delay","delay test 30s",message -> {
message.getMessageProperties().setDelay(30000);
return message;
});
System.out.printf("%tc 消息发送成功 \n",new Date());
return "消息发送成功";
}
介绍一下延时队列
消息发送后,并不立即给消费者,而是等待特定时间,才发送给消费者
1.订单在十分钟未支付自动取消(当然也不一定用延时队列,计时器,啥的其实都可以,每分钟扫描一下即可)
RabbitMQ并没直接实现延迟队列:
1.TTL+死信队列 (灵活,不需要插件,但是存在一个消息顺序问题,需要额外逻辑来处理死信队列的消息,增加系统的复杂性)2.使用官方的延迟插件实现延迟功能
(优点,不存在消息顺序问题,也还比较灵活,通过插件,可以直接创建延迟队列,
缺点:有版本限制,需要特定插件)
总结
死信队列:普通队列的底下保底,假如被消费端拒绝了,意味着消息会被丢到对应的死信队列中,相当于延长了队列了生命周期(因为假如被拒绝,就可能会被丢弃)。
使用channel.basicNack,并且此时requeue属性被设置成false.
消息被拒绝
消息在队列中存活时间超过设置的TTL时间
消息队列的消息数量已经超过最大队列长度
那么该消息会变成为死信,死信消息会被RabbitMQ进行特殊处理,如果配置了死心队列消息,那么消息将会被丢进死信队列中,如果没有配置,则该消息会被丢弃
为每个需要使用死信的业务配置一个死信交换机,这里同一个项目的私信交换机可以公用一个,然后为每个业务队列分配一个单独的路由Key,死信队列只不过是绑定在死信交换机上的队列,并非什么特殊交换机,只不过是接受死信的交换机,可以为任何类型【Direct.Fanout,Topic】
死信队列消息流转过程:
生产者->交换机->队列(拒绝消费)->死信交换机->死信队列
延时对列:我发送的消息,不希望他今天消费,希望他明天消费,如果设置时间没到,则不能被消费(定时发布)
TTL:一条消息或者该队列中所有消息的最大存活时间,如果一条消息设置了TTL属性或者进入设置TTL属性的队列,那么这条消息如果在TTL设置的时间内没有被消费,则会成为死信,如果同时配置了队列的TTL和消息的TTL,那么较小的那个值会被禁用(更清楚的表达:
存在两种方法设置过期时间,一种是给队列设置,这种设置后,整个队列都是相同的过期时间,另一种是给消息本身进行配置,这种设置后,每种消息过期时间不同,如果同时使用这两种方法,那么以过期时间小的那个数值为准)
队列设置:队列中声明使用x-message-url参数,单位为毫秒
单个消息设置:是设置消息属性的expiration参数的值,单位为毫秒