Kafka 的保证——分区内顺序与整数表示的位置
一句话总结
Kafka 的主题(topic)是按分区切分的、只追加的日志,相同键的事件会按顺序堆积在同一个分区里, 而消费者组的“读到哪里了”,是每个分区一个整数(偏移量)。消费之后并不会被删除——只有保留设置才会删除。 这四句话,是解读后面“两次”和“消失”的钥匙。
为什么需要它
用过队列的人,会把 Kafka 误认为队列。队列中的消息一旦取出就消失了,broker 会为 每条消息记住“谁收到了什么”。设计文档 的“Consumer Position”一节,写下了这种方式的代价——如果一交给消费者就标记为已消费, 消费者在处理过程中崩溃,消息就丢了;如果等待确认(ack), 处理完了却没能发出确认,就会被消费两次;而且 broker 还得为每条消息保存 多个状态。
Kafka 选择了另一条路。把主题切分成全序(totally ordered)的分区, 让每个分区在一个消费者组内只由一个消费者读取,这样消费者的 位置就只是“下一个要读取的偏移量”这一个整数。定期为这个整数做检查点, 就是确认的全部,成本非常低,而副产品是倒回重读——如果代码里 有 bug,可以回到旧的偏移量重新读取。文档写道,这违背了队列的 契约,但对很多消费者来说是必不可少的功能。
工作原理
事件与主题。 按介绍文档所述,事件带有 键、值、时间戳(以及可选的头部),主题就是这些事件堆积的地方。 主题可以同时接纳多个生产者和多个消费者,事件在被消费之后也 不会被删除。要保留多久,由每个主题的保留设置决定(第 4 个模块)。
分区与键。 主题被切分成多个分区(“桶”),分布在多个 broker 上。
新事件会追加到其中某个分区的末尾,而相同键(例如订单号)的事件
会进入同一个分区。生产者配置文档
中的 partitioner.class 条目写明了默认行为——有键时按键的哈希选择分区,没有键时
采用 sticky 方式:在攒满 batch.size 之前,一直追加到同一个
分区。所以不带键发送的少量事件会挤在一个分区里,给了键,则每个订单排成一条线。
Kafka 承诺的顺序只在分区内部。同一个订单的 created → paid → shipped 因为在同一个分区,会按这个顺序被读到,但不同订单之间,哪个分区
先被读取,是没有规定的。这就是需要保证顺序的单位必须成为键的原因。
orders (3 파티션) 컨슈머 그룹 order-svc 의 위치
P0: order-2 created, order-3 created, order-2 paid → 오프셋 3
P1: order-1 created, order-1 paid, order-1 shipped → 오프셋 3
P2: order-4 created, order-5 created → 오프셋 2
消费者组与偏移量。 组是用 group.id 绑在一起的消费者,组的位置就是
每个分区上提交给 broker 的偏移量。不同的组有不同的位置——
即使 order-svc 已经读到了末尾,analytics 仍会从头开始重新读取。日志末尾
偏移量与已提交偏移量之间的差,就是延迟(lag),也是运维中最先要看的
数字。kafka-consumer-groups.sh --describe 会按分区显示 CURRENT-OFFSET、
LOG-END-OFFSET 和 LAG。
分区数只能增加。 运维文档
的“Modifying topics”一节列出了增加分区时的三个副作用。数据按
hash(key) % 파티션 수(占位符为分区数)划分,所以数量一变,相同的键就可能进入另一个分区,
已有的数据也不会被重新分配(键的顺序保证可能被破坏);
auto.offset.reset=latest 的现有消费者,可能会漏掉在它发现新分区之前
进来的消息;元数据的传播存在延迟。此外,不支持缩减。
在现场相遇的样子
“订单状态看起来是颠倒的”这类反馈,常见的原因是键。要么订单服务没有带键 发送,要么用事件类型而不是订单号作了键,要么在增加分区数之后,同一个订单的 旧事件和新事件被分散在不同的分区里。这三种情况下,broker 是正常的,日志也是 正常的——是对它没有承诺过的东西抱了期待。
延迟曲线突然上升,无非两种情况之一:生产者发送了很多,或者消费者停了。 对组做 describe,如果 CONSUMER-ID 一列是空的,就是后者。 即使消费者挂了,已提交的偏移量仍保留在 broker 上,所以等它重新启动,就会从那个位置 继续读取——那时什么会来两次、什么会消失,就是第 3 个模块的内容。
下一项实验要做什么
往有 3 个分区的主题里放入带键的事件,观察相同的键按顺序堆积在同一个分区里, 读取组的偏移量和延迟,确认两个组互相独立,然后 把分区增加到 6 个,亲自观察键的分布发生变化。