容器 RUNNING,为什么中间件仍不可用?ActiveMQ 与 Kafka/ZooKeeper 恢复实录

一套分阶段启动工具在基础设施就绪检查中反复超时。SSH 可连接,Docker 能响应,容器也显示 RUNNING,但 ActiveMQ 的 61616 与两个 Kafka broker 的 9092 未开放。排查最终落到了两个不同层面:ActiveMQ 的 KahaDB 恢复位置问题,以及 Kafka 启动失败与 ZooKeeper 从节点视图不一致。

**案例结论:**ActiveMQ 通过保留 journal、重建索引恢复;两个 Kafka broker 在仲裁就绪后重新启动,并修复了一个 ZooKeeper 从节点的旧数据视图。本文区分实测事实、合理推断和未确认事项,不将"启动成功"写成"所有历史消息均已证明无损"。

发布说明:系统名、容器名、IP、目录与日志位置标识均已替换为示例值;版本、端口、依赖关系、问题机制和验证计数保留。文中的命令是脱敏后的操作模板,执行前需替换路径并核对实际部署。

01|背景、目标与约束

启动器按"网络与数据库 → 缓存及基础中间件 → 消息队列 → 业务应用"的顺序执行。某个必需组件未就绪时,该阶段中断,业务启动门禁拒绝继续。这一设计避免了把依赖未就绪的业务容器一并拉起,但也意味着:日志停在 ActiveMQ,并不能证明排在它后面的组件正常。

环境与边界 本次案例
运行环境 Linux + Docker 20.10.22;Windows 启动器通过 OpenSSH 远程执行命令
组件版本 ActiveMQ 5.14.5、Kafka 2.0.0、ZooKeeper 服务端 3.4.12、Java 8
恢复目标 恢复真实中间件进程与协议服务;保留消息数据和回退材料;确认集群元数据一致
操作约束 优先只读检查;ActiveMQ 先备份与副本验证;Kafka 不重置消费位点,不清理消息分区;保留 ZooKeeper 健康多数派
明确不包含 未执行业务应用全链路验收;未证明事故前后逐条业务消息完整性;未修改启动器源代码

02|系统架构与故障边界

图 1 · 运维检查链、容器进程与中间件依赖边界

本案例有两种"容器仍运行、服务已退出"的形态。ActiveMQ 使用后台启动再跟随日志的命令;Kafka 组合容器则同时运行 ZooKeeper、Kafka 和占位进程。对这两种容器,Docker Running 只能说明容器内仍有进程,不能替代端口、协议或集群检查。

复制代码
/opt/mq/activemq/bin/activemq start && tailf /opt/mq/log.log

这里 activemq start 将 broker 放到后台。Java 后续失败,不会让已经运行的 tailf 自动退出。Kafka 容器中,ZooKeeper 的 Java 存活也不代表 kafka.Kafka 存活。因此检查"有没有 Java"同样不够精确。

03|数据模型、关联关系与一致性

图 2 · KahaDB 文件关联、恢复位置与数据流

本次没有修改业务关系数据库,也没有业务表、商户或门店 ID 映射证据。KahaDB 是文件型消息存储,不能为了凑 ERD 而虚构业务表。这里用存储实体关联与 ID 映射说明恢复依赖。

对象 职责与关联 本次处理
Destination / Message 队列或主题组织消息;Message ID、内部序号与存储位置之间存在映射。发送、确认、事务记录不是简单的一消息一文件关系。 读取管理统计;未消费业务队列
db-N.log journal 文件;一个文件包含多条持久化操作记录,Location 使用文件 ID 与偏移定位。 不手工删除或截断 journal;由 broker 正常回放
db.data 页文件中的索引和恢复元数据,包括位置映射与恢复进度。 备份后移出旧文件,触发 journal 重放
db.redo / db.free 页文件恢复及空闲页相关辅助文件;需与对应页文件状态一起理解。 与旧 db.data 一并保留移出,避免混用新旧索引状态
ZooKeeper /brokers/ids/N broker 注册节点;数据包含地址,ephemeralOwner 标识拥有该临时节点的会话。 比较三个节点的数据与 owner;未手工删除注册节点
ZooKeeper /controller 当前 Kafka controller 相关元数据及其会话归属。 与 broker 注册视图一起进行一致性校验

