9.消息队列
9.1 消息队列的使用场景。
消息队列在分布式场景中广泛应用,主要用于解决异步处理、解耦服务、流量削峰、数据一致性等多个场景。
-
异步处理:
- 在电商应用中,用户注册后,可以将发送验证邮件或短信的任务放入消息队列,由后台服务异步处理,这样主线业务逻辑(如保存用户信息)不会因为等待这些耗时操作而阻塞。
- 订单创建完成后,订单状态变更、库存更新、积分发放等操作可以异步执行,提高系统的响应速度。
-
服务解耦:
- 通过消息队列作为中间件,上游服务只需将任务消息发布到队列,下游服务订阅并消费消息来完成各自的工作,避免了直接调用带来的紧耦合问题。
- 当服务架构发生变化时,只需重新配置消息队列的消费者即可,不影响生产者和其它消费者。
-
流量削峰:
- 当短时间内大量请求涌入时,可以通过将请求转化为消息投递到队列,让后端服务按照自身处理能力平稳地消费消息,防止服务因瞬时高负载而崩溃。
-
数据一致性保障:
- 在分布式事务处理中,通过消息队列实现最终一致性。例如,在分布式事务中一个操作成功后,需要触发另一个服务的操作,如果直接调用可能导致事务复杂性增加,使用消息队列可以简化这种流程,并通过重试机制保证数据最终达到一致状态。
9.2 消息的重发,补充策略。
在消息队列中,为保证数据的可靠投递,通常会设计消息重发和补充策略。消息重发和补充策略:
1.消息确认机制:
- 消费者收到消息后需要向消息队列发送一个确认信号(ACK),只有收到ACK后,消息队列才会将消息从队列中移除。
- 如果消费者在规定的时间内没有发送ACK,或者发送了否定确认(NACK),则消息队列会认为该消息未被正确处理,从而进行重新投递。
2.定期重试:对于无法立即成功消费的消息,消息队列可以设置一定次数或时间间隔的重试策略,例如首次投递失败后等待一段时间再投递,直到达到最大重试次数为止
3.死信队列:当消息达到最大重试次数仍然无法正常消费时,可以将其转移到死信队列中,由专门的服务来分析和处理这些死信,避免影响主消息队列的性能和可用性
4.幂等消费:保证消息处理逻辑的幂等性,即使同一条消息被重复消费多次,也不会导致业务状态不一致。
5.事务消息:当消息生产者提交消息时,会开启一个本地事务,并在事务提交成功后通知消息队列;如果事务回滚,则不会通知消息队列投递消息
9.3 消息队列如何保证消息的有序性。
消息队列保证消息的有序性有下面三种方式::
- 在RocketMQ中,使用顺序消息来保证全局有序。每个Topic下的消息严格按发送顺序进行消费。RocketMQ通过特殊的顺序消息队列和内部机制确保同一Topic下全局消息的顺序。
-
对于大部分场景,往往只需要保证特定键(Key)的消息有序,而不是全局所有消息有序。在Kafka中,通过将具有相同Key的消息路由到同一个Partition上,由于Partition在同一时间只会被一个消费者线程处理,因此同一个Partition中的消息可以保证消费顺序。对于不同的Partition,消息顺序不保证。
- 在 RabbitMQ 中,只要确保所有生产者将消息发送到同一个队列,并且该队列只有一个消费者(或一个消费者组中的一个消费者实例),那么消息就会按照它们到达队列的顺序进行消费。这是因为RabbitMQ在队列中是先进先出(FIFO)的原则处理消息。
9.4 用过哪些MQ,和其他mq比较有什么优缺点,MQ的连接是线程安全的吗。
常见消息队列的优缺点:
-
RabbitMQ:
- 优点:遵循AMQP协议,具有良好的跨语言支持;支持多种交换机类型(如直连、主题、头和扇出),灵活的消息路由机制;提供事务和持久化功能。
- 缺点:在处理大量小消息时性能可能不如其他专门为大数据量优化的消息队列;对于复杂的路由逻辑管理相对复杂。
-
Apache Kafka:
- 优点:设计为高吞吐量的分布式流平台,尤其适合处理大数据场景;具有优秀的水平扩展能力,能够实现大规模集群部署;持久化能力强,且具备顺序读写特性,保证了消息的有序性。
- 缺点:原生API对开发者来说学习曲线较陡峭;默认不支持同步请求-响应模式,需要额外工作来模拟这种模式。
-
RocketMQ:
- 优点:阿里巴巴开源的产品,适用于大规模分布式系统;提供了顺序消息、事务消息等功能;支持定时/延时消息,高性能且稳定。
- 缺点:社区活跃度相较于Kafka略低,跨语言客户端的支持不及Kafka广泛。
-
ActiveMQ:
- 优点:成熟的开源项目,支持多种协议(包括AMQP, MQTT, STOMP等);集成JMS接口,便于Java应用集成。
- 缺点:性能相对于新出现的一些消息队列有所不足,不适合非常高的并发场景。
大多数现代MQ产品提供的客户端连接对象通常是线程安全的。例如,RabbitMQ客户端在创建连接后,多个线程可以共享该连接进行发送和接收操作。但是,Channel
(通道)通常不是线程安全的,每个线程应该拥有各自的Channel实例来进行操作。
9.5 MQ系统的数据如何保证不丢失。
MQ系统要确保数据不丢失,通常需要在生产者、消息队列服务端和消费者三个层面实施一系列策略。以下是一些保证消息不丢失的关键措施:
-
生产者端:
- 确认机制:生产者发送消息后,可以通过ACK机制要求MQ服务返回确认消息,只有收到确认消息后才认为消息投递成功。例如RabbitMQ的publisher confirms,RocketMQ的事务消息等。
- 持久化:将消息设置为持久化的模式,这样即使在消息写入到MQ服务器之前发生故障,消息也能在服务恢复时重新写入。
-
MQ服务端:
- 消息持久化:将接收到的消息立即或在短暂缓存后立即刷盘,以防止因服务器故障导致内存中的消息丢失。如Kafka中默认的消息存储是持久化的,并且可以配置多个副本以增加容错性。
- 集群与冗余:部署集群并通过主从复制、分区副本等方式提供高可用性和数据冗余,如Kafka的ISR(In-Sync Replicas)和Raft一致性算法,RocketMQ的主从同步机制等。
- 事务支持:对于需要事务处理的消息,支持分布式事务或者两阶段提交来保证消息的最终一致性。
-
消费者端:
- 消费确认(Acknowledge):消费者在处理完消息后向MQ服务器发送确认信息,只有当MQ服务器收到确认后才会从队列中删除该消息。未确认的消息会在超时后重新投递给其他消费者或者保留在队列中等待下次重试。
- 幂等处理:设计消费者逻辑时考虑幂等性,即使因为网络或其他原因导致消息被重复投递,也能确保多次处理同一消息的结果一致。
-
运维监控与日志记录:
- 监控告警:对消息堆积、节点故障等情况进行实时监控并及时报警,以便快速发现和修复问题。
- 日志审计:通过详细的日志记录和分析,确保能够追踪消息的生命周期,发现问题后能回溯定位。
9.6 rabbitmq如何实现集群高可用。
rabbitMQ实现集群高可用步骤:
-
创建集群节点:在多台服务器上分别安装并启动RabbitMQ,每个实例都将作为一个集群中的节点。
-
共享存储(磁盘节点):要实现高可用性,集群中的节点通常需要访问相同的持久化存储区域。可以通过共享文件系统(如NFS)或分布式存储解决方案(如RabbitMQ的镜像队列)来实现消息数据在多个节点间的复制与同步。
-
设置 Erlang Cookie:所有参与集群的节点必须拥有相同的Erlang Cookie。这个cookie是Erlang用来识别和连接不同节点的一个安全标识符,在
/var/lib/rabbitmq/.erlang.cookie
文件中配置。 -
建立集群:使用
rabbitmqctl
工具将一个节点加入到现有集群中。镜像队列 (Mirrored Queues) 高可用策略: - 镜像队列高可用策略:为了保证队列级别的高可用,可以启用镜像队列。在RabbitMQ中,可以定义一个策略,使得指定队列在多个节点间进行镜像复制。当某个节点故障时,其他节点上的镜像队列可以继续提供服务。
- 负载均衡与客户端重连:当集群建立完成后,生产者和消费者应当能够透明地连接到任意节点,并且在节点失效时能自动重新连接到集群中的其他节点。
- 监控与运维:设置合适的监控和报警机制,以便在节点出现故障时及时发现并采取措施,比如通过心跳检测、网络监控等手段。
9.7 kafka吞吐量高的原因。
- 顺序读写磁盘:kafka消息存储机制是将消息追加到日志文件末尾,充分利用磁盘的顺序写功能,提升下入速度。
- 零拷贝技术:利用操作系统提供的零拷贝技术减少数据在内核空间和用户空间之间的复制操作,直接在内核中完成网络传输和磁盘读写的操作,减少了CPU消耗和内存带宽占用。
- 批量处理:生产者和消费者都以批处理形式发送和拉取消息,提升单位时间数据传输量。
- 分区与并行处理:Kafka主题下的每个分区都是一个独立的日志序列,多个消费者实例可以并行消费不同分区的消息,从而实现水平扩展,并行处理能力随着分区数目的增加而提升。
- 高效数据压缩:支持对消息进行压缩(如GZIP、Snappy等算法),减小数据在网络上传输时的大小,提高了网络带宽的利用率
- 页缓存优化:利用操作系统的页缓存来存储和管理数据,从而加速读取速度。
- 分布式设计:Kafka集群是由多个Broker节点组成的,它们之间通过副本机制提供容错性和高可用性,同时也能分散负载,共同服务于大量的生产者和消费者
9.8 kafka 和其他消息队列的区别,kafka 主从同步怎么实现。
kafka适用处理实时大数据场景,采用分布式持久化日志存储,每个主题下的消息按照分区有序存储,并且可以进行高效的批量读写操作,通过水平扩展Broker节点数量来增加系统的吞吐量和容错性。
主从同步:
- 在Kafka中,主从同步是通过多副本机制实现的。每个主题分区都有一个Leader副本和多个Follower副本。生产者将消息发送到Leader副本,而Follower副本定期从Leader上拉取新的数据进行同步。
- 当Leader副本出现故障时,Kafka会自动选举一个健康的Follower成为新的Leader,从而保证服务的连续性。这个过程由Kafka内部的控制器组件管理和协调。
9.9 利用mq怎么实现最终一致性。
我在项目中使用RocketMQ实现了可靠消息分布式事务最终一致性,流程如下:
- 事务发起方向MQ发起Half消息;
- 发送成功后,MQ接收到Half消息后,响应给事务发起者;
- 事务发起者执行本地事务
- 执行完事务后向MQ发送提交互或回滚状态事务消息;
- 如果事务参与方未收到MQ投递的消息,或执行事务失败,MQ并未删除保存的消息时,MQ会回查事务发起方事务状态,确认时提交事务还是回滚事务。
- 事务发起方查询本地事务,确定事务是否执行成功。
- 事务发起方根据查询的事务状态,向MQ发送提交或回滚事务消息。
- 如果第七步中事务发起方向MQ发送的是提交事务消息,那么MQ向事务参与方投递消息。
- 如果第七步中事务发起方向MQ发送的是回滚事务消息,那么MQ不会像事务参与方投递消息,并删除内部存储的消息数据。
- 如果MQ向事务参与方投递的是执行本地事务的消息,那么事务参与方执行本地事务。
- 如果MQ向事务参与方投递的是回查本地事务状态的消息,那么事务参与方回查本地事务状态。
9.10 MQ有可能发生重复消费,如何避免,如何做到幂等。
在本人项目中,消息发送前,会将对应的事务操作作为值,事务标识作为key存储在缓存中,通过判断缓存中是否存在事务标识来实现数据的幂等性处理。
9.11 MQ的消息延迟了怎么处理,消息可以设置过期时间么,过期了你们一般怎么处理。
消息队列(MQ)中的消息延迟处理通常需要根据具体场景采取不同的策略。如果消息确实出现延迟,可以尝试以下几种方法:
-
排查原因:首先确定消息延迟的原因,可能是生产者端的问题(如网络问题、发送速率过快导致积压等),也可能是消费者端的问题(如消费速度慢、消费线程不足、异常未及时处理等)。通过监控和日志分析来定位问题。
-
优化系统:对于生产者端,可以通过限速、批量发送、增加并发等方式减少消息堆积。对于消费者端,可以提高消费能力,例如增加消费者实例数量、优化消费逻提升性能,或配置合理的负载均衡策略。
-
设置延时队列或死信队列:很多MQ支持延时消息,允许为消息设置一个未来的时间点进行投递。若消息在规定时间内未被成功处理,可将其路由到死信队列,并针对死信队列设计特定的重试机制或人工介入处理
-
设置消息过期时间:大多MQ都支持为消息设置TTL(Time To Live),即消息的有效存活时间。当消息达到其过期时间后,会自动从队列中移除或者转移到其他特殊队列(如死信队列)。
-
过期消息处理:当消息过期后,可以根据业务需求进行不同处理:1)如果是死信队列,则可以设计额外的消费者专门处理这些过期失效的消息,例如记录到日志、通知管理员、或者重新投递等。2)也可以定期清理过期但未被消费的消息,防止消息队列不断增长导致存储压力过大。3)对于重要的业务流程,可能还需要实现补偿逻辑或重试策略,确保业务的一致性不受影响。