概念:基于Erlang语言的一个消息队列
作用:1.异步通讯2.应用解耦 3.削峰填谷
角色:
生产者 publisher
消息平台 cluster
交换机 exchange、队列 queue
消费者 consumer
一、搭建服务器
二、搭建客户端(项目集成MQ)
1.导包
spring-boot-starter-amqp
2.配置
Rabbitmq服务器连接:地址、端口、用户名、密码、虚拟主机 声明交互机、声明队列、绑定交换机和队列 生产方:开启confirm确认机制、开启return返回机制 消费方:开启重试策略、设置重试失败策略
3.代码
发送消息: RabbitTrmlate
配置消息转化器还有对象序列化
接受消息:@RabbitListener
1.建立连接工厂 2.建立连接 3.建立通道 4.声明交换机、队列、绑定 5.发送消息、接受消息(一直监听) 6.释放资源
一对一: 1.简单模式 Simple、 2.工作队列 worker queue 多个消费者 一对多: 1.发布订阅 Publish/Subscribe ExchangeTypes.FANOUT FanoutExchange 把消息发送到交换机绑定的所有队列 2.路由模式 Routing ExchangeTypes.DIRECT DirectExchagne 发送消息是会指定‘routingkey’交换机根据routingkey是否匹配,把消息 发送匹配的队列 3.主题(通配符)模式 Topic ExchangeTypes.TOPIC TopicExchagne 发送消息是会指定‘routingkey’交换机根据routingkey是否匹配把消息 发送匹配的队列 routingkey支持通配符 # 零个或多个词 * 零个或一个词 4.远程调用:RPC 延迟高、不如用同步的 举例:A给B发送消息 B给A这个结果
交换机和队列绑定
//配置类 Bingding @RabbitListenner(bingdings={ })
##
提供了publisher confirm机制来避免消息发送到MQ过程中丢失。这种机制必须给每个消息指定一个唯一ID。消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功
publisher-confirm,发送者确认
消息成功投递到交换机,返回ack
消息未投递到交换机,返回nack
publisher-return,发送者回执
修改配置
spring: rabbitmq: #生产者确认类型 异步回调 publisher-confirm-type: correlated # 开启publish-return功能 publisher-returns: true template: #:定义消息路由失败时的策略。true mandatory: true
配置ReturnCallback
package cn.itcast.mq.config; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Configuration; @Slf4j @Configuration public class CommonConfig implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 获取RabbitTemplate RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class); // 设置ReturnCallback rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { // 投递失败,记录日志 log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}", replyCode, replyText, exchange, routingKey, message.toString()); // 如果有业务需要,可以重发消息 }); } }
生产者代码
public void testSendMessage2SimpleQueue() throws InterruptedException { // 1.消息体 String message = "hello, spring amqp!"; // 2.全局唯一的消息ID,需要封装到CorrelationData中 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); // 3.添加callback correlationData.getFuture().addCallback( result -> { if(result.isAck()){ // 3.1.ack,消息成功 log.debug("消息发送成功, ID:{}", correlationData.getId()); }else{ // 3.2.nack,消息失败 log.error("消息发送失败, ID:{}, 原因{}",correlationData.getId(), result.getReason()); } }, ex -> log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),ex.getMessage()) ); // 4.发送消息 rabbitTemplate.convertAndSend("task.direct", "task", message, correlationData); // 休眠一会儿,等待ack回执 Thread.sleep(2000); }
如果突然宕机,也可能导致消息丢失
默认是非持久化的
交换机持久化
@Bean public DirectExchange simpleExchange(){ // 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除 return new DirectExchange("simple.direct", true, false); }
队列持久化
@Bean public Queue simpleQueue(){ // 使用QueueBuilder构建队列,durable就是持久化的 return QueueBuilder.durable("simple.queue").build(); }
消息持久化
Message message1 = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8)) //设置持久化PERSISTENT .setDeliveryMode(MessageDeliveryMode.PERSISTENT).build();
RabbitMQ是阅后即焚机制,RabbitMQ确认消息被消费者消费后会立刻删除。
配置
一般设定auto自动模式;但是这种模式有一种弊端 就是在代码出现异常是 他会 不断的推送消息 控制台不断的报错
spring: rabbitmq: listener: simple: acknowledge-mode: none # 关闭ack、auto自动、manual手动
当消费者出现异常后,消息会不断requeue(重入队)到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力
在消费者出现异常时利用本地重试
配置
spring: rabbitmq: listener: simple: retry: enabled: true # 开启消费者失败重试 initial-interval: 1000 # 初识的失败等待时长为1秒 multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval max-attempts: 3 # 最大重试次数 stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false
消费者失败消息处理策略
当失败消息在消费者失败 就会进去失败的一个交换机和队列
实现策略
- RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式 - ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队 - RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机(一般使用)
处理失败消息的交换机和队列
@Bean public DirectExchange errorMessageExchange(){ return new DirectExchange("error.direct"); } @Bean public Queue errorQueue(){ return new Queue("error.queue", true); } @Bean public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){ return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error"); }
关联队列和交换机
@Bean public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){ return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error"); }
如何确保RabbitMQ消息的可靠性?
1.生产者发送消息到交换机 confirm-ack 成功则不做处理 失败则重新发送消息或者记录错误日志 注意这里消息的唯一标识是id
2.交换机推送到消息队列 return失败才会有返回可以重新发送消息或者记录错误日志
3.RabbitMQ 服务器宕机 交换机和队列持久化
4.消费者从队列中取出消息 手动告知队列删除消息
概念
死信是指 消费失败、过期消息、消息堆积满
在正常流程的消费者失败的消息返还给队列中用队列绑定一个交换机和队列专门储存这些消息
如何给队列绑定死信交换机
1.给队列设置属性指定一个交换机
2.给队列设置属性 设置死信交换机与死信队列
// 声明普通的 simple.queue队列,并且为其指定死信交换机:dl.direct @Bean public Queue simpleQueue2(){ return QueueBuilder.durable("simple.queue") // 指定队列名称,并持久化 .deadLetterExchange("dl.direct") // 指定死信交换机 .build(); } // 声明死信交换机 dl.direct @Bean public DirectExchange dlExchange(){ return new DirectExchange("dl.direct", true, false); } // 声明存储死信的队列 dl.queue @Bean public Queue dlQueue(){ return new Queue("dl.queue", true); } // 将死信队列 与 死信交换机绑定 @Bean public Binding dlBinding(){ return BindingBuilder.bind(dlQueue()).to(dlExchange()).with("simple"); }
TTL:一个队列中的消息如果超时未消费
声明交换机和队列
@RabbitListener(bindings = @QueueBinding( value = @Queue(name = "dl.ttl.queue", durable = "true"), exchange = @Exchange(name = "dl.ttl.direct"), key = "ttl" )) public void listenDlQueue(String msg){ log.info("接收到 dl.ttl.queue的延迟消息:{}", msg); }
设置超时时间
//交换机 @Bean public DirectExchange ttlExchange(){ return new DirectExchange("ttl.direct"); } @Bean public Queue ttlQueue(){ return QueueBuilder.durable("ttl.queue") // 指定队列名称,并持久化 .ttl(10000) // 设置队列的超时时间,10秒 .deadLetterExchange("dl.ttl.direct") // 指定死信交换机 .build(); } //交换机和队列的绑定 @Bean public Binding ttlBinding(){ return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("ttl"); }
消息超时的两种方式是?
1.给队列设置ttl属性,进入队列后超过ttl时间的消息变为死信 2.给消息设置ttl属性,队列接收到消息超过ttl时间后变为死信 3.两者共存时,以时间短的ttl为准
如何实现发送一个消息20秒后消费者才收到消息?
1.给消息的目标队列指定死信交换机 2.消费者监听与死信交换机绑定的队列 3.发送消息时给消息设置ttl为20秒
消费者延迟收到的消息效果叫延迟队列
可以安装延迟队列插件
代码实现死信交换机
@RabbitListener(bindings = @QueueBinding( value = @Queue(name = "dalay.queue",declare = "true"), exchange = @Exchange(name = "delay.direct",declare = "true"), key = "delay" )) public void setRabbitTemplate(String msg){ log.info("接受到 delay。queue的延迟消息",msg); }
测试代码
@Test public void setRabbitTemplate(){ Message message = MessageBuilder.withBody("hello".getBytes(StandardCharsets.UTF_8)) .setHeader("xdelay", 5000) .build(); CorrelationData data = new CorrelationData(UUID.randomUUID().toString()); rabbitTemplate.convertAndSend("delay.direct","delay",message ,data); log.debug("发送成功"); }
延迟队列插件的使用步骤包括哪些?
1.声明一个交换机,添加delayed属性为true 2.发送消息时,添加x-delay头,值为超时时间
消息堆积问题
概念:当生产者发送消息的速度超过了消费者处理消息的速度,就会导致队列中的消息堆积
解决思路
1.增加更多消费者提高消费速度
2.在消费者内开启线程池加快消息处理速度
3.扩大队列容积,提高堆积上限
特征
1.直接写到磁盘而不是内存
2.消费者要消费时才会从磁盘中读取并加载到内存
3.支持数百万条的消息存储
设置惰性队列
设置一个队列为惰性队列,只需要在声明队列时,指定x-queue-mode属性为lazy即可
//1.首先配置环境变量 //2.在从cmd里面输入下面的代码 rabbitmqctl set_policy Lazy "^lazy-queue$" '{"queue-mode":"lazy"}' --apply-to queues
设置属性
@Bean public Queue lazyQuerue(){ return QueueBuilder .durable("lazy.queue") .lazy() .build(); }
注解方式
@RabbitListener(queuesToDeclare = =@Queue( name = "lazy.queue", durable = "true", arguments = @Argument(name = "x-queue-mode",value = "lazy") )) public void liten(String msg){ log.info("接受消息",msg); }
消息堆积问题的解决方案?
队列上绑定多个消费者,提高消费速度
使用惰性队列,可以再mq中保存更多消息
惰性队列的优点有哪些?
基于磁盘存储,消息上限高
没有间歇性的page-out,性能比较稳定
惰性队列的缺点有哪些?
基于磁盘存储,消息时效性会降低
性能受限于磁盘的IO
•普通集群:是一种分布式集群,将队列分散到集群的各个节点,从而提高整个集群的并发能力。
•镜像集群:是一种主从集群,普通集群的基础上,添加了主从备份功能,提高集群的数据可用性。
3.8版本以后推出了仲裁队列代替了镜像集群
特征
1.会在集群的各个节点共享部分数据 2.当访问集群某节点时,如果队列不在该节点,会从数据所在节点传递到当前节点并返回 3.队列所在节点宕机,队列中的消息就会丢失
本质是主从模式
特征
1. 交换机、队列、队列中的消息会在各个mq的镜像节点之间同步备份。 2. 创建队列的节点被称为该队列的**主节点,**备份到的其它节点叫做该队列的**镜像**节点。 3.一个队列的主节点可能是另一个队列的镜像节点 4. 所有操作都是主节点完成,然后同步给镜像节点 5. 主宕机后,镜像节点会替代成新的主
本质镜像集群
特征
1.与镜像队列一样,都是主从模式,支持主从数据同步 2. 使用非常简单,没有复杂的配置 3.主从同步基于Raft协议,强一致
创建仲裁队列
@Bean public Queue quorumQueue() { return QueueBuilder .durable("quorum.queue") // 持久化 .quorum() // 仲裁队列 .build(); }
配置
spring: rabbitmq: addresses: 192.168.150.105:8071, 192.168.150.105:8072, 192.168.150.105:8073 username: itcast password: 123321 virtual-host: /
1)消息可靠性问题 1.消息持久化 2.队列设置 3.消息重试机制、确认机制 2)延迟消息问题 1.用RabbitMQ的延迟插件 2.使用TTL和死信队列实现延迟消息 3)高可用问题 搭建集群? 普通集群、镜像集群(队列类型:仲裁quorum) 怎么搭建的? 1.rabbitMQ服务器要能联通 2.erlang.cookie要一致(认证) 4)消息堆积问题 1.增加消费者数量 2.设置消息 TTL 3.配置死信队列 4.配置 QOS(服务质量) 5.扩容 RabbitMQ 集群 5)消息重复消费 1.把消息转换为“幂等性”消息(首选) 2.记录消息消费日志,判断消费是否重复消费(性能下降) 6)消息顺序消费问题 1.使用单个消费者 2.使用多个队列 3.消息加锁 4.手动应答模式
在项目中用RabbitMQ干嘛了?
延迟消息、提高响应速度、服务间解耦、削峰填谷
你还知道RabbitMQ还能干吗?
日志收集、负载均衡、消息确认和重试
MQ消息是异步发送的,异步是是什么 ?异步和同步什么区别
异步:在主线程中开启一个新线程去做另外事情,无需等待结果
优点 :提高响应速度、解耦、
缺点:时效性差、安全性差
RabbitMQ怎么使用的,聊聊你具体怎么用?
1.搭建服务器
2.项目集成MQ
RabbitMQ开发过程中遇到过什么问题吗?怎么解决的?
消息丢失
业务引入:安全性没有要求的怎么办? 安全性要求比较高
confrim
return
持久化(交换机、队列、消息)
手动确认消息(补充重试策略+重试失败策略)