文章目录
- 1.Kafka为什么比RocketMQ的吞吐量要高
- 2.Kafka的Pul和Push分别有什么优缺点
- 3.Kafka是pull?push?优劣势分析
- 4.Kafka、ActiveMO、RabbitMO.RocketMQ 对比
- 5.Kafka消息高可靠解决方案
- 6.kafka高性能高吞吐的原因
- 7.Kafka消息丢失的场景及解决方案
- 8.Kafka中zk的作用
- 9.Kafka中高性能的原因分析
- 10.MQ如何保证消费幂等性
- 11.MQ如何保证分布式事务的最终一致性
- 12.MQ如何进行产品选型
- 13.MQ如何保证消息的高效读写
- 14.MQ如何保证消息顺序
- 15.MQ如何保证消息不丢失
- 16.rabbitmq的镜像队列原理
- 17.MQ有什么用
- 18.rabbitmq的死信队列、延迟队列原理
- 19.RabbitMQ如何保证消息的可靠性传输
- 20.Rabbitmq事务消息
- 21.RabbitMQ如何确保消息发送?消息接收?
- 22.rabbitmq可以直连队列么
- 23.RocketMQ 如何保证不丢消息
- 24.RabbitMQ死信队列、延时队列
- 25.RocketMQ的底层实现原理
- 26.RocketMQ 事务消息原理
- 27.简述kafka的rebalance机制
- 28.简述kafka的副本同步机制
- 29.RocketMQ 怎么实现顺序消息
- 30.简述kafka架构设计
- 31.简述rabbitmq的持久化机制
- 32.简述RabbitMq的交换机类型
- 33.简述rabbitmq的普通集群模式
- 34.简述RabbitMQ架构设计
- 35.简述RabbitMQ事务消息机制
- 36.简述RocketMQ 架构设计
- 37.简述RocketMQ 持久化机制
- 38.死信队列是什么?延时队列是什么?
- 39.如何保证消息不被重复消费
- 40.消息队列有哪些作用
- 41.消息队列如何保证消息可靠传输
- 42.如何设计一个MQ
- 43.消息队列的优缺点,使用场景
1.Kafka为什么比RocketMQ的吞吐量要高
Kafka的⽣产者采⽤的是异步发送消息机制,当发送⼀条消息时,消息并没有发送到Broker⽽是缓存起 来,然后直接向业务返回成功,当缓存的消息达到⼀定数量时再批量发送给Broker。这种做法减少了⽹络io,从⽽提⾼了消息发送的吞吐量,但是如果消息⽣产者宕机,会导致消息丢失,业务出错,所以理 论上kafka利⽤此机制提⾼了性能却降低了可靠性。
2.Kafka的Pul和Push分别有什么优缺点
-
pull表示消费者主动拉取,可以批量拉取,也可以单条拉取,所以pull可以由消费者⾃⼰控制,根据⾃⼰的消息处理能⼒来进⾏控制,但是消费者不能及时知道是否有消息,可能会拉到的消息为空
-
push表示Broker主动给消费者推送消息,所以肯定是有消息时才会推送,但是消费者不能按⾃⼰的能⼒来消费消息,推过来多少消息,消费者就得消费多少消息,所以可能会造成⽹络堵塞,消费者压⼒⼤等问题
3.Kafka是pull?push?优劣势分析
PuIl模式:
- 根据consumer的消费能力进行数据拉取,可以控制速率
- 可以批量拉取、也可以单条拉取
- 可以设置不同的提交方式,实现不同的传输语义
缺点:如果kafka没有数据,会导致consumer空环,消耗资源
解决:通过参数设置,consumer拉取数据为空或者没有达到一定数量时进行阻塞
push模式: 不会导致consumer循环等待
缺点:速率固定、忽略了consumer的消费能力,可能导致拒绝服务或者网络拥塞等情况
4.Kafka、ActiveMO、RabbitMO.RocketMQ 对比
ActiveMQ:JMS规范,支持事务、支持XA协议,没有生产大规模支撑场景、官方维护越来越少
RabbitMQ:erlang语言开发、性能好、高并发,支持多种语言,社区、文档方面有优势,erlang语言不利于java程序员二次开发,依赖开源社区的维护和升级,需要学习AMQP协议、学习成本相对较高以上吞吐量单机都在万级
kafka:高性能,高可用,生产环境有大规模使用场景,单机容量有限(超过64个分区响应明显变长)、社区更新慢 吞吐量单机百万
rocketmq:java实现,方便二次开发、设计参考了kafka,高可用、高可靠,社区活跃度一般、支持语 言较少 吞吐量单机十万
5.Kafka消息高可靠解决方案
消息发送:
- ack:0、不重试,1、lead写入成功就返回了,all/-1、等待ISR同步完再返回
- unclean.leader.election.enable : false,禁止选举ISR以外的follower为leader
- tries > 1,重试次数
- min.insync.replicas > 1:同步副本数,没满足该值前、不提供读写服务、写操作会异常
消费:
手工提交offset
broker:减小刷盘间隔
6.kafka高性能高吞吐的原因
1、磁盘顺序读写:保证了消息的堆积
- 顺序读写,磁盘会预读,预读即在读取的起始地址连续读取多个页面,主要时间花费在了传输时 间,而这个时间两种读写可以认为是一样的。
- 随机读写,因为数据没有在一起,将预读浪费掉了。需要多次寻道和旋转延迟。而这个时间可能是 传输时间的许多倍。
2、零拷贝:避免 CPU 将数据从一块存储拷贝到另外一块存储的技术
-
传统的数据复制:
1、读取磁盘文件数据到内核缓冲区
2、将内核缓冲区的数据copy到用户缓冲区
3、将用户缓冲区的数据copy到socket的发送缓冲区
4、将socket发送缓冲区中的数据发送到网卡、进行传输 -
零拷贝:
磁盘文件->内核空间读取缓冲区->网卡接口->消费者进程
3、分区分段+索引
Kafka的message消息实际上是分布式存储在一个一个小的segment中的,每次文件操作也是直接操 作的segment。为了进一步的查询优化,Kafka又默认为分段后的数据文件建立了索引文件,就是文件
系统上的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操作的并行度
4、批量压缩:多条消息一起压缩,降低带宽
5、批量读写
6、直接操作page cache,而不是JVM、避免GC耗时及对象创建耗时,且读写速度更高,进程重启、缓 存也不会丢失
7.Kafka消息丢失的场景及解决方案
1)消息发送
1、ack=0,不重试
producer发送消息完,不管结果了,如果发送失败也就丢失了。
2、ack=1,1eader crash
producer发送消息完,只等待1ead写入成功就返回了,1eader crash了,这时fo11ower没米及同步,消息丢失。
3、unclean.leader.election.enable true允许选举ISR以外的副本作为1eader,会导致数据丢失,默认为fa1se。
producer发送异步消息完,只等待1ead写入成功就返回了,1eader crash了,这时ISR中没有fo11ower,1eader从OSR中选举,因为OSR中本米落后于Leader造成消息丢失。
解决方案:
1、配置:ack=a11/-l,tries >1,unclean.leader.election.enable : false
producer发送消息完,等待fo11ower同步完再返回,如果异常则承试。削本的数量可能影响吞吐量。
不允许选举ISR以外的副本作为1eader。
2、配界:min.insync.replicas >1
副本指定必须确认写操作成功的最小副本数量。如果不能满足这个最小值,则生产者将引发一个异常(要么是NotEnoughReplicas,要么是NotEnoughReplicasAfterAppend)
min,insync.replicas和ack更大的持久性保证。确保如果大多数副本没有收到写操作,则生产者将引发异常。
3、失败的offset单独记录
producer发送消息,会自动重试,遇到不可恢复异常会抛出,这时可以捕获异常记录到数据库或缓存,进行单独处理。
2)消费
先commit再处理消息。如果在处理消息的时候异常了,但是offset已经提交了,这条消息对于该消费者来说就是丢失了,再也不会消费到了。
3)broker的刷盘
减小刷盘间隔
8.Kafka中zk的作用
/brokers/ids:临时节点,保存所有broker节点信息,存储broker的物理地址、版本信息、启动时间等,节点名称为brokerID,broker定时发送心跳到zk,如果断开则该brokerID会被删除
/brokers/topics:临时节点,节点保存broker节点下所有的topic信息,每一个topic节点下包含一个固定的partitions节点,partitions的子节点就是topic的分区,每个分区下保存一个state节点、保存着当前leader分区和ISR的brokeriD,state节点由leader创建,若leader宕机该节点会被制除,直到有新的leader选举产生、重新生成state节点
/consumers/[group_id]/owners/[topic]/[broker_id-partitidn_id]:维护消费者和分区的注册关系
/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id]:分区消息的消费进度Offset
client通过topic找到topic树下的state节点、获取leader的brokerlD,到broker树中找到broker的物理地址,但是client不会直连zk,而是通过配置的broker获取到zk中的信息
9.Kafka中高性能的原因分析
kafka不基于内存,而是硬盘存储,因此消息堆积能力更强
顺序写:利用磁盘的顺序访问速度可以接近内存,kafka的消息都是append操作,partition是有序的,节省了磁盘的寻道时间,同时通过批量操作、节省写入次数,partition物理上分为多个segment存储,方便删除
传统:
- 读取磁盘文件数据到内核缓冲区
- 将内核缓冲区的数据copy到用户缓冲区
- 将用户缓冲区的数据copy到socket的发送缓冲区
- 将socket发送缓冲区中的数据发送到网卡、进行传输
零拷贝:
- 直接将内核缓冲区的数据发送到网卡传输
- 使用的是操作系统的指令支持
kafka不太依赖jvm,主要理由操作系统的pageCache,如果生产消费速率相当,则直接用pageCache交换数据不需要经过磁盘IO
10.MQ如何保证消费幂等性
其实就是要方式消费者重复消费消息的问题。
所有MQ产品并没有提供主动解决幂等性的机制,需要由消费者自行控制。
RocketMQ: 给每个消息分配了个MessageID。这个MessageID就可以作为消费者判断幂等的依据。这种方式不太建议。
最好的方式就是自己带一个有业务标识的ID,来进行幂等判断。OrderID统一ID分配。
11.MQ如何保证分布式事务的最终一致性
分布式事务:业务相关的多个操作,保证他们同时成功或者同时失败。
最终一致性: 与之对应的就是强一致性
MQ中要保护事务的最终一致性,就需要做到两点
1、生产者要保证100%的消息投递。 事务消息机制
2、消费者这一端需要保证幂等消费。 唯一ID+ 业务自己实现幂等
分布式MQ的三种语义:
at least once
at most once
exactly once:
RocketMQ 并不能保证exactly once。商业版本当中提供了exactly once的实现机制。
kafka: 在最新版本的源码当中,提供了exactly once的demo。
RabbitMQ: erlang天生就成为了一种屏障。
12.MQ如何进行产品选型
Kafka
优点: 吞吐量非常大,性能非常好,集群高可用。
缺点:会丢数据,功能比较单一。
使用场景:日志分析、大数据采集
RabbitMQ
优点: 消息可靠性高,功能全面。
缺点:吞吐量比较低,消息积累会严重影响性能。erlang语言不好定制。
使用场景:小规模场景。
RocketMQ
优点:高吞吐、高性能、高可用,功能非常全面。
缺点:开源版功能不如云上商业版。官方文档和周边生态还不够成熟。客户端只支持java。
使用场景:几乎是全场景。
13.MQ如何保证消息的高效读写
零拷贝: kafka和RocketMQ都是通过零拷贝技术来优化文件读写。
传统文件复制方式: 需要对文件在内存中进行四次拷贝。
零拷贝: 有两种方式, mmap和transfile
Java当中对零拷贝进行了封装, Mmap方式通过MappedByteBuffer对象进行操作,而transfile通过FileChannel来进行操作。
Mmap 适合比较小的文件,通常文件大小不要超过1.5G ~2G 之间。
Transfile没有文件大小限制。
RocketMQ当中使用Mmap方式来对他的文件进行读写。commitlog。 1G
在kafka当中,他的index日志文件也是通过mmap的方式来读写的。在其他日志文件当中,并没有使用零拷贝的方式。
kafka使用transfile方式将硬盘数据加载到网卡。
14.MQ如何保证消息顺序
全局有序和局部有序: MQ只需要保证局部有序,不需要保证全局有序。
生产者把一组有序的消息放到同一个队列当中,而消费者一次消费整个队列当中的消息。
RocketMQ中有完整的设计,但是在RabbitMQ和Kafka当中,并没有完整的设计,需要自己进行设计。
RabbitMQ:要保证目标exchange只对应一个队列。并且一个队列只对应一个消费者。
Kafka: 生产者通过定制partition分配规则,将消息分配到同一个partition。 Topic下只对应一个消费者。
15.MQ如何保证消息不丢失
1、哪些环节会造成消息丢失?
2、怎么去防止消息丢失。
2.1 生产者发送消息不丢失
kafka: 消息发送+回调
RocketMQ: 1、消息发送+回调。
2、事务消息。
RabbitMQ:
1、消息发送+回调
2、 手动事务: channel.txSelect()开启事务, channel.txCommit()提交事务, channel.txRollback()回滚事务。这种方式对channel是会产生阻塞的,造成吞吐量下降。
3、Publisher Confirm。整个处理流程跟RocketMQ的事务消息,基本是一样的。
2.2 MQ主从消息同步不丢失
RocketMQ:
1、普通集群中,同步同步、异步同步。异步同步效率更高,但是有丢消息的风险。同步同步就不会丢消息。
2、Dledger集群-两阶段提交:
RabbitMQ:
普通集群:消息是分散存储的,节点之间不会主动进行消息同步,是有可能丢失消息的。
镜像集群:镜像集群会在节点之间主动进行数据同步,这样数据安全性得到提高。
Kafka: 通常都是用在允许消息少量丢失的场景。acks。0,1,all
2.3 MQ消息存盘不丢失
RocketMQ: 同步刷盘 异步刷盘:异步刷盘效率更高,但是有可能丢消息。同步刷盘消息安全性更高,但是效率会降低。
RabbitMQ: 将队列配置成持久化队列。新增的Quorum类型的队列,会采用Raft协议来进行消息同步。
2.4 MQ消费者消费消息不丢失
RocketMQ: 使用默认的方式消费就行, 不要采用异步方式。
RabbitMQ: autoCommit -> 手动提交offset
Kafka: 手动提交offset
16.rabbitmq的镜像队列原理
GM负责消息的广播,所有的GM组成gm_group,形成链表结构,负责监听相邻节点的状态,以及传递消息到相邻节点,master的GM收到消息时代表消息同步完成
mirror_queue_master/slave负责消息的处理,操作blockingQueue,Queue负责AMQP协议(commit.rollback、ack等)
master处理读写
17.MQ有什么用
MQ: MessageQueue,消息队列。 队列是一种FIFO先进先出的数据结构。消息由生产者发送到MQ进行排队,然后由消费者对消息进行处理。QQ、 微信 就是典型的MQ场景。
MQ的作用主要有三个方面:
1、异步:
例子:快递。 快递员-> 菜鸟驿站<- 客户
作用:异步能提高系统的响应速度和吞吐量。
2、解耦:
例子:《Thinking in java》 -> 编辑社
作用:服务之间进行解耦,可以减少服务之间的影响,提高系统的稳定性和可扩展性。
另外,解耦之后可以实现数据分发。生产者发送一个消息后,可以由多个消费者来处理。
3、削峰:
例子:长江涨水->三峡大坝
作用:以稳定的系统资源应对突发的流量冲击。
MQ的缺点:
1、系统可用性降低: 一旦MQ宕机,整个业务就会产生影响。高可用
2、系统的复杂度提高: 引入MQ之后,数据链路就会变得很复杂。如何保证消息不丢失?消息不会重复调用?怎么保证消息的顺序性?、、、、、
3、数据一致性: A系统发消息,需要由B、C两个系统一同处理。如果B系统处理成功、C系统处理失败,这就会造成数据一致性的问题。
18.rabbitmq的死信队列、延迟队列原理
死信消息:
- 消息被消费方否定确认,使用 channel.basicNack 或 channel.basicReject ,并且此时 requeue 属性被设置为 false 。
- 消息在队列的存活时间超过设置的TTL时间。
- 消息队列的消息数量已经超过最大队列长度。
那么该消息将成为死信消息。如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有 配置,则该消息将会被丢弃
为每个需要使用死信的业务队列配置一个死信交换机,同一个项目的死信交换机可以共用一个,然后为 每个业务队列分配一个单独的routeKey,死信队列只不过是绑定在死信交换机上的队列,死信交换机也 不是什么特殊的交换机,只不过是用来接受死信的交换机,所以可以为任何类型【Direct、Fanout、 Topic】
TTL:一条消息或者该队列中的所有消息的最大存活时间
如果一条消息设置了TTL属性或者进入了设置TTL属性的队列,那么这条消息如果在TTL设置的时间内没 有被消费,则会成为“死信”。如果同时配置了队列的TTL和消息的TTL,那么较小的那个值将会被使用。
只需要消费者一直消费死信队列里的消息
agruments.put("x-dead-letter-exchange", "dlx.exchange");
channel.queueDeclare(queueName, true, false, false, agruments);
channel.queueBind(queueName, exchangeName, routingKey);
channel.exchangeDeclare("dlx.exchange", "topic", true, false, null);
channel.queueDeclare("dlx.queue", true, false, false, null);
channel.queueBind("dlx.queue", "dlx.exchange", "#");
19.RabbitMQ如何保证消息的可靠性传输
1、使用事务消息
2、使用消息确认机制
发送方确认:
- channel设置为confirm模式,则每条消息会被分配一个唯一id
- 消息投递成功,信道会发送ack给生产者,包含了id,回调ConfirmCallback接口
- 如果发生错误导致消息丢失,发生nack给生产者。回调ReturnCallback接口
- ack和nack只有一个触发,且只有一次,异步触发。可以继续发送消息
接收方确认:
- 声明队列时,指定noack=false,broker会等待消费者手动返回ack、才会删除消息,否则立刻删除
- broker的ack没有超时机制,只会判断链接是否断开,如果断开、消息会被重新发送
20.Rabbitmq事务消息
通过对信道的设置实现
1 . channel.txSelect0);通知服务器开启事务模式;服务端会返回Tx.Select-Ok
2. channel.basicPublish;发送消息,可以是多条,可以是消费消息提交ack
3. channel.txCommit0提交事务:
4. channel.txRollback()回滚事务;
消费者使用事务:
- autoAck=false,手动提交ack,以事务提交或回滚为准;
- autoAck=true,不支持事务的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队列已经把消息移除了
如果其中任意一个环节出现问题,就会抛出l0Exception异常,用户可以拦截异常进行事务回滚,或决定要不要重复消息。
事务消息会降低rabbitmg的性能
21.RabbitMQ如何确保消息发送?消息接收?
发送方确认机制:
信道需要设置为confirm模式,则所有在信道上发布的消息都会分配一个唯一 ID。
一旦消息被投递到queue(可持久化的消息需要写入磁盘),信道会发送一个确认给生产者(包含消息唯一 ID)。
如果 RabbitMq 发生内部错误从而导致消息丢失,会发送一条nack(未确认)消息给生产者。
所有被发送的消息都将被 confirm(即 ack) 或者被nack一次。但是没有对消息被 confirm 的快慢做任何保证,并且同一条消息不会既被 confirm又被nack
发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者,生产者的回调方法会被触发。
ConfirmCa11back接口:只确认是否正确到达Exchange,成功到达则网调
Returnca11back接口:消息失败返回时回调
接收方确认机制:
消费者在声明队列时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(或者磁盘,持久化消息)中移去消息。否则,消息被消费后会被立即删除。
消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费着确认了消息,RabbitMQ才能安全地把消息从队列中剧除。
RabbitMQ不会为未ack的消息设置超时时间,它判断此消县是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbithQ允许消费各消费一条消息的时间可以很长。保证数据的最终一致性;
如果消费者返回ack之前断开了链接,RabbitMQ 会重新分发给下一个订阅的消费者,(可能存在消息重复消费的隐患,需要去重)
22.rabbitmq可以直连队列么
生产者和消费者使用相同的参数声明队列。重复声明不会改变队列
//生产者
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//发送10条消息,依次在消息后面附加1-10个点
for (int i = 6; i > 0; i--)
{
String message = "helloworld";
channel.basicPublish("", QUEUE_NAME,null, message.getBytes());
}
//消费者
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
QueueingConsumer consumer = new QueueingConsumer(channel);
// 指定消费队列
channel.basicConsume(QUEUE_NAME, true, consumer);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
doWork(message);
}
channel.queueDeclare(queue, durable, exclusive, autoDelete, arguments) ;
queue:队列名字
durable:队列持久化标志,true为持久化队列
exclusive:exclusive:排他队列,仅对创建的链接可见、链接中的channel都可见,其他链接不能重复
声明,链接关闭队列会被自动删除
autoDelete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于临
时队列。
arguments:Map类型,队列参数设置
x-message-ttl:数字,消息队列中消息的存活时间,超过会被删除
x-expires:数字,队列自身的空闲存活时间,指定时间内没有被访问,就会被删除
x-max-length和x-max-length-bytes:队列最大长度和空间,超出会删除老的数据
x-dead-letter-exchange和x-dead-letter-routing-key:设置死信
x-max-priority:队列支持的优先级别,需要生产者在发送消息时指定,消息按照优先级从高到底分
发给消费者
channel.basicPublish(exchange, routingKey, mandatory, immediate,
basicProperties, body);
exchange: 交换机名
routingKey: 路由键
mandatory:为true时,如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那
么会调用basic.return方法将消息返还给生产者, channel.addReturnListener添加一个监听器,当
broker执行basic.return方法时,会回调handleReturn方法,这样就可以处理变为死信的消息了;设为
false时,出现上述情形broker会直接将消息扔掉;
immediate: 3.0以前这个标志告诉服务器如果该消息关联的queue上有消费者,则马上将消息投递给它,如
果所有queue都没有消费者,直接把消息返还给生产者,不用将消息入队列等待消费者了。3.0之后取消了该
参数
basicProperties:消息的详细属性,优先级别、持久化、到期时间等,headers类型的exchange要用到
的是其中的headers字段。
body:消息实体,字节数组。
QueueingConsumer:一个已经实现好了的Consumer,相比于自己实现Consumer接口,这是个比较安全快
捷的方式。该类基于jdk的BlockingQueue实现,handleDelivery方法中将收到的消息封装成Delivery
对象,并存放到BlockingQueue中,这相当于消费者本地存放了一个消息缓存队列。nextDelivery()方法
底层调用的BlockingQueue的阻塞方法take()。
channel.basicConsume(queue, autoAck, consumer);
queue:队列名。
autoAck:自动应答标志,true为自动应答。
consumer:消费者对象,可以自己实现Consumer接口,建议使用QueueingConsumer。
23.RocketMQ 如何保证不丢消息
生产者:
- 同步阻塞的方式发送消息,加上失败重试机制,可能broker存储失败,可以通过查询确认
- 异步发送需要重写回调方法,检查发送结果
- ack机制,可能存储CommitLog,存储ConsumerQueue失败,此时对消费者不可见
broker:同步刷盘、集群模式下采用同步复制、会等待slave复制完成才会返回确认
消费者:
- offset手动提交,消息消费保证幂等
24.RabbitMQ死信队列、延时队列
- 消息被消费方否定确认,使用 channe1.basicNack或 channe1.basicReject ,并且此时requeue 属性被设置为fa1se。
- 消息在队列的存活时间超过设置的TL时间,
- 消息队列的消息数量已经超过最大队列长度。
那么该消息将成为“死信”。“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃
为每个需要使用死信的业务队列配置一个死信交换机,这里同一个项目的死信交换机可以共用一个,然后为每个业务队列分配一个单独的路由key,死信队列只不过是绑定在死信交换机上的队列,死信交换机也不是什么特殊的交换机,只不过是用来接受死信的交换机,所以可以为任何类型【Direct、Fanout、Topic】
TTL:一条消息或者该队列中的所有消息的最大存活时间
如果一条消息设置了TL属性或者进入了设置TL属性的队列,那么这条消息如果在TL设置的时间内没有被消费则会成为“死信”。如果同时配置了队列的TTL和消息的TTL,那么较小的那个值将会被使用。
只需要消费者一直消费死信队列里的消息
25.RocketMQ的底层实现原理
RocketMQ由NameServer集群、Producer集群、Consumer集群、Broker集群组成,消息⽣产和消费
的⼤致原理如下:
- Broker在启动的时候向所有的NameServer注册,并保持⻓连接,每30s发送⼀次⼼跳
- Producer在发送消息的时候从NameServer获取Broker服务器地址,根据负载均衡算法选择⼀台服
务器来发送消息
- Conusmer消费消息的时候同样从NameServer获取Broker地址,然后主动拉取消息来消费
26.RocketMQ 事务消息原理
依赖于TransactionListener接口
- executeLocalTransaction方法会在发送消息后调用,用于执行本地事务,如果本地事务执行成 功,rocketmq再提交消息
- checkLocalTransaction用于对本地事务做检查,rocketmq依赖此方法做补偿
通过两个内部的topic来实现对消息的两阶段支持,
prepare:将消息(消息上带有事务标识)投递到一个名为RMS_SYS_TRANS_HALF_TOPIC的topic中, 而不是投递到真正的topic中。
commit/rollback:producer再通过TransactionListener的executeLocalTransaction方法执行本地事 务,当producer的localTransaction处理成功或者失败后,producer会向broker发送commit或 rollback命令,如果是commit,则broker会将投递到RMQ_SYS_TRANS_HALF_TOPIC中的消息投递到 真实的topic中,然后再投递一个表示删除的消息到RMQ_SYS_TRANS_OP_HALF_TOPIC中,表示当前事 务已完成;如果是rollback,则没有投递到真实topic的过程,只需要投递表示删除的消息到 RMQ_SYS_TRANS_OP_HALF_TOPIC。最后,消费者和消费普通的消息一样消费事务消息
- 第一阶段(prepare)失败:给应用返回发送消息失败
- 事务失败:发送回滚命令给broker,由broker执行消息的回滚
- Commit或rollback失败:由broker定时向producer发起事务检查,如果本地事务成功,则提交消息事务,否则回滚消息事务
事务状态的检查有两种情况:
- commit/rollback:broker会执行相应的commit/rollback操作
- 如果是TRANSACTION_NOT_TYPE,则一段时间后会再次检查,当检查的次数超过上限(默认15 次)则丢弃消息
27.简述kafka的rebalance机制
consumer group中的消费者与topic下的partion重新匹配的过程
何时会产生rebalance:
- consumer group中的成员个数发生变化
- consumer消费超时
- group订阅的topic个数发生变化
- group订阅的topic的分区数发生变化
coordinator:通常是partition的leader节点所在的broker,负责监控group中consumer的存活,
consumer维持到coordinator的心跳,判断consumer的消费超时
- coordinator通过心跳返回通知consumer进行rebalance
- consumer请求coordinator加入组,coordinator选举产生leader consumer
- leader consumer从coordinator获取所有的consumer,发送syncGroup(分配信息)给到 coordinator
- coordinator通过心跳机制将syncGroup下发给consumer
- 完成rebalance
leader consumer监控topic的变化,通知coordinator触发rebalance
如果C1消费消息超时,触发rebalance,重新分配后、该消息会被其他消费者消费,此时C1消费完成提 交offset、导致错误
解决:coordinator每次rebalance,会标记一个Generation给到consumer,每次rebalance该 Generation会+1,consumer提交offset时,coordinator会比对Generation,不一致则拒绝提交
28.简述kafka的副本同步机制
LEO:下一条待写入位置
firstUnstableOffset:第一条未提交数据
LastStableOffset:最后一条已提交数据
LogStartOffset:起始位置
isolation.level=read_committed:只能消费到LastStableOffset,read_committed可以消费到HW的 上一条
一个partition对应的ISR中最小的LEO作为分区的HW,consumer最多只能消费到HW所在的位置
leader收消息后会更新本地的LEO,leader还会维护follower的LEO即remote LEO,follower发出fetch 同步数据请求时(携带自身的LEO)、leader会更新remote LEO,更新分区的HW,然后将数据响应给 follower、follower更新自身HW(取响应中的HW和自身的LEO中的较小值),LEO+1
ISR:如果一个follower落后leader不超过某个时间阈值,那么则则ISR中,否则将放在OSR中。
同步副本时,follower获取leader的LEO和LogStartOffset,与本地对比、如果本地的LogStartOffset超 出了leader的值,则超过这个值的数据删除,再进行同步,如果本地的小于leader的、则直接同步
29.RocketMQ 怎么实现顺序消息
默认是不能保证的,需要程序保证发送和消费的是同一个queue,多线程消费也无法保证
发送顺序:发送端自己业务逻辑保证先后,发往一个固定的queue,生产者可以在消息体上设置消息的 顺序
发送者实现MessageQueueSelector接口,选择一个queue进行发送,也可使用rocketmq提供的默认 实现
- SelectMessageQueueByHash:按参数的hashcode与可选队列进行求余选择
- SelectMessageQueueByRandom:随机选择
mq:queue本身就是顺序追加写,只需保证一个队列统一时间只有一个consumer消费,通过加锁实 现,consumer上的顺序消费有一个定时任务、每隔一定时间向broker发送请求延长锁定
消费端:
pull模式:消费者需要自己维护需要拉取的queue,一次拉取的消息都是顺序的,需要消费端自己保证 顺序消费
push模式:消费实例实现自MQPushConsumer接口,提供注册监听的方法消费消息registerMessageListener、重载方法
- MessageListenerConcurrently :并行消费
- MessageListenerOrderly :串行消费,consumer会把消息放入本地队列并加锁,定时任务保证 锁的同步
30.简述kafka架构设计
Consumer Group:消费者组,消费者组内每个消费者负责消费不同分区的数据,提高消费能力。逻辑上的一个订阅者。
Topic:可以理解为一个队列,Topic 将消息分类,生产者和消费者面向的是同一个Topic。
Partition:为了实现扩展性,提高并发能力,一个Topic以多个Partition的方式分布到多个 Broker 上,每个Partition 是一个有序的队列。一个Topic 的每个Partition都有若干个副本(Replica),一个Leader 和若千个Follower。生产者发送数据的对象,以及消费者消费数据的对象,都是 Leader。Follower负责实时从 Leader 中同步数据,保持和 Leader 数据的同步。Leader 发生故障时,某个Folower 还会成为新的 Leader。
31.简述rabbitmq的持久化机制
1、交换机持久化:exchange_declare创建交互机时通过参数指定
2、队列持久化:queue_declare创建队列时通过参数指定
3、消息持久化:new AMQPMessage创建消息时通过参数指定
append的方式写文件,会根据大小自动生成新的文件,rabbitmq启动时会创建两个进程,一个负责持 久化消息的存储,另一个负责非持久化消息的存储(内存不够时)
消息存储时会在ets表中记录消息在文件中的映射以及相关信息(包括id、偏移量,有效数据,左边文 件,右边文件),消息读取时根据该信息到文件中读取、同时更新信息
消息删除时只从ets删除,变为垃圾数据,当垃圾数据超出比例(默认50%),并且文件数达到3个,触 发垃圾回收,锁定左右两个文件,整理左边文件有效数据、将右边文件有效数据写入左边,更新文件信 息,删除右边,完成合并。当一个文件的有用数据等于0时,删除该文件。
写入文件前先写buffer缓冲区,如果buffer已满,则写入文件(此时只是操作系统的页存)
每隔25ms刷一次磁盘,不管buffer满没满,都将buffer和页存中的数据落盘
每次消息写入后,如果没有后续写入请求,则直接刷盘
32.简述RabbitMq的交换机类型
交换器分发会先找出绑定的队列,然后再判断 routekey ,来决定是否将消息分发到某一个队列中
Channel channel = connection.createChannel(); //在rabbitmq中创建一个信道
channel.exchangeDeclare("exchangeName", "direct"); //创建一个type为direct的交换器
channel.queueDeclare("queueName"); //创建一个队列
channel.queueBind("queueName", "exchangeName", "zhangsna"); //绑定并设置路由键
channel.queueBind("queueName", "exchangeName", "lisi"); //绑定并设置路由键
channel.queueBind("queueName", "exchangeName", "wangwu");//绑定并设置路由键
fanout:扇形交换机,不再判断routekey,直接将消息分发到所有绑定的队列
direct:判断routekey的规则是完全匹配模式,即发送消息时指定的routekey要等于绑定的routekey
topic:判断routekey的规则是模糊匹配模式
header:绑定队列与交换器的时候指定一个键值对,当交换器在分发消息的时候会先解开消息体里的
headers 数据,然后判断里面是否有所设置的键值对,如果发现匹配成功,才将消息分发到队列中;这
种交换器类型在性能上相对来说较差,在实际工作中很少会用到
33.简述rabbitmq的普通集群模式
元数据:
- 队列元数据:队列名称和它的属性
- 交换器元数据:交换器名称、类型和属性
- 绑定元数据:一张简单的表格展示了如何将消息路由到队列
- vhost元数据:为vhost内的队列、交换器和绑定提供命名空间和安全属性
为什么只同步元数据:
- 存储空间,每一个节点都保存全量数据,影响消息堆积能力
- 性能,消息的发布者需要将消息复制到每一个集群节点
客户端连接的是非队列数据所在节点:则该节点会进行路由转发,包括发送和消费
集群节点类型:
- 磁盘节点:将配置信息和元信息存储在磁盘上。
- 内存节点:将配置信息和元信息存储在内存中。性能优于磁盘节点。依赖磁盘节点进行持久化
RabbitMQ要求集群中至少有一个磁盘节点,当节点加入和离开集群时,必须通知磁盘节点(如果集群中唯一的磁盘节点崩溃了,则不能进行创建队列、创建交换器、创建绑定、添加用户、更改权限、添加和删除集群节点)。如果唯一磁盘的磁盘节点崩溃,集群是可以保持运行的,但不能更改任何东西。因此建议在集群中设置两个磁盘节点,只要一个可以,就能正常操作。
34.简述RabbitMQ架构设计
Broker:rabbitmq的服务节点
Queue:队列,是RabbitMQ的内部对象,用于存储消息。RabbitMQ中消息只能存储在队列中。生产 者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的 消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费。(注意:RabbitMQ不支持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由Key绑 定多个队列,由多个消费者来订阅这些队列的方式。
Exchange:交换器。生产者将消息发送到Exchange,由交换器将消息路由到一个或多个队列中。如果路由不到,或返回给生产者,或直接丢弃,或做其它处理。
RoutingKey:路由Key。生产者将消息发送给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消息流向哪里。
bindingKey:通过绑定将交换器和队列关联起来,在绑定的时候一般会指定一个绑定键,这样 RabbitMQ就可以指定如何正确的路由到队列了。 交换器和队列实际上是多对多关系。就像关系数据库中的两张表。他们通过BindingKey做关联(多对多关系表)。在投递消息时,可以通过Exchange和RoutingKey(对应BindingKey)就可以找到相对应的队 列。
信道:信道是建立在Connection 之上的虚拟连接。当应用程序与Rabbit Broker建立TCP连接的时候, 客户端紧接着可以创建一个AMQP 信道(Channel) ,每个信道都会被指派一个唯一的D。RabbitMQ 处 理的每条AMQP 指令都是通过信道完成的。信道就像电缆里的光纤束。一条电缆内含有许多光纤束,允 许所有的连接通过多条光线束进行传输和接收。
vhost:虚拟主机,每一个应用可以指定不同的vhost,此时对于应用来说、vhost就是broker
35.简述RabbitMQ事务消息机制
通过对信道设置实现
- channel.txSelect();通知服务器开启事务模式;服务端会返回Tx.Select-Ok
- channel.basicPublish;发送消息,可以是多条,可以是消费消息提交ack
- channel.txCommit()提交事务;
- channel.txRollback()回滚事务;
消费者使用事务:
- autoAck=false,手动提交ack,以事务提交或回滚为准;
- autoAck=true,不支持事务的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队 列已经把消息移除了
如果其中任意一个环节出现问题,就会抛出IoException异常,用户可以拦截异常进行事务回滚,或决定要不要重复消息。事务消息会降低rabbitmq的性能
36.简述RocketMQ 架构设计
路由信息是包括了:BokerServer,Topic和ConsumeQueueID等信息。
37.简述RocketMQ 持久化机制
- commitLog:日志数据文件,被所有的queue共享,大小为1G,写满之后重新生成,顺序写
- consumeQueue:逻辑queue,消息先到达commitLog、然后异步转发到consumeQueue,包含 queue 在 CommitLog 中的物理位置偏移量 Offset,消息实体内容的大小和 Message Tag 的 hash 值。大小约为 600W 个字节,写满之后重新生成,顺序写
- indexFile:通过 key 或者时间区间来查找 CommitLog 中的消息,文件名以创建的时间戳命名, 固定的单个 IndexFile 大小为 400M,可以保存 2000W 个索引
所有队列共用一个日志数据文件,避免了kafka的分区数过多、日志文件过多导致磁盘IO读写压力较大 造成性能瓶颈,rocketmq的queue只存储少量数据、更加轻量化,对于磁盘的访问是串行化避免磁盘 竞争,缺点在于:写入是顺序写,但读是随机的,先读ConsumeQueue,再读 CommitLog,会降低消 息读的效率
消息发送到broker后,会被写入commitLog,写之前加锁,保证顺序写入。然后转发到 consumeQueue
消息消费时先从consumeQueue读取消息在 CommitLog 中的起始物理偏移量 Offset,消息大小、和 消息 Tag 的 HashCode 值。在从CommitLog 读取消息内容
- 同步刷盘,消息持久化到磁盘才会给生产者返回ack,可以保证消息可靠、但是会影响性能
- 异步刷盘:消息写入pageCache就返回ack给生产者,刷盘采用异步线程,降低读写延迟提高性能 和吞吐
38.死信队列是什么?延时队列是什么?
-
死信队列也是⼀个消息队列,它是⽤来存放那些没有成功消费的消息的,通常可以⽤来作为消息重 试
-
延时队列就是⽤来存放需要在指定时间被处理的元素的队列,通常可以⽤来处理⼀些具有过期性操 作的业务,⽐如⼗分钟内未⽀付则取消订单
39.如何保证消息不被重复消费
幂等:一个数据或者一个请求,重复来多次,确保对应的数据是不会改变的,不能出错。
思路:
- 如果是写 redis,就没问题,反正每次都是 set ,天然幂等性
- 生产者发送消息的时候带上一个全局唯一的id,消费者拿到消息后,先根据这个id去 redis里查一 下,之前有没消费过,没有消费过就处理,并且写入这个 id 到 redis,如果消费过了,则不处理。
- 基于数据库的唯一键
40.消息队列有哪些作用
- 解耦:使⽤消息队列来作为两个系统之间的通讯⽅式,两个系统不需要相互依赖了
- 异步:系统A给消息队列发送完消息之后,就可以继续做其他事情了
- 流量削峰:如果使⽤消息队列的⽅式来调⽤某个系统,那么消息将在队列中排队,由消费者⾃⼰控 制消费速度
41.消息队列如何保证消息可靠传输
消息可靠传输代表了两层意思,既不能多也不能少。
-
为了保证消息不多,也就是消息不能重复,也就是⽣产者不能重复⽣产消息,或者消费者不能重复消费消息
-
⾸先要确保消息不多发,这个不常出现,也⽐较难控制,因为如果出现了多发,很⼤的原因是⽣产 者⾃⼰的原因,如果要避免出现问题,就需要在消费端做控制
-
要避免不重复消费,最保险的机制就是消费者实现幂等性,保证就算重复消费,也不会有问题,通过幂等性,也能解决⽣产者重复发送消息的问题
-
消息不能少,意思就是消息不能丢失,⽣产者发送的消息,消费者⼀定要能消费到,对于这个问题,就要考虑两个⽅⾯
-
⽣产者发送消息时,要确认broker确实收到并持久化了这条消息,⽐如RabbitMQ的confirm机制, Kafka的ack机制都可以保证⽣产者能正确的将消息发送给broker
-
broker要等待消费者真正确认消费到了消息时才删除掉消息,这⾥通常就是消费端ack机制,消费 者接收到⼀条消息后,如果确认没问题了,就可以给broker发送⼀个ack,broker接收到ack后才会 删除消息
42.如何设计一个MQ
1、实现一个单机的队列数据结构。 高效、可扩展。
2、将单机队列扩展成为分布式队列。- 分布式集群管理
3、基于Topic定制消息路由策略。- 发送者路由策略,消费者与队列对应关系,消费者路由策略
4、实现高效的网络通信。- Netty Http
5、规划日志文件,实现文件高效读写。- 零拷贝,顺序写。 服务重启后,快速还原运行现场。
6、定制高级功能,死信队列、延迟队列、事务消息等等。
43.消息队列的优缺点,使用场景
优点:
1、解耦,降低系统之间的依赖
2、异步处理,不需要同步等待
3、削峰填谷,将流量从高峰期引到低谷期进行处理
缺点:
1、增加了系统的复杂度,幂等、重复消费、消息丢失等问题的带入
2、系统可用性降低,mq的故障会影响系统可用
3、一致性,消费端可能失败
场景:日志采集、发布订阅等