为什么要用MQ?MQ有什么好处?
1、异步通信:通过异步通信,可以减少客户端等待时间,实现接口快速响应
2、系统解耦:对于复杂的系统,减小系统与系统之间的依赖
3、流量削峰:对于瞬时访问增大,缓解服务器压力,保护服务,同时保证消息不丢失
MQ的主要特点?
1、独立运行的服务
2、使用队列数据结构进行存储数据消息(既然mq使用队列存储消息,为什么不直接使用队列进行通信,因为queue是不能跨进程的)
3、发布、订阅模型,即生产者生产消息,消费者消费消息
使用MQ带来的问题?
1、系统可用性降低了(因为mq需要部署在服务器,如果服务器出问题,系统可用性降低了)
2、系统变复杂了(需要了解mq实现原理,以及解决消息丢失、重复消费等问题)
Mq的基本特性:
1、高可靠:通过发送确认,接收确认、持久化等保证了高可靠
2、灵活的路由:通过交换机4种路由方式
3、支持多客户端:支持多语言
4、集群与扩展性:支持集群、负载均衡
5、高可用队列:镜像集群实现队列消息的复制
6、权限管理:提供给了基于用户与vhost的权限管理
7、插件系统:可视化插件
8、与spring 集成:spring Amqp实现简单
MQ工作模型?
消费者、生产者与broker进行连接采用TCP长连接方式,channel虚拟信道,减少服务器资源的消耗
Rabbit Mq 4种路由方式:
binding key与rounting key
direct直连:
2、topic主题类型的交换机
#号代表:0个或者多个单词(通过英文的.来隔开单词)
*号代表:不多不少1个单词(通过英文的.来隔开单词)
3、fanout广播
4、header模式
header模式与routing不同的地方在于,header模式取消routingkey,使用header中的 key/value(键值对)匹配队列。
TTL过期时间:
队列的消息过期时间x-message-ttl = 10 10s钟过期
单条消息的过期时间?
如果同时设定队列消息过期时间和消息本身的过期时间,哪一个会最先失效?
队列和消息,哪个过期时间设置的小,哪个先过期。
什么是死信交换机和死信队列呢?
设置普通队列的死信交换机,死信交换机与死信队列绑定,当普通队列的消息成为死信消息就会进入死信交换机。
消息什么时候会变成死信呢?
1、消息被消费者拒绝
2、消息过期
3、队列到达了最大长度,将对头的消息丢到死信交换机上(队列到底能存储多少条消息?通过x-max-length、x-max-length-bytes参数可以设置)
死信队列如何使用?
延迟队列
延迟队列的总体方案:
1:先将消息入库,通过定时任务扫描,达到延迟时间后,将消息投递出去
2:死信队列的实现:设置消息过期时间,到达时间后消息进入死信交换机,进入死信队列,消费者消费消息
3:rabbitmq-delayed-message-exchange的实现(Liunx):实现起来简单,死信队列的延迟时间不是很精准
服务端流控
通过x-max-length、x-max-length-bytes这2个参数能实现服务端限流?不能,因为当这2个参数设置的值满足后,会将队头的消息移除
如果服务端产生消息的速度比消费端消费消息的速度快,那就会造成内存空间的大量占用(Mq内存节点),会占用磁盘空间(磁盘节点),如何解决?
默认如果内存低于40%的时候,mq拒绝服务端连接Conn,通过配置参数vm_memory_high_watermark控制
当占用磁盘空间低于30%时候,拒绝服务端连接,通过配置文件参数进行配置disk_free_limit.relative=3.0
当占用磁盘空间低于2G时候,拒绝服务端连接,通过配置文件参数进行配置disk_free_limit.absolute=2G
消费者消费消息会将消息缓存到本地,如果消息过多,而没有消费完,消费端限制通过设置prefetch count属性控制,如果消费端接收消息后比如5条,5条都没有给服务端应答的时候,就暂时不会进行消费消息了
Spring AMQP:
Spring 集成AMQP时,它做了什么?
1、帮助我们去管理对象,通过标签的方式进行配置
2、封装了rabbitMqTemplate来简化配置(rabbitTemplate)实现消息的收发
Spring AMQP包括什么?
ConnectFactory:定义虚拟机的ip、端口等等信息 连接工厂
RabbitAdmin:存放绑定关系、交换机、队列等
Message:消息对象
RabbitTemplate:消息的收发,通过定义不同的RabbitTemplate实现对不同服务器的mq进行发送消息
MessageListenerContainerFactory:动态创建队列、动态消除队列
MessageConvertor:主要是定义传输序列化格式,比如json、xml、object等等
Rabbit Mq可靠性投递与实践经验!!!!
1、掌握Rabbit Mq可靠性消息的投递发送
1)服务端确认机制:
1、tranction模式(不建议使用,这种方式存在阻塞,只有一条消息成功后,才会进行下一条消息)
2、confirm模式(发送一条确认一条,建议使用批量确认)
2到3过程,如何确保交换机可以把消息路由到队列呢?
路由保证:
1、mandatory = true + ReturnListener
2、指定交换机备份交换机
在当前交换机指定备份交换机去保证消息可靠性投递
交换机才有备份交换机,队列才有死信交换机
第3阶段,存储消息持久化:
1)消息持久化
2)队列持久化
3)交换机持久化
4)集群
第4步骤:
消费者确认机制ack:AutoAck
自动ack:接收到消息就发送ack
手动ack:接收到消息到处理完消息,才给服务器发送ack确认消息机制
none:自动应答
manual:手工应答
auto:自动应答,方法没发生异常才会自动应答,如果出错,则发送nack拒绝、requeue重新入队
rabbit Mq 保证消息的顺序性,一定是生产者发送到路由器和队列中的消息,消费者也需要取路由器和队列的消息,这样才能保证消息的顺序性
2、掌握Rabbit Mq集群原理与高可用架构的搭建
rabbit Mq端口:5672(单机端口)、15672(插件端口)、25672(集群端口)
erlang 语言 .erlang.cookie 进行集群通讯
集群节点的类型:disc:将交换机、队列、绑定关系、消息等放到磁盘上(主要进行数据的备份)
RAM:原数据放到内存中(内存节点更快速,适合应用连接)
1. 消息队列的作用与使用场景
异步:批量数据异步处理(批量上传文件)
削峰:高负载任务负载均衡(电商秒杀抢购)
解耦:串行任务并行化(退货流程解耦)
广播:基于Pub/Sub实现一对多通信
2. 多个消费者监听一个队列时,消息如何分发
轮询:默认的策略,消费者轮流,平均地接收消息
公平分发:根据消费者的能力来分发消息,给空闲的消费者发送更多消息
//当消费者有x条消息没有响应ACK时,不再给这个消费者发送消息
channel.basicQos(int x)
3. 无法被路由的消息去了哪里
无设置的情况下,无法路由(Routing key错误)的消息会被直接丢弃
解决方案:
将mandatory设置为true,并配合ReturnListener,实现消息的回发
声明交换机时,指定备份的交换机
Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("alternate-exchange","备份交换机名");
4. 消息在什么时候会变成死信
消息拒绝并且没有设置重新入队
消息过期
消息堆积,并且队列达到最大长度,先入队的消息会变成DL
5. RabbitMQ如何实现延时队列
利用TTL(队列的消息存活时间或者消息存活时间),加上死信交换机
// 设置属性,消息10秒钟过期
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.expiration("10000") // TTL // 指定队列的死信交换机
Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");
6. 如何保证消息的可靠性投递
发送方确认模式:
将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。
一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。
如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(not acknowledged,未确认)消息。
发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。
接收方确认机制
接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。
这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。保证数据的最终一致性;
下面罗列几种特殊情况:
如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)
如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。
7. 消息幂等性
生产者方面:可以对每条消息生成一个msgID,以控制消息重复投递
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
porperties.messageId(String.valueOF(UUID.randomUUID()))
消费者方面:消息体中必须携带一个业务ID,如银行流水号,消费者可以根据业务ID去重,避免重复消费
8. 消息如何被优先消费
//生产者
Map<String, Object> argss = new HashMap<String, Object>();
argss.put("x-max-priority",10);
//消费者
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.priority(5) // 优先级,默认为5,配合队列的 x-max-priority 属性使用
9. 如何保证消息的顺序性
一个队列只有一个消费者的情况下才能保证顺序,否则只能通过全局ID实现(每条消息都一个msgId,关联的消息拥有一个parentMsgId。可以在消费端实现前一条消息未消费,不处理下一条消息;也可以在生产端实现前一条消息未处理完毕,不发布下一条消息)
10. RabbitMQ的集群模式和集群节点类型
mq集群节点分为内存节点和磁盘节点
**普通模式:**默认模式,以两个节点(rabbit01,rabbit02)为例来进行说明,对于Queue来说,消息实体只存在于其中一个节点rabbit01(或者rabbit02),rabbit01和rabbit02两个节点仅有相同的元数据,即队列结构。当消息进入rabbit01节点的Queue后,consumer从rabbit02节点消费时,RabbitMQ会临时在rabbit01,rabbit02间进行消息传输,把A中的消息实体取出并经过B发送给consumer,所以consumer应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理Queue。否则无论consumer连rabbit01或rabbit02,出口总在rabbit01,会产生瓶颈。当rabbit01节点故障后,rabbit02节点无法取到rabbit01节点中还未消费的消息实体。如果做了消息持久化,那么等到rabbit01节点恢复,然后才可被消费。如果没有消息持久化,就会产生消息丢失的现象。
**镜像模式:**把需要的队列做成镜像队列,存在与多个节点属于RabibitMQ的HA方案,该模式解决了普通模式中的问题,其实质和普通模式不同之处在于,消息体会主动在镜像节点间同步,而不是在客户端取数据时临时拉取,该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉,所以在对可靠性要求比较高的场合中适用
节点分为内存节点(保存状态到内存,但持久化的队列和消息还是会保存到磁盘),磁盘节点(保存状态到内存和磁盘),一个集群中至少需要一个磁盘节点
11.如何自动删除长时间没有消费的消息
// 通过队列属性设置消息过期时间
Map<String, Object> argss = new HashMap<String, Object>();
argss.put("x-message-ttl",6000);
// 对每条消息设置过期时间
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.expiration("10000") // TTL
12.消息基于什么传输
AMQP协议,RabbitMQ使用信道的方式来传输数据。信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制
13.如何确保消息不丢失
消息持久化,当然前提是队列必须持久化
RabbitMQ确保持久性消息能从服务器重启中恢复的方式是,将它们写入磁盘上的一个持久化日志文件,当发布一条持久性消息到持久交换器上时,Rabbit会在消息提交到日志文件后才发送响应。
一旦消费者从持久队列中消费了一条持久化消息,RabbitMQ会在持久化日志中把这条消息标记为等待垃收集。如果持久化消息在被消费之前RabbitMQ重启,那么Rabbit会自动重建交换器和队列(以及绑定),并重新发布持久化日志文件中的消息到合适的队列。
channel和vhost的作用是什么?
channal是减少连接数
vhost节省硬件资源
交换机与队列、队列与消费者的绑定关系?
多对多
队列与消费者为1对多的时候,会采用轮训机制调用消费者,每次拿几条是prefetch count来决定的
无法被路由的消息,去哪里了?
直接被丢弃 解决是备份交换机、回发
消息在什么情况下会进入死信?
消息过期、消息被拒绝、消息超过了队列的长度
rabbit Mq如何实现延迟队列?
1、将消息放入数据库,通过定时任务去扫描,发送消息
2、通过TTl加死信队列来实现延迟队列
3、通过delay-message插件
哪些情况会导致消息丢失?如何解决?
一个队列最多可以存放多少条消息?取决于硬件配置,如果参数做了持久化,无论是内存节点还是磁盘节点都会放到磁盘上
如何提高消息的消费速率呢?
增加消费者
如何动态的创建队列和消费者?listenerContainer
大量消息堆积如何处理?增加消费者、直接把队列清空掉、重启服务
设计一个mq?
存储、转发