KahaDB 将操作写入 journal,并维护索引、确认及事务等恢复状态;这些步骤不应被理解为跨所有配置都完全同步的单次写入。故障恢复需要同时解释有效日志、索引进度和未完成事务。备份时必须停止写入,否则分别复制 journal 与索引可能得到跨时刻的文件组合。官方 KahaDB 说明与5.14.5 恢复实现可供对照。

04|排查证据与处置时间线

阶段 观测与动作 结论
发现 MySQL 曾端口超时,后续已通过;ActiveMQ 持续超时;两个 Kafka broker 未就绪。 不同组件必须分别验证,不能用一次重试概括全部问题。
ActiveMQ 定位 Java 已退出,tailf 存活;应用日志报告 KahaDB journal 恢复失败。 超时是表象,进程退出才是直接原因。
ActiveMQ 备份与试验 约 7 GB 数据,442 个文件通过 SHA-256 校验;同镜像、禁用网络的副本重建索引。 副本回放 3,154,246 条操作,耗时约 74.5 秒。
ActiveMQ 恢复 原索引移出后启动原服务,执行相同回放。 约 74.3 秒完成;临时队列收发、端口和工具检查通过。
Kafka 恢复 确认 ZooKeeper 仲裁健康,仅启动 A/B 的 Kafka broker。 两者分别注册 broker 1、2 并监听 9092。
深层一致性检查 C 从节点 zxid 与 A/B 相同,但返回旧 broker 数据及旧 owner。 角色和 zxid 检查不足以排除数据视图偏离。
ZooKeeper 修复 备份 C 状态,移出其旧快照与事务日志,保留 myid,从 leader 获取新 SNAP。 三节点 broker/controller 读取视图一致。
最终验证 3 broker、47 Topic、96 分区;离线与副本不足分区均为 0。 中间件就绪检查通过;业务全链路验收仍是独立步骤。

以上顺序来自本次实际操作记录;备份和命令等待时间包含磁盘写入及连接等待,不能把 74 秒回放耗时当作整个故障停机时间。

05|ActiveMQ:Symptoms → Investigation → Root Cause

Symptoms|容器 RUNNING,但 61616 一直不通

复制代码
容器已运行,保留:mq-broker
等待健康检查:mq-broker / ActiveMQ 监听端口
错误:健康检查超时:mq-broker / ActiveMQ 监听端口

Docker 标准输出只有启动脚本、Java 路径和 pidfile 创建记录,其中还包含历史启动日志。pidfile 创建仅证明启动脚本走到过某一步,不能证明 broker 此刻仍运行。

Investigation|从容器状态追到应用文件日志

复制代码
docker top mq-broker -eo pid,ppid,comm
docker exec mq-broker sh -c   'tail -n 120 /opt/mq/activemq/data/activemq.log'

# 脱敏后的关键应用日志
Recovering from the journal @12001:18000000
Failed to start Apache ActiveMQ
java.io.IOException: Failed to recover data at position:12001:18000000
Apache ActiveMQ 5.14.5 is shutdown

进程表只有 shell 与 tailf,没有 broker Java。宿主机内存与磁盘空间当时充足,容器未显示 OOMKilled;这些证据不能排除所有存储问题,但不支持把当前故障直接归因于内存不足或磁盘满。按日志位置读取记录头时,抽样的 5 字节为全零。

Root Cause|旧恢复状态与有效日志遍历边界不一致

可以直接确认的是:沿既有恢复位置启动会失败;移出旧索引后,broker 能从 journal 重新回放并正常启动。这个对照支持"既有索引/恢复状态与可遍历日志不一致"的判断。全零记录头只是定位线索,也可能位于预分配或未完成记录区域,不能据此认定整个 journal 文件损坏。

**根因边界:**没有足够证据确认哪次异常退出、虚拟机快照或底层写入导致了这种状态;也没有证据证明磁盘硬件故障。对"恢复为何失败"和"这种状态最初如何产生"必须分别下结论。

06|ActiveMQ:Resolution → Verification → Lessons Learned

图 3 · ActiveMQ 备份、副本试验、原服务恢复与验证时序

Resolution|完整备份,再在隔离副本上重建索引

