八股文---消息队列

1.如何保证消息不丢失

生产者、中间件、消费者 所以要保证消息就是保证三个环节都不能丢失数据。


解决方案

消息生产阶段:生产者会不会丢消息,取决于生产者对于异常情况的处理是否合理。从消息被生产出来,然后提交给 MQ 的过程中,只要能正常收到 ( MQ 中间件) 的 ack 确认响应,就表示发送成功,所以只要处理好返回值和异常,如果返回异常则进行消息重发,那么这个阶段是不会出现消息丢失的。

消息存储阶段:Kafka 在使用时是部署一个集群,生产者在发布消息时,队列中间件通常会写「多个节点」,也就是有多个副本,这样一来,即便其中一个节点挂了,也能保证集群的数据不丢失。开启持久化功能可以确保MQ消息不丢失(交换器,队列,消息)

消息消费阶段消费者接收消息+消息处理之后,才回复 ack 的话,那么消息阶段的消息不会丢失。不能收到消息就回 ack,否则可能消息处理中途挂掉了,消息就丢失了

2.消息重复消费

重复消费可能是因为网络波动,消费者挂了,但是消费者有个自动确认的机制,消费者就有可能重新消费这个消息

解决方案:

每条消息设置一个唯一的标识id(支付id,订单id,文章id.......)

幂等方案:【 分布式锁、数据库锁(悲观锁、乐观锁) 】

3.如何保证幂等写?

幂等性是指 同一操作的多次执行对系统状态的影响与一次执行结果一致。例如,支付接口若因网络重试被多次调用,最终应确保仅扣款一次。实现幂等写的核心方案:

解决方案

唯一标识(幂等键):客户端为每个请求生成全局唯一ID(如 UUID、业务主键),服务端校验该ID是否已处理,适用场景接口调用、消息消费等。

数据库事务 + 乐观锁:通过版本号或状态字段控制并发更新,确保多次更新等同于单次操作,适用场景数据库记录更新(如余额扣减、订单状态变更)。

数据库唯一约束:利用数据库唯一索引防止重复数据写入,适用场景数据插入场景(如订单创建)。

分布式锁:通过锁机制保证同一时刻仅有一个请求执行关键操作,适用场景高并发下的资源抢夺(如秒杀)。

消息去重:消息队列生产者为每条消息生成唯一的消息 ID,消费者在处理消息前,先检查该消息 ID 是否已经处理过,如果已经处理过则丢弃该消息

4.延迟队列(死信交换机)

延迟队列

延迟队列:进入队列的消息会被延迟消费的队列

比如场景:超时订单,限时优惠,定时发布

死信交换机

延迟队列=死信交换机+TTL(生存时间)

当一个队列的消息满足下列情况之一,就可以成为死信(可能不会被消费)

  1. 消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false
  2. 消息是一个过期消息,超时无人消费
  3. 要投递的队列消息堆积满了,最早的消息可能成为死信

如果该队列配置了dead-letter-exchange属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为死信交换机(Dead Letter Exchange,简称DLX)

TTL

TTL,也就是Time-To_Live.如果一个队列中的消息TTL结束还没有被消费,就会变成死信,TTL超时分为两种情况:

  • 消息所在的队列设置了存活时间
  • 消息本身设置了存活时间

5.消息队列的可靠性、顺序性怎么保证?

可靠性

解决方案

消息持久化:确保消息队列能够持久化消息是非常关键的。在系统崩溃、重启或者网络故障等情况下,未处理的消息不应丢失。例如,像 RabbitMQ 可以通过配置将消息持久化到磁盘,通过将队列和消息都设置为持久化的方式(设置durable = true),这样在服务器重启后,消息依然可以被重新读取和处理。

消息确认机制:消费者在成功处理消息后,应该向消息队列发送确认(acknowledgment)。消息队列只有收到确认后,才会将消息从队列中移除。如果没有收到确认,消息队列可能会在一定时间后重新发送消息给其他消费者或者再次发送给同一个消费者。以 Kafka 为例,消费者通过commitSync或者commitAsync方法来提交偏移量(offset),从而确认消息的消费。

消息重试策略:当消费者处理消息失败时,需要有合理的重试策略。可以设置重试次数和重试间隔时间。例如,在第一次处理失败后,等待一段时间(如 5 秒)后进行第二次重试,如果重试多次(如 3 次)后仍然失败,可以将消息发送到死信队列,以便后续人工排查或者采取其他特殊处理。

顺序性

解决方案

消息队列对顺序性的支持部分消息队列本身提供了顺序性保证的功能。比如 Kafka 可以通过将消息划分到同一个分区(Partition)来保证消息在分区内是有序的,消费者按照分区顺序读取消息就可以保证消息顺序。但这也可能会限制消息的并行处理程度,需要在顺序性和吞吐量之间进行权衡。

