消息队列

img

优点:解耦,削峰,异步

缺点:开发复杂,需要保证数据一致性

消息重复消费怎么解决?

最经典的场景:Consumer 收到消息后,成功把数据处理完并写进了数据库,但就在准备向 Kafka 提交 Offset 的前一秒,系统突然宕机了(或者耗时过长触发了 Rebalance)。 等 Consumer 节点重启(或任务交接给其他节点)后,Kafka 检查记录发现:“这条消息的 Offset 没提交,肯定没被消费。” 于是把这批消息又发了一遍,导致重复处理。

如何保证幂等性( 同一操作的多次执行对系统状态的影响与一次执行结果一致):

唯一标识

MySQL 唯一索引防重

Redis 记录状态 / SETNX 去重

消息丢失怎么解决的?

确保生产者正常收到ack

kafka集群

确保消费者在接收+处理消息后回复ack给中间件

kafka结构

image-20260928113101515

image-20260928112619844

客户端层

Producer(生产者):负责把消息发往 Kafka。

Consumer(消费者):负责从 Kafka 拉取消息。

Consumer Group(消费者组):Kafka 的一大特色。多个 Consumer 可以组成一个组来共同消费一个 Topic。

  • 核心规则:同一个 Partition 只能被同一个组里的某一个 Consumer 消费。

Kafka都选择拉模式,引入了“长轮询 (Long Polling)”:消费者请求后,若没消息,Broker 不立即返回,而是挂起等待(如等待 5s 或 30s)。一旦新消息到达或超时,再立即返回。这既实现了近实时的效果(有消息秒回),又大幅减少了无效的空轮询(没消息时请求不空跑)。

服务端 Broker 层

Broker:一台 Kafka 服务器就是一个 Broker。多个 Broker 组成一个高可用的集群。

Controller(控制器):集群的大总管(通常是某一台 Broker 兼任),负责管理整个集群的元数据、Partition 的状态同步以及 Leader 选举。

逻辑与物理结构

Topic(主题,逻辑概念):消息的分类标签。比如 order-topic 专门存订单消息,log-topic 专门存日志。

Partition(分区,物理概念):为了提升吞吐量,一个 Topic 会被切分成多个 Partition,分散在不同的 Broker 上。分区中用offset标记分区中消息的唯一位置

副本机制

Leader 副本:对外接客。Producer 和 Consumer 的读写请求,全部都由 Leader 负责处理。

Follower 副本:默默打工。它的唯一任务就是不断从 Leader 那里拉取数据,保持同步。一旦 Leader 挂了,Follower 就会被选举为新的 Leader 顶上。

kafka特点

基本特点

高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒

可扩展性:kafka集群支持热扩展

持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失

容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)

高并发:支持数千个客户端同时读写

Kafka 为什么这么快

顺序写入优化:Kafka将消息顺序写入磁盘,减少了磁盘的寻道时间。

零拷贝技术:传统 I/O 模式下,数据发送到网络需要经历“磁盘 -> 内核态 -> 用户态 -> 内核态网卡” 4 次拷贝和频繁的上下文切换。Kafka 底层调用了 sendfile 系统调用,数据直接从内核态的 Page Cache 拷贝到网卡缓冲区,完全绕过了 Kafka 应用程序(用户态),极大降低了 CPU 负载。

批量处理技术:Producer 不会来一条消息就发一条,而是将消息攒成一个 Batch 批量发送,大幅减少了网络请求的网络 I/O 次数。

压缩技术:Kafka支持对消息进行压缩,这不仅减少了网络传输的数据量,还提高了整体的吞吐量。

kafka常见问题解决方案

Kafka 如何保证顺序读取消息?

Kafka 可以保证在同一个分区内消息是有序的,生产者写入到同一分区的消息会按照写入顺序追加到分区日志文件中,消费者从分区中读取消息时也会按照这个顺序。

生产者端:将同一业务的消息发往同一个 Partition

  • 指定相同的 Message Key
    • Kafka 的默认分区策略会根据消息的 key 计算哈希值,并将相同 key 的消息路由到同一个 Partition(例如:将同一个用户的操作或同一个订单的状态流转,都以 order_id 作为 key)。

服务端(Broker):单分区内物理追加与严格递增

  • 在同一个 Partition 内部,所有消息都是以追加写(Append-only)的方式存入日志。
  • 每条消息都会被分配一个严格单调递增的 Offset(偏移量),从底层文件存储上固化了消息的物理先后顺序。

消费者端:单线程读取

  • 默认机制:单 Partition 对应单 Consumer
    • 在同一个消费者组内,一个 Partition 只能同时被一个 Consumer 实例消费。Consumer 只要按顺序单线程调用 poll() 拉取消息并串行处理,就能严格按 Offset 顺序消费。

如何保证全局有序

只使用一个分区:将所有消息都写入到同一个分区,消费者也只从这个分区消费消息。

kafka 消息积压怎么办?

增加消费者实例可以提高消息的消费速度,从而缓解积压问题。

增加 Kafka 主题的分区数量可以提高消息的并行处理能力。

如果分区数是N,那么最好线程数也保持为N,这样通常能够达到最大的吞吐量。