原服务先停止,数据、配置、容器信息与旧索引分别留档。磁盘上的完整备份不作为试验工作目录;隔离容器使用同一镜像、独立数据副本和 --network none,避免客户端接入。为减少额外磁盘写入,测试副本使用了空间足够的内存盘,磁盘备份仍保留。

复制代码
# Bash 模板:示例名称和路径,请先替换并核对
set -eu
container=mq-broker
data=/srv/example/mq/activemq/data
recovery=/srv/example/recovery/amq-$(date +%Y%m%d-%H%M%S)
mkdir -p "$recovery"
chmod 700 "$recovery"
docker stop --time 30 "$container"
docker inspect "$container" > "$recovery/container-inspect.json"
docker cp "$container:/opt/mq/activemq/conf" "$recovery/conf-backup"
cp -a "$data" "$recovery/data-backup"
(cd "$data" && find . -type f -print0 | sort -z | xargs -0 sha256sum)   > "$recovery/source.sha256"
(cd "$recovery/data-backup" && sha256sum -c "$recovery/source.sha256")   > "$recovery/backup-verify.txt"

停止服务是为了建立同一时刻的可恢复文件集合。442 个文件校验通过,证明本次复制与源文件一致,而非证明数据在故障前就完整。docker inspect 可能包含环境变量等敏感内容,因此备份目录限制访问,文章中不发布完整 inspect。

复制代码
# 已完成副本验证后,才对停止状态的原服务执行
# 下面的 data、recovery、container 沿用前一段变量
test "$(docker inspect -f '{{.State.Running}}' "$container")" = false
mkdir "$recovery/index-before"
for file in db.data db.redo db.free; do
  if [ -f "$data/kahadb/$file" ]; then
    mv "$data/kahadb/$file" "$recovery/index-before/$file"
  fi
done
if [ -f "$data/activemq.pid" ]; then
  mv "$data/activemq.pid" "$recovery/stale.pid"
fi
docker start "$container"

这一步移出可重新构建的索引状态,保留 journal,让恢复走重新遍历路径。它不是所有 KahaDB 故障的通用修复:若回放再次报错,必须停止并定位实际日志问题。本次未启用 ignoreMissingJournalfiles,未手工清空、截断或删除消息日志;broker 启动后的正常清理行为仍由自身管理。

Verification|四层证据,而非只看容器状态

层次 实际结果 能证明什么
恢复日志 副本和原服务均回放 3,154,246 条存储操作;新恢复日志未检出 ERROR、Dropped、Failed to recover。 两条恢复路径得到相同操作数,未观察到新的恢复错误。
进程与端口 Java 持续存在;宿主机连接容器 IP:61616 成功。 进程和 TCP 服务可用。
协议与管理 临时队列发送随机标识并接收校验;JMX TotalMessageCount 为 2,279,与副本一致。 基本 JMS 收发正常,两个恢复实例的待处理统计一致。
运维工具 重新调用配置中的 ActiveMQ 健康检查,返回 True。 恢复结果与工具实际检查路径一致。

**不要混淆计数:**3,154,246 是 journal 存储操作数,2,279 是恢复后的待处理消息统计;两者不是同一个指标。临时队列测试未消费业务消息,也不证明断电后的持久化耐久性、业务幂等或历史消息零丢失。

Lessons Learned|让主进程退出可见,让应用日志可见

容器主进程与 broker 生命周期应绑定;前台运行、退出码传递、明确的 broker 进程检查,以及应用文件日志采集,应一起建设。本次仅完成服务恢复,这些启动器和镜像层改造属于后续工作。

07|Kafka:Symptoms → Investigation → Root Cause

Symptoms|两个容器活着,broker 已经退出

stream-a、stream-b 中的 ZooKeeper Java 仍运行,Kafka Java 均已退出。A 的历史日志出现注册节点所属会话不匹配;B 在启动时连接 ZooKeeper 超时。

复制代码
# stream-a:历史注册冲突(标识已脱敏)
Error while creating ephemeral at /brokers/ids/1
node already exists and owner does not match current session
KeeperException$NodeExistsException

# stream-b:历史依赖连接超时
ZooKeeperClientTimeoutException:
Timed out waiting for connection while in state: CONNECTING

