apache / apache/rocketmq

[Bug] 顺序消费丢失消息一条(生产环境)

Open
#10,955 4 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
22.6k
Forks
12k
Avg merge
3d 1h
Merged PRs (30d)
27

Description

### Before Creating the Bug Report

- [x] I found a bug, not just asking a question, which should be created in [GitHub Discussions](https://github.com/apache/rocketmq/discussions).

- [x] I have searched the [GitHub Issues](https://github.com/apache/rocketmq/issues) and [GitHub Discussions](https://github.com/apache/rocketmq/discussions) of this repository and believe that this is not a duplicate.

- [x] I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ.

### Runtime platform environment

OpenSUSE
Push模式+顺序消费+CONSUME_FROM_LAST_OFFSET
同步刷盘

### RocketMQ version

Server:RocketMQ 4.9.7
Client:RocketMQ 4.7.1

### JDK Version

java8

### Describe the Bug

## 消费配置

- 消费模式:Push 模式 + 顺序消费
- 消费位点策略:`CONSUME_FROM_LAST_OFFSET`
- 启用TRACE,但无任何消费TRACE信息

## 业务消费方式

每个整点启动消费者进行消费,消费结束后立即 shutdown,下一个整点再重新启动。

## 问题概述

- 丢失消息:broker-b 的 5 号队列,第 9 号位置消息
- 消息发送时间(bornTime):2026-07-31 15:00:00.398
- 消费者启动时间:2026-07-31 15:00:00.230
- 内部首次计算消费位置时间:2026-07-31 15:00:00.230
- 消费者首次拉取消息时间:2026-07-31 15:00:01.348

注:消费者与消息发送方非同机,两端时间可能存在时差。

## 消费者日志分析

**阶段结论:** 消息从未进入 RocketMQ 客户端,在拉取(pull)过程中被直接跳过,导致丢失。

Image

## Broker-B 5 号队列索引分析

**阶段结论:** 该队列历史上断续地持续有消息写入,并非首次消费。commitlog 中曾有对应消息,只是后续被清理;但 consumeQueue 对应的消费 offset 记录理论上应当一直存在。由于是事后调查,consumerOffset 文件中实际是否存在该 offset 已无法考证。

Image

## 关键时间线(生产、消费可能存在时差)

| 时间 | 事件 |
|---|---|
| 15:00:00.230 | 消费者启动,内部首次计算出消费位置 offset = 9 |
| 15:00:00.398 | 消息实际写入(bornTime) |
| 15:00:01.348 | 消费者首次真正发起 pull,此时计算出的 offset = 10 |

首次计算 offset(9)与首次真正 pull(对应 offset 10)之间相隔约 **1.1 秒**,且消息的 bornTime 正好落在这个窗口中间。

## 结论

1. 消息从未被拉取到消费者端——即消费者的 pull 请求返回结果中不包含该消息。

2. 消费者启动时首次计算出的 offset = 9,但首次真正发起 pull 时使用的 offset 已经变成 10,中间 9 号消息被跳过。对 offset=9 的来源存在两种假设,单独看均有矛盾之处:

**情形 A:offset=9 为上次消费位置(记录在 consumerOffset 文件中)**
若如此,则无法解释为何 1.1 秒后首次 pull 时 offset 又变成了 10——如果只发生过一次 `computePullFromWhere`,pull 应该直接用 9 去拉取,而不会跳过 9 号消息。

**情形 B:offset=9 为 maxOffset(队列末尾位置,因未查到已持久化的 consumerOffset 而走的兜底逻辑)**
若如此,说明系统出现了类似 [apache/rocketmq#2708](https://github.com/apache/rocketmq/issues/2708) 描述的问题。但排查各方日志均未发现相关异常记录,该假设同样缺乏直接证据支撑。

## 下一步
- 目前基于现有证据暂时无法进一步闭环,暂无其他明确排查方向,烦请各位老师帮忙提供一些思路,看看是否还有遗漏的排查点。

### Steps to Reproduce

无法复现

### What Did You Expect to See?

目前还未彻底分析到消息丢失的底层原因,还无修复方案

### What Did You See Instead?

预期:offset=9 的消息应被恰好一次地正常消费(无论是被交接前的旧实例还是新实例)。
实际:所有已排查的消费者实例日志中均未发现该消息被拉取或处理的记录,业务侧也未感知到该消息被处理过。

### Additional Context

_No response_

Contributor guide

Open the contributing guide

Research direction

Trace the consumerOffset file, computePullFromWhere, and the first pull request against the reported broker-b queue. Compare the recorded offset with the commitlog and consumeQueue history to explain how offset 9 became 10 without a client trace. Done means identifying a supported root cause or documenting why the evidence cannot distinguish the proposed scenarios.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.