消费者顺序处理策略:消费者在处理顺序消息时,应该避免并发处理可能导致顺序打乱的情况。例如,可以通过单线程或者使用线程池并对顺序消息进行串行化处理等方式,确保消息按照正确的顺序被消费。

6.如何处理消息队列的消息积压问题?

消息积压是因为生产者的生产速度,大于消费者的消费速度。遇到消息积压问题时,我们需要先排查,是不是有bug产生了。如果不是bug,我们可以优化一下消费的逻辑,比如之前是一条一条消息消费处理的话,我们可以确认是不是可以优为批量处理消息。如果还是慢,我们可以考虑水平扩容,增加Topic的队列数,和消费组机器的数量,提升整体消费能力

解决方案

  1. 提高消费者消费能力,如使用多线程。
  2. 增加消费者数量,采用工作队列模式,让多个消费者并行消费同一队列。
  3. 扩大队列容量,使用RabbitMQ的惰性队列,支持数百万条消息存储,直接存盘而非内存。
### Linux 消息队列的核心概念 Linux 中的消息队列是一种进程间通信(IPC)机制,允许不同进程之间通过发送和接收消息进行数据交换。与管道、共享内存等其他 IPC 机制相比,消息队列具有更高的灵活性和可靠性。 消息队列的基本组成包括: - **消息**:由类型和数据两部分组成,其中类型是一个正整数,用于标识消息的类别。 - **队列**:一个先进先出(FIFO)的数据结构,用于存储消息,支持多个进程按需读取或写入[^4]。 ### 工作原理 Linux 的消息队列实现主要基于 System V 和 POSIX 两种标准: - **System V 消息队列**:提供 `msgget`、`msgsnd`、`msgrcv` 和 `msgctl` 等系统调用来创建、操作和管理消息队列- **POSIX 消息队列**:使用 `mq_open`、`mq_send`、`mq_receive` 和 `mq_close` 等函数来处理消息队列,并支持异步通知机制。 当一个进程向队列中发送消息时,内核会将该消息添加到队列的末尾;而接收方则可以根据消息类型选择性地从队列中取出消息。这种机制确保了进程之间的解耦和高效通信。 ### 核心技术知识点 1. **消息队列的创建与销毁** - 使用 `msgget` 创建一个新的消息队列或获取已存在的队列标识符。 - 使用 `msgctl` 命令配合 `IPC_RMID` 参数删除消息队列。 2. **消息的发送与接收** - `msgsnd` 用于将消息发送到指定的消息队列- `msgrcv` 允许接收者根据特定的消息类型从队列中提取消息。 3. **权限控制** - 每个消息队列都有自己的访问权限设置,类似于文件系统的权限模型,可以通过 `msg_perm` 字段进行配置。 4. **限制与性能考量** - 消息大小和队列长度受限于系统参数,如 `/proc/sys/kernel/msgmax` 和 `/proc/sys/kernel/msgmnb`。 - 过大的消息或过长的队列可能导致内存消耗增加,影响系统稳定性。 5. **同步与异步处理** - 支持阻塞与非阻塞模式下的消息收发。 - POSIX 消息队列还提供了信号通知功能,便于构建响应式应用。 ### 应用场景 - **任务调度系统**:在分布式环境中协调多个工作节点的任务分配。 - **日志收集服务**:作为缓冲区暂存来自不同来源的日志信息,再由中心服务器统一处理。 - **实时数据流处理**:适用于需要低延迟传输的小规模数据包传递。 - **微服务架构中的事件驱动设计**:利用消息队列实现服务间的松耦合交互。 ### 示例代码 以下是一个简单的示例,展示如何使用 System V 消息队列发送和接收消息: ```c #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <string.h> struct my_msgbuf { long mtype; char mtext[200]; }; int main() { struct my_msgbuf buf; int msqid; // 获取消息队列 ID msqid = msgget(1234, 0666 | IPC_CREAT); if (msqid == -1) { perror("msgget"); return 1; } printf("Enter lines of text, ^D to quit:\n"); while (fgets(buf.mtext, sizeof(buf.mtext), stdin) != NULL) { buf.mtype = 1; // 设置消息类型为1 msgsnd(msqid, &buf, strlen(buf.mtext)+1, 0); // 发送消息 } return 0; } ``` 对应的接收端程序如下所示: ```c #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> struct my_msgbuf { long mtype; char mtext[200]; }; int main() { struct my_msgbuf buf; int msqid; // 获取消息队列 ID msqid = msgget(1234, 0666 | IPC_CREAT); if (msqid == -1) { perror("msgget"); return 1; } for (;;) { msgrcv(msqid, &buf, 200, 1, 0); // 接收类型为1的消息 printf("%s", buf.mtext); } return 0; } ``` 这两个程序分别演示了如何初始化消息队列、发送和接收消息的过程。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值