# 当时配置
zookeeper.connection.timeout.ms=6000

Investigation|先确认进程,再比较三节点实际视图

A、B、C 的 broker.id 分别为 1、2、3,未发现这三个配置重复。ZooKeeper 当前角色为一主两从,leader 显示两个同步 follower。但从 C 读取 broker 注册数据,仍返回旧时间戳和旧 ephemeralOwner;A、B 返回当前会话。即使三节点显示相同 zxid,实际读取视图仍不一致。

图 4 · ZooKeeper 多数派保留、从节点备份与重新同步

Root Cause|启动失败后没有自动恢复;从节点还存在旧视图

B 的直接退出原因是启动阶段连接超时;依赖就绪后它没有自行重新拉起。A 的直接退出原因是会话归属冲突;当前 C 仍残留旧注册视图,与该类问题的机制相符,但不能仅凭事后快照证明每一次历史 NodeExists 的唯一来源。ZooKeeper 从节点视图偏离多数派则是本次实测确认的独立一致性问题。

因此,单纯增加等待时间、重启整个组合容器或删除 broker 节点都不是本次首选。重启整个容器会同时重启 ZooKeeper;并行重启多个容器会削弱仲裁,而无差别删除注册节点可能影响仍在线的 broker。

08|Kafka/ZooKeeper:Resolution → Verification → Lessons Learned

Resolution|恢复 broker 与修复从节点分开执行

  • 首先备份三个 broker 的配置、容器信息和启动前日志,确认健康 ZooKeeper 多数派。

  • 只在 A、B 容器内启动 Kafka broker,不重启容器内的 ZooKeeper;两者重新注册,9092 开放。

  • 确认 C 当前是 follower,A/B 能维持 2/3 仲裁后,只停止 C 的 ZooKeeper 进程;C 的 Kafka broker 持续运行。

  • 完整备份 C 的 dataDir 与 dataLogDir 并做 SHA-256 校验;移出两个目录各自的 version-2 子目录,保留 myid 和配置。

  • 重新启动 C 的 ZooKeeper,让它从健康 leader 获取 SNAP;保留旧状态目录,供后续分析和回退决策使用。

    已确认 quorum 正常且目标 broker 不存在后,启动 broker

    docker exec stream-a /bin/sh -c '/opt/mq/kafka/bin/kafka-server-start.sh -daemon /opt/mq/kafka/config/server.properties'
    docker exec stream-b /bin/sh -c '/opt/mq/kafka/bin/kafka-server-start.sh -daemon /opt/mq/kafka/config/server.properties'

    C 重新同步后的关键日志(zxid 使用示例标识)

    Getting a snapshot from leader 0x2a000000cc
    Snapshotting: 0x2a000000cc

这里没有清空整个 ZooKeeper 集群,也没有把旧状态直接覆盖到健康 leader。仅修复一个从节点的本地状态,并保持多数派提供权威状态。快照与事务日志需要配套理解;只移走其中一类文件而保留另一类旧状态,会使恢复路径难以解释。相关机制参见ZooKeeper 管理指南。

Verification|端口之外,验证元数据与 ISR

验证项 结果
broker 进程与注册 A、B、C 各存在一个 kafka.Kafka 进程;注册 ID 为 1、2、3。
TCP 与工具检查 三个 9092 均可连接;三个 Kafka 配置健康检查返回 True;ActiveMQ 检查仍为 True。
ZooKeeper 读取一致性 分别连接三个节点,比较 /brokers/ids、各 broker 数据与 ephemeralOwner,以及 /controller 数据与 owner,3/3 一致。
仲裁状态 leader 具有两个同步 follower,pending sync 为 0。
Kafka AdminClient 只读检查 发现 3 broker、47 Topic(含内部 Topic)、96 分区;离线分区 0,ISR 数少于副本数的分区 0。
操作边界 未创建业务消息,未提交消费位点,未重置 group offset,未清理 Kafka 消息分区。

这次 AdminClient 验证是只读元数据检查,证明检查时的 broker、leader 和副本状态正常;它不替代生产者发送、业务消费者处理、端到端幂等或历史消息校验。Kafka 日志中的历史 LogCleaner checkpoint 告警另列为观察项,本次未手工修改消费位点或清理检查点文件。

