简介
成功运行的消费者先写入文件,再提交读取进度。这是两个独立操作。本实验展示两种失败情况:格式错误的 JSON 记录必须在进度越过它之前使处理停止;进程在写入后、提交前退出,则可能再次收到同一条记录。
你将先完成正常交付,然后检查坏记录的确切位置,最后使用提供的练习开关在刷新与提交之间停止。重启同一组后,文件中相同的事件 ID 和 Kafka 位置会揭示重复交付。你将观察这一行为,无需实现去重或重试框架。
这个独立环境准备了三个单分区 Topic,以及上一个实验中熟悉的完整消费者。额外的受控退出开关已经实现。你需要掌握之前的 Python JSON、文件和手动提交概念;无需重新配置 Broker 或设计新的恢复架构。请使用终端完成实验。
确认成功处理路径
这一步,你将在诊断问题前确认应用能处理有效输入。每个 Topic 都只有一个分区,记录位置容易理解。orders-normal 包含两条有效记录。消费者使用与之前相同的显式写入、刷新、同步消息提交和关闭顺序。
检查提供的应用及其中的受控退出开关,不要修改它:
cd /home/labex/project
cat /home/labex/project/consumer.py
该开关默认关闭。使用独立的组和文件运行正常的双记录处理路径:
bigdata-python /home/labex/project/consumer.py --topic orders-normal --group normal-reader --output /home/labex/project/output/normal-delivery.jsonl --max-messages 2 --timeout-seconds 20 > /home/labex/project/output/normal.log 2>&1
cat /home/labex/project/output/normal.log
cat /home/labex/project/output/normal-delivery.jsonl
预期 normal1 和 normal2 分别位于 offset 0 和 1,两次写入后提交的下一个 offset 为 2。max-messages 是本次调用请求的数量;后续步骤中,只追加的文件可以保留多次调用的观测结果。
这个成功基线有助于区分影响整个应用的连接问题与特定记录引起的失败。对于有意制造的失败,你将使用不同的 Topic、组和文件,以便清楚保留证据。
在格式错误的 JSON 处停止且不越过它提交
这一步,你将检查一条前置有效记录、一条无效 JSON 记录和一条后续有效记录。读取本地证据:
cat /home/labex/project/data/bad-events.txt
在 orders-bad 的分区 0 中,前置有效记录位于 offset 0,格式错误的文本位于 offset 1,后续有效事件位于 offset 2。这里采用快速失败策略:报告坏记录位置并退出。不要捕获错误后继续提交该分区的后续记录。
请求交付三条记录。下一条命令预期会失败。
bigdata-python /home/labex/project/consumer.py --topic orders-bad --group bad-reader --output /home/labex/project/output/bad-prefix.jsonl --max-messages 3 --timeout-seconds 20 > /home/labex/project/output/bad-first.log 2>&1
printf '%s\n' "$?" > /home/labex/project/output/bad-first-exit.txt
cat /home/labex/project/output/bad-first-exit.txt
cat /home/labex/project/output/bad-first.log
cat /home/labex/project/output/bad-prefix.jsonl
预期状态为非零,并出现 Decode failure,标明 topic=orders-bad partition=0 offset=1。文件应只包含位于 offset 0 的 bad-prefix。它成功写入后已提交,因此该组的下一个 offset 为 1;坏记录和后续记录仍未处理。
重启同一组,并追加到同一文件。下一条命令预期会失败。
bigdata-python /home/labex/project/consumer.py --topic orders-bad --group bad-reader --output /home/labex/project/output/bad-prefix.jsonl --max-messages 3 --timeout-seconds 20 > /home/labex/project/output/bad-retry.log 2>&1
printf '%s\n' "$?" > /home/labex/project/output/bad-retry-exit.txt
cat /home/labex/project/output/bad-retry-exit.txt
cat /home/labex/project/output/bad-retry.log
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group bad-reader --describe
同一个 offset 1 应再次失败。前置有效记录不应再次写入,因为它的下一个位置 1 已经提交。后续有效事件不能出现在文件中。这让策略清晰可见:操作人员必须决定如何处理坏数据,而这个最小应用既不会悄悄跳过它,也不会越过它提交。
成功写入后、提交前退出
这一步,你将使用准备好的 orders-replay Topic 制造另一种故障窗口。它只包含位于 offset 0 的 repeat1。--exit-before-commit 练习开关会在文件刷新后、显式提交前立即以状态 73 退出。它会记录真实位置,并在自动提交被禁用的情况下关闭消费者。
使用新组和单独的文件。下一条命令预期会失败。
bigdata-python /home/labex/project/consumer.py --topic orders-replay --group replay-reader --output /home/labex/project/output/redelivery.jsonl --max-messages 1 --timeout-seconds 20 --exit-before-commit > /home/labex/project/output/before-commit-exit.log 2>&1
printf '%s\n' "$?" > /home/labex/project/output/before-commit-exit-status.txt
cat /home/labex/project/output/before-commit-exit-status.txt
cat /home/labex/project/output/before-commit-exit.log
cat /home/labex/project/output/redelivery.jsonl
预期状态为 73,并保存一个 repeat1 封装对象,其位置为分区 0、offset 0。日志包含 CONTROLLED_EXIT_AFTER_WRITE_BEFORE_COMMIT 标记。这是已经提供的故障演示,无需你实现相应修改。
文件现在已有该记录,但消费者组尚未提交对应位置。因此同一组再次启动时,会应用之前学过的起始策略规则。下一步要观察的正是两者分离形成的窗口。
重启并识别重复交付的 Kafka 记录
这一步,你将在不启用练习开关的情况下重启 replay-reader。由于缺少已提交位置且采用 earliest 策略,它会再次读取保留记录。保持追加模式和同一输出文件,让两次交付都清晰可见:
bigdata-python /home/labex/project/consumer.py --topic orders-replay --group replay-reader --output /home/labex/project/output/redelivery.jsonl --max-messages 1 --timeout-seconds 20 > /home/labex/project/output/after-restart.log 2>&1
cat /home/labex/project/output/after-restart.log
cat /home/labex/project/output/redelivery.jsonl
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group replay-reader --describe
预期会看到两个封装对象,它们的 event_id、key、value、topic、partition 和 offset 完全相同。Broker 中仍只有一条原始记录。第二次调用成功提交下一个 offset 1,同时文件保留两次交付的证据。
使用以下证据区分常见情况:
| 观察到的情况 | 首先应查看的证据 |
|---|---|
| 没有新事件 | Topic 有效且组进度已到末尾;有时限的轮询可能不返回记录 |
| Topic 名称错误 | 将提供的名称与 Topic 列表或详情比较;本客户端禁止意外创建 Topic |
| 没有有效的已提交位置 | 检查组进度和配置的 earliest 起始策略;它可以读取保留历史 |
| 解码失败 | 查看报告的 Topic、分区、offset 和值的证据;组进度会停在该坏记录之前 |
文件写入和 Kafka 位置提交不属于同一个事务。两者之间的中断可能导致重复交付,这个真实记录位置就展示了这一点。本练习保留两次交付,不实现去重、死信队列或事务设计。最终挑战将运用成功处理路径,并提供明确的干净重试流程。
总结
你确认了正常交付,在格式错误的 JSON 处停止且没有越过它提交,并观察到已刷新但未提交的记录在重启后再次交付。相同的事件内容和 Kafka 位置解释了文件中的重复观测结果,而无需假设 Broker 中多出第二条事件。最终挑战将在新数据上综合运用成功发布、有界消费、文件输出和先写入后提交的行为。



