好处
作为消息系统来说,第一肯定是解耦,解除了生产者和消费者的直接联系,通过 MQ 来作为媒介。MQ 来保证消息的可靠性和持久性等,并且扩展性和可恢复性高。
选型
- RabbitMQ:主要是数据一致性、稳定性和可靠性要求高,对性能和吞吐量的考量是其次。属于重量级消息中间件,实现了 Broker 架构,消息发给客户端之前会在队列中排队,对路由、负载均衡、消息持久化都有较好的支持。
- Redis:这个就不说了,上不得台面,主要还是用做缓存。
- Kafka:分布式系统,相对轻量级且性能很高,但是消息不能保证全局有序且不支持事务,可能丢消息/重复消息,复杂性也比较高。
Kafka 消息处理流程
标准 Kafka 消息流转流程
- 生产者发送一条消息(record)
-
- 指定目标 topic(如 orders)。
- 不会直接"路由到 broker",而是由生产者根据 topic 的元数据(通过 ZooKeeper 或 KRaft 获取)选择目标 broker(即该 topic 的 leader partition 所在的 broker)。
- 生产者将消息发送到该 leader partition
-
- 每个 topic 可以有多个 partition。
- 生产者决定发到哪个 partition(通过 key hash 或指定 partition 或轮询)。
- 不是"随机负载均衡",而是生产者决定 partition(除非用了 sticky partitioner 或 key 为 null 时轮询)。
- broker 接收消息
-
- 该 broker 是目标 partition 的 leader。
- 消息写入 leader partition 的 log 文件。
- follower 副本异步拉取同步(ISR 机制)。
- 消费者消费消息
-
- 消费者组(consumer group)中的消费者**主动拉取(pull)**消息。
- 每个 partition 只能被同组内的一个消费者实例消费(即"独占")。
- 不是"随机负载均衡",而是 Kafka 自动 rebalance,将 partition 分配给消费者。
核心组件以及概念
offset
对分区中的每个记录分配一个连续 id 记为 offset,以消费者为单位保留的唯一元数据是消费者在日志中的偏移量或位置。实际上,消费者可以选择从任何位置去顺序消费,比如从 old 数据开始或者从 now 数据开始。
message
Message 里面包含了 offset、size、data 三种属性。offset 是消息逻辑上的定位,不是实际存储位置。
partition 数据
Partition 数据查找的时候是顺序查找的,因此数据量大的时候就会很慢,通过分段 + 二分 + 索引解决。
Producer
- 负载均衡的实现是:有指定 key 就 分区 = 消息 key 进行哈希后 % 分区数,没有就轮询。
- 批量发送减少 IO,批量发送是通过消息数和时间限制来进行触发。
- 消息丢失是通过 acks 这个标识符值的配置决定的:
-
- 0:关闭,直接接收;
- 1:需要 Leader 确认,其他副本直接同步;
- all:全部副本接收后才能返回 ack。
Consumer
重平衡
简单来说就是消费过程中分配 partition 给消费者,核心步骤分为以下:
- 选出 coordinator:主要看 offset 保存在哪个 partition,这个 partition 所在的 leader 就是 coordinator。
- 交互:
-
- 消费者启动或者控制者宕机,消费者请求任意 broker,那么 broker 会按照上面的步骤告知这个消费者对应的控制者是谁,也就是给出地址。
- 消费者发心跳请求给控制者,如果返回错误响应,那么就说明消费者的控制者已经是上个版本的信息了,要重新加进去进行 rebalance,成功了就从上次分配的 partition 继续执行。
- 重平衡:
-
- 消费者给控制者发加入消费者组的请求。
- 其他消费者这时发心跳请求过来的话,控制者就告知他们要进行重平衡。
- 全部消费者都加入消费者组了,这时控制者会选出一个 Leader,并且把 follower 的消息都给他,让他根据信息去分配 partition。
- 消费者向控制者发同步请求,也就是问选出来的 leader 情况。
- 控制者响应并告知选主情况。
增加分区、增加消费者,消费者宕机关闭等、控制者宕机都需要重平衡。
传输保证
这个实际上是为了保证消息丢失和消息重发等问题,消息送到消费者就立刻可以踢出 partition 了吗?如果消费者没成功消费呢,因此这里制定了几种策略来解决这些问题:
- 最多一次:消息可能会丢但肯定不会重复。
- 最少一次:消息不会丢但是可能重复。
- 一次:消息不丢不重复,只消费一次。
消费方式
采取 pull 模式从 broker 中拉数据,保证以消费者的消费能力为主。
存储策略
Kafka 是保留全部消息的,有两种方式删除:一种是基于时间,一种是基于大小。
Replication
副本的基本单元是分区,副本数不会大于 broker 数,因为副本一般和 leader 不在一个 broker 中,副本和副本也是在不同 broker 中的。消息的生产消费都是在 leader 进行,副本只是为了备份。