Lessons Learned|角色、进度、数据视图都要看

ZooKeeper "leader/follower 正常"与"zxid 一样"提供的是不同维度的信息,都不能单独替代关键元数据读取校验。对于 broker 注册冲突,要同时回答:谁在运行、哪个会话持有节点、不同服务器返回的内容是否一致。

09|5 Whys:把机制与未知项分开

追问 ActiveMQ 链路 Kafka/ZooKeeper 链路
1. 为什么业务不能继续启动? 61616 不可用,就绪门禁拒绝继续。 两个 9092 不可用,broker 未就绪。
2. 为什么端口不可用? broker Java 在持久化恢复期间退出。 broker 启动遇到连接超时或会话冲突后退出。
3. 为什么容器仍 RUNNING? tailf 继续存活,未绑定 Java 生命周期。 组合容器中的 ZooKeeper 和占位进程仍存活。
4. 为什么重复点击仍不能恢复? 容器运行被保留,既有恢复状态未改变。 未重启已退出的 broker;另有从节点旧视图问题。
5. 更深一层需要解决什么? 恢复状态一致性、备份演练、主进程管理与文件日志采集。初始状态异常的触发事件仍未确认。 启动时序、进程级恢复和 ZooKeeper 数据视图校验。视图偏离最初如何产生仍需更早日志与存储历史。

10|Troubleshooting:本次验证中踩过的两个坑

坑一:journal 只读挂载,并不保证 broker 能做"只读恢复"

Symptoms: 诊断容器报 Read-only file system,并提示缺少 db.free。Investigation: 该试验只复制了部分页文件,并把旧 journal 通过只读路径链接进来。**Root Cause:**页文件辅助状态不完整;存储实现也可能以可写模式打开 journal,恢复过程并非纯读。

Resolution: 放弃此诊断路径,使用完整、可写但网络隔离的数据副本。Verification: 在完整副本上重放成功并通过收发测试。**Lessons Learned:**只读试验的报错属于试验环境限制,不能拿它当成原故障的新根因;备份与试验工作副本应分开。

坑二:JMX 本地模式可能查到的是 CLI 自己

Symptoms: 命令退出码为 0,却没有 broker 统计。Investigation: 原查询使用 --jmxlocal,没有明确连接 broker JVM。Root Cause: 命令执行成功,不等于查询对象正确。Resolution: 读取真实 broker PID,使用 --pid 查询。Verification: 输出 BrokerName 与 TotalMessageCount。**Lessons Learned:**管理命令必须验证结果字段,不能只验证退出码。

复制代码
docker exec mq-broker sh -c '
  pid=$(cat /opt/mq/activemq/data/activemq.pid)
  /opt/mq/activemq/bin/activemq query --pid "$pid"     -QBroker=localhost --view BrokerName,TotalMessageCount
'
# 预期字段:BrokerName = localhost;TotalMessageCount = 2279

11|方案取舍与回退边界

方案 本次选择与理由
延长健康检查超时 未作为恢复手段。已退出的进程不会因等待更久而恢复;应先辨别"仍在初始化"和"已退出"。
清空 KahaDB 或忽略 journal 错误 未采用。可能改变消息可恢复性,且本次已有保留 journal 的有效恢复路径。
重建 ActiveMQ 索引 采用。完整备份、同版本副本验证后执行,旧索引留档。
整组重启 Kafka 组合容器 未采用。ZooKeeper 与 Kafka 共容器,整组操作会扩大仲裁影响。
手工删除 broker 注册节点 未采用。优先核实真实进程和 owner,再修复偏离多数派的从节点。
修复单个 ZooKeeper follower 采用。保留 A/B 仲裁,备份 C 状态,重新获取 leader 快照,逐项验证数据视图。

ActiveMQ 若验证失败,应先停 broker 并保留失败后的现场。只有确认没有新业务写入、并评估一致性后,才能决定恢复整套备份;不能在 journal 已变化时只把旧 db.data 覆盖回去。ZooKeeper follower 若无法重新加入,同样应保持多数派运行、隔离失败节点并保留新旧状态,不应盲目恢复旧状态后参与仲裁。

12|后续优化与完成标准

优先级 后续动作 完成标准
P0 将容器生命周期与真实 broker 生命周期绑定。 broker 异常退出可观测、可告警;不会被 tailf 或其他 JVM 掩盖。
P0 启动器在失败时采集应用日志、目标进程和端口状态。 一次失败记录能解释退出原因,而非只留下"90 秒超时"。
P1 Kafka 启动前检查 ZooKeeper 仲裁与会话建立,再启动 broker。 依赖未就绪可有限重试;避免重启整个组合容器破坏仲裁。
P1 增加 ZooKeeper 关键节点多视图核对与一致性告警。 比较注册数据、owner 和 controller;不能只比较角色或 zxid。
P1 演练消息存储备份、恢复与业务侧对账。 具备可恢复备份、校验清单、恢复耗时记录和逐条消息/幂等验收方案。
P2 观察 LogCleaner checkpoint 告警及压缩进度。 确认告警是否持续、压缩线程是否前进,再决定是否需要独立修复。
P2 评估旧版本组件的升级与部署拆分。 在隔离环境验证协议、存储格式、复制行为和回退;本次未直接升级生产组件。

13|关键实现:可复用的验证与恢复片段

A. ZooKeeper 单从节点重新同步模板

下面对应本次 C 从节点的处理方式。前提是 A/B 已核实健康且足以维持仲裁,C 确认为 follower,且 dataDir、dataLogDir 已根据配置和挂载逐一核对。脚本中的两类路径是示例值,不能照搬到其他节点。

复制代码
set -eu
node=stream-c
zk_data=/srv/example/stream/zk-c/data
zk_log=/srv/example/stream/zk-c/log
state=/srv/example/recovery/zk-c-$(date +%Y%m%d-%H%M%S)
mkdir -p "$state"
chmod 700 "$state"

# 必须先确认健康多数派与目标角色
for peer in stream-a stream-b; do
  docker exec "$peer" /opt/mq/zookeeper/bin/zkServer.sh status 2>&1     | grep -E 'Mode: (leader|follower)'
done
docker exec "$node" /opt/mq/zookeeper/bin/zkServer.sh status 2>&1   | grep 'Mode: follower'
docker exec "$node" /opt/mq/zookeeper/bin/zkServer.sh stop
sleep 2
if docker exec "$node" sh -c   "ps -ef | grep -q '[o]rg.apache.zookeeper.server.quorum.QuorumPeerMain'"; then
  echo 'ZooKeeper has not stopped'; exit 1
fi
docker exec "$node" sh -c "ps -ef | grep -q '[k]afka.Kafka'"

cp -a "$zk_data" "$state/data-backup"
cp -a "$zk_log" "$state/log-backup"
(cd "$zk_data" && find . -type f -print0 | sort -z | xargs -0 sha256sum)   > "$state/data.sha256"
(cd "$state/data-backup" && sha256sum -c "$state/data.sha256")
(cd "$zk_log" && find . -type f -print0 | sort -z | xargs -0 sha256sum)   > "$state/log.sha256"
(cd "$state/log-backup" && sha256sum -c "$state/log.sha256")

# 保留 myid、配置与旧状态;只移出目标从节点的 version-2
mkdir "$state/old-state"
mv "$zk_data/version-2" "$state/old-state/data-version-2"
mv "$zk_log/version-2" "$state/old-state/log-version-2"
docker exec "$node" /opt/mq/zookeeper/bin/zkServer.sh start
# 接着验证 follower、同步数、实际 broker/controller 视图与 Kafka 分区状态

此脚本不会替代人工确认"哪两个节点构成可信多数派"。若停止或校验阶段失败,应停在当前步骤保留证据;若启动后无法重新加入,应保持健康多数派,按前述回退边界处理,而不是继续重置其他成员。

B. Kafka/ZooKeeper 只读验证入口

复制代码
# 在能够访问集群的容器内执行;地址均为示例值
for endpoint in 10.20.0.11:2181 10.20.0.12:2181 10.20.0.13:2181; do
  printf 'ls /brokers/ids\nget /brokers/ids/1\nget /brokers/ids/2\nget /brokers/ids/3\nget /controller\nquit\n'     | /opt/mq/zookeeper/bin/zkCli.sh -server "$endpoint"
done
# 比较三个输出中的节点列表、注册数据、ephemeralOwner 和 controller。

/opt/mq/kafka/bin/kafka-broker-api-versions.sh   --bootstrap-server 10.20.0.11:9092
/opt/mq/kafka/bin/kafka-topics.sh --zookeeper 10.20.0.11:2181   --describe --unavailable-partitions
/opt/mq/kafka/bin/kafka-topics.sh --zookeeper 10.20.0.11:2181   --describe --under-replicated-partitions

上述 CLI 是便于复现的人工检查入口。本次最终验收使用程序同步读取三个 ZooKeeper 视图,并用 Kafka AdminClient 统计分区;不能仅以 CLI 的退出码推定"无离线分区",还需要检查实际输出。

C. ActiveMQ 临时队列收发探针

复制代码
import javax.jms.*;
import org.apache.activemq.ActiveMQConnectionFactory;

public class RecoveryProbe {
  public static void main(String[] args) throws Exception {
    Connection c = new ActiveMQConnectionFactory(
        "tcp://127.0.0.1:61616").createConnection();
    try {
      c.start();
      Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
      TemporaryQueue q = s.createTemporaryQueue();
      MessageConsumer reader = s.createConsumer(q);
      MessageProducer writer = s.createProducer(q);
      String token = "probe-" + java.util.UUID.randomUUID();
      writer.send(s.createTextMessage(token));
      Message result = reader.receive(5000);
      if (!(result instanceof TextMessage) ||
          !token.equals(((TextMessage) result).getText())) {
        throw new java.lang.IllegalStateException("probe failed");
      }
      reader.close(); writer.close(); q.delete(); s.close();
      System.out.println("JMS_SEND_RECEIVE_OK");
    } finally { c.close(); }
  }
}

# 探针文件已放入目标容器 /tmp,使用实际 JAVA_HOME
"$JAVA_HOME/bin/javac" -cp '/opt/mq/activemq/lib/*' /tmp/RecoveryProbe.java
timeout 20 "$JAVA_HOME/bin/java"   -cp '/tmp:/opt/mq/activemq/lib/*' RecoveryProbe

探针使用独立临时队列,只接收自己发送的随机标识;连接关闭后释放临时资源。它验证的是当前连接与基本 JMS 收发,不是业务队列消费或跨重启持久化验证。

14|总结与复盘结论

这次恢复的关键是把四个状态分开判断:容器是否运行、目标进程是否存活、协议服务是否可用、集群元数据是否一致。ActiveMQ 通过完整备份与隔离副本把索引重建变成可验证的恢复动作;Kafka 通过保留 ZooKeeper 多数派、单独恢复 broker、重同步偏离的 follower,避免把局部故障扩大成整组不可用。

已完成的是中间件进程、端口、基本协议和集群元数据恢复。没有证据的历史触发事件、业务端到端一致性和逐条消息完整性,保留为后续验证事项。这样的边界使复盘能迁移到类似场景,而不把一次有效操作包装成无条件通用修复。

参考资料与证据口径

相关推荐
真上帝的左手2 小时前
30. 技术专题-‌应用框架组件-缓存-Redis
数据库·redis·分布式·缓存·中间件
斯普信专业组3 小时前
Kafka 3.9.1 三节点集群部署方案(ZooKeeper 模式)
分布式·zookeeper·kafka
真上帝的左手4 小时前
30. 技术专题-‌应用框架组件-搜索引擎-ES
大数据·分布式·elasticsearch·搜索引擎·中间件
kingx317 小时前
BugKu PRA 渗透测试 2(Harry‘s Blog):9 Flag 全链实战复盘
中间件·渗透测试·log4j2·内网渗透·反序列化
皮皮林55119 小时前
Kafka 怎么保证消费只消费一次的?
kafka
sun_weitao1 天前
用 FastAPI + MQTT 做一个 ESP32最小化跑通示例
中间件·vue·fastapi
茶海棠1 天前
Hadoop+zookeeper+HBase+Kafka集群搭建全流程
hadoop·zookeeper·kafka·hbase
后台模板学习1 天前
Kafka 与 RabbitMQ 在用户认证与权限管理场景
kafka·rabbitmq·linq
真上帝的左手1 天前
12. 消息队列-Kafka进阶
分布式·中间件·kafka