消息队列服务器磁盘已满时的紧急恢复

立刻运行 df -h 和 du -sh。确认挂载点。找出最占用空间的对象。所谓消息队列服务器磁盘已满,就是存储空间耗尽为零。这种情况会导致消息摄取停止、触发 Broker 关闭,并带来消息丢失风险。临时队列在重启后若发生索引重建,可能会丢失全部数据。此时你面对的是磁盘空间不足,而且每一分钟都至关重要。
本运行手册遵循严格顺序:先诊断问题,再手动释放空间,然后恢复 Broker,最后防止下次再次宕机。优先检查日志文件和队列。先核实存储使用情况,而不是只看队列数量。动作要快,但在弄清楚究竟是什么占满磁盘之前,不要删除任何内容。
诊断消息队列服务器磁盘已满
确认磁盘使用情况和挂载点
先从 df -h 开始。这个命令会显示每个挂载点及其使用百分比。你需要准确找出哪个挂载点达到了 100%。消息队列服务器磁盘已满通常发生在某一个卷上,而不是所有卷同时满。接着,对 /var/lib、/var/log 以及你的 Broker 数据目录运行 du -sh。这样可以按体积找出最大空间占用者,并指向根本原因。
要特别留意这样一种情况:某个卷已经满了,但其上的队列目录看起来并不大。消息队列目录可能与 Broker 的实际存储目录位于不同挂载点上。两边都要检查。磁盘写满往往源于磁盘空间不足、权限问题,或你未曾预料路径中的文件系统错误。在删除任何东西之前,先确认挂载点。
找出最占空间的对象和队列深度
仅看队列深度很容易误判。应使用管理工具检查深度和消息内容。Prometheus 可通过 rabbitmq_queue_messages 指标暴露停车队列和等待队列的消息数。死信速率计数器比深度更早揭示变化。读取 x-death 头部可以统计重试次数,并将瞬时故障与永久性故障区分开来。当死信速率在 5 分钟内超过发布速率的 1% 时,应触发告警。
即使队列看起来为空,存储也可能已经满了。这就是 MSMQ 悖论。要核实的是存储,而不是队列计数。索引开销是主要原因之一:
| 常见原因 | 影响 |
|---|---|
| Kafka 磁盘空间预分配 | 每个分区都会使用独立的 LogSegment;分区越多,占用放大越明显 |
| 每个队列的消息索引存储 | 每个队列都会保存索引,且每个索引文件都会消耗空间 |
| 大型部署中的索引开销 | 大量队列的索引可消耗数百 GB 空间;优化存储后可大幅下降 |
还要检查磁盘队列长度,把它作为 I/O 压力信号。数值过高意味着磁盘子系统已经不堪重负。旧日志文件和临时文件也会占据空间。检查日志目录和队列目录中是否存在陈旧数据。主机上的磁盘空间不足或内存不足会进一步恶化问题。应同时跟踪磁盘空间和内存,才能找到问题根因。
安全地手动清理消息队列
删除旧日志和临时文件
先处理最容易下手的目标。一旦确认消息队列服务器磁盘已满,/var/log 目录通常会有可以安全删除的轮转日志。运行 du -sh /var/log/ 找出最大的日志文件。删除一周前的归档文件。Broker 自身的日志轮转路径——如 /var/log/rabbitmq/ 或 /var/log/kafka/——也可能包含旧的 *.gz 文件,可以删除。/tmp 或 Broker 临时目录中的临时文件也会占空间,也应一并清理。
如果由于磁盘已满导致 Broker 无法访问,可以重启进入恢复模式。在 Ubuntu 上,可修改 GRUB 命令行进入单用户模式。这样你可以获得一个 shell,在没有其他进程干扰的情况下删除 /var/log/ 中的日志文件。
如果另一个文件系统上有空闲空间,优先将较旧的 send-summary 文件移走,而不是直接删除。对于 GreenArrow,可使用 hvmail_move_old_send_summary_files:
- 确认脚本位于
/var/hvmail/bin/hvmail_move_old_send_summary_files。 - 对 send-summary 目录运行
du -hs,估算所需空闲空间。 - 创建目标目录,例如
mkdir /media/scratch/var-hvmail-log-send-summary。 - 以最小文件年龄 7 天和目标路径为参数运行该命令。
- 确保备份覆盖新位置。Managed Backups 会自动处理这一点。
要遵守每个目录的存储配额。如果不能先完成日志轮转,就不要删除当前仍在写入的 Broker 日志。先删除旧的轮转数据,往往就能释放出大量空间,而无需立即动到队列数据。这会给你争取到足够空间,以更安全的方式继续操作。存储卷中的磁盘空间不足,可能会阻止 Broker 写入新条目。仅这一步,就可能释放出足够空间,让 Broker 干净地重启。重启前还要检查主机是否存在磁盘空间不足或内存不足的问题。
清空死信队列和过期消息
清理完日志之后,再处理队列数据。消息队列目录——例如 /var/lib/rabbitmq/mnesia 或同类目录——保存着队列数据。该位置如果磁盘空间不足,可能导致索引损坏。应使用 Broker 管理工具清空死信队列。对于 RabbitMQ,可使用 rabbitmqctl purge_queue。对于 Kafka,可使用 kafka-delete-records 并指定目标 offset。在清理之前,务必先与业务方确认这些消息不再需要用于重放。只有在确认之后,才能清理队列。
绝对不要直接从文件系统中删除正在使用的消息数据文件。这样会破坏索引并导致数据丢失。必须使用 Broker 命令。对于 MSMQ,存储目录即使在队列看起来为空时也可能被写满。MSMQ 使用由 .stg 和 .mq 文件组成的存储系统,即使消息数显示为零,这些文件仍会持续占用空间。应通过在该目录上运行 dir 来核实实际磁盘使用情况。
对于基于 PostgreSQL 的 Broker,旧的 WAL 文件可能会占用大量存储。先找出 WAL 增长的根因——例如不活跃的复制槽、归档失败,或大批量写入。如果原因是不再使用的复制槽,并且对应消费者已经消失,在确认其处于不活跃状态后,可使用 SELECT pg_drop_replication_slot('slot_name') 删除该复制槽。然后运行 CHECKPOINT; 以回收不再需要的 WAL 段。如果数据库因磁盘已满而停机,应先扩容 WAL 所在卷,启动 PostgreSQL,再继续调查。作为最后手段,可先进行 dry run,再使用 pg_archivecleanup 清理旧段。一个常见陷阱是:磁盘空间不足、权限问题或文件系统错误会伪装成队列故障。切勿通过直接删除数据文件来“重置”队列。
清空死信队列后,运行 Broker 的维护命令。对于 RabbitMQ,可使用 rabbitmqctl force_gc 触发压缩整理。对于 Kafka,可使用分区大小工具。随后观察几分钟队列深度,确认新消息已开始恢复流动。此时还不要急于重启 Broker。先验证队列深度。手动清理消息队列的目标,是为 Broker 恢复建立一个稳定基础。
恢复 Broker 并验证完整性
重启 Broker 并确认消息流恢复
只有在释放出足够空间后,才启动 Broker。运行你的服务管理器命令,并观察启动日志中是否存在错误。磁盘写满可能触发线性日志行为,从而改变 Broker 在磁盘上处理消息的方式。要逐行阅读日志输出。确认生产者已重新连接,消费者已恢复投递。
如果事故期间 rsyslog 的磁盘队列被激活,重启后还要确认它们能够正常排空。重启后再次检查队列深度。如果队列保持不变,通常意味着消费者被阻塞,或存储目录存在权限问题。还要关注日志中是否持续出现重试信息。这类重试通常指向一个有毒载荷,需要将其路由到死信队列。
如有必要,重新平衡分区或队列
重启后,分区可能分布不均。检查 Broker 指标,识别热点。若某个 Broker 承担了大部分负载,应将分区或队列迁移到负载较低的节点上。这个步骤在清理大量积压后尤其重要,因为负载分布不均会导致某个卷很快再次被写满。
在宣布恢复完成之前,必须验证消息完整性。死信队列用于承接有毒消息,而在智能体架构中,它的重要性会进一步提升。当智能体无法验证消息的语义完整性时,它会将载荷路由到死信队列进行检查,而不是直接丢弃或无限重试。这样可以在恢复结束之前提前暴露完整性故障。
消息签名与验证可通过数字签名确保真实性并防止篡改。消息哈希会使用带有 PSS 填充的私钥进行签名,生成一个再经过 base64 编码的签名。相应的验证函数会在消息被处理之前校验该签名——从而在宣布恢复完成前确认其完整性。
对于批量验证,每个接收模块都会使用匹配的算法、验证标签和密钥检查每一个消息批次。验证通过意味着处理正确,可以清理中间数据。验证失败则会触发告警、检查错误记录、重新发送消息并重新处理。基于标签的方案会通过标识符选择消息数据,并依据标签值验证完整性。在关闭事故之前,请确认这三项检查全部通过。
防止下一次磁盘写满事故
扩展 LVM 卷并设置配额
这次事故你挺过来了。现在必须阻止下一次发生。最快的预防手段,就是在磁盘写满之前扩展 LVM 卷。这可以防止因磁盘空间耗尽而导致崩溃。可按以下步骤操作:
- 运行
sudo lvs、sudo vgs和sudo pvs检查当前 LVM 配置,确认卷组中是否仍有可用空间。 - 使用卷组中全部可用空闲空间扩展逻辑卷:
sudo lvextend -l +100%FREE /dev/mapper/vg-lv_root。 - 扩展文件系统,使其与扩容后的卷匹配。对于 ext4,运行
sudo resize2fs /dev/mapper/vg-lv_root。对于 XFS,运行sudo xfs_growfs /。
为每个队列和每个日志目录设置磁盘配额与保留策略。存储配额能从源头限制增长。设置队列配额阈值,避免单个队列占满整个卷。对于 Kafka,基于大小和基于时间的保留策略可以同时生效。Kafka 会采用先满足的那个条件,从而同时提供时间和存储两方面的双重保护。设置 log.retention.bytes 以限制每个分区的总段大小,设置 log.segment.bytes 以限制单个段的大小,再设置 log.roll.hours 按计划生成新段。保留周期应与业务需求对齐:实时数据保留 7 到 30 天,分析型数据保留 90 到 365 天,合规数据保留 3 到 7 年。数据保留超过 90 天后,磁盘使用往往呈线性增长,因此 30 到 90 天通常是较合适的平衡点。
配置保留策略和邮件告警
Linux 本身不会在磁盘即将写满时原生发出事件,因此任何解决方案都需要轮询。可以使用 cron 配合 ntfy 之类的通知工具,定期检查磁盘使用率,并在超过阈值时发送告警。也可以查找现成脚本,通过轮询磁盘空间并触发邮件通知。还可考虑 Zabbix 或 Netdata 这类完整监控方案,它们可以在多个阈值上发送邮件告警。应按严重级别定义分层通知流程:75% 为 Warning,80% 为 Average,85% 为 High,90% 为 Critical,95% 为 Disaster。再根据级别把消息路由到不同渠道。
建议将监控告警阈值设置为 70% 和 85%。当存储磁盘使用率连续 10 分钟超过 70% 时触发预警;当连续 5 分钟超过 85% 时触发严重告警。达到 100% 时,所有持久化消息发送都会失败。应告警的是写满趋势,即 days-to-full,而不是只盯着当前占用百分比。具体数值没有是否具备“预警 + 严重”两个层级来得重要。
自动化清理任务,并监控其效果。应用消息 TTL 策略,使超过阈值的旧消息自动移除。设置最大队列长度策略,并指定如 drop-head 或 reject-publish 之类的溢出行为。这些机制可以将磁盘使用控制在有界范围内。使用 Prometheus 抓取 Broker 指标以跟踪可用磁盘空间。定义一个预警:当剩余磁盘空间低于 10 GB 且持续 5 分钟时触发;定义一个严重告警:当剩余磁盘空间低于 3 GB 且持续 1 分钟时触发。通过 Alert Manager 将告警路由到 PagerDuty 或 Slack。通过检查 rabbitmqctl status,确认当前没有活动告警。确认发布者已不再被阻塞。定期结合 df -h 与 rabbitmqctl status | grep disk_free 监控磁盘空间。留意主机是否存在磁盘空间不足或内存不足问题。检查日志文件和存储目录中是否有陈旧数据。按计划删除旧日志文件。这样才能让你的队列保持健康,让存储使用更可预测。
恢复分四步:诊断、手动清理空间、重启 Broker、预防再次发生。最快的处理路径,是在删除任何内容之前,先弄清楚到底是什么占满了磁盘。临时队列和 MSMQ 存储都可能在队列计数显示为零时写满卷,因此必须直接核查存储目录本身。现在,你已经拥有一套可重复执行的运行手册。
向前看。要在 LVM 卷写满前就完成扩容。通过自动化保留策略,让旧日志和陈旧文件按计划离场。让每一类日志都按定时策略轮转。把邮件告警设在 70% 使用率,而不是等到 100%。如果你能及早发现,消息队列服务器磁盘已满这种事故,只会发生一次。把这份手册放在手边。
常见问题
我该如何确认磁盘写满的确切原因?
运行 df -h 找出已写满的挂载点。对 /var/lib、/var/log 和 Broker 数据目录运行 du -sh。这样就能看出究竟是旧日志文件、队列索引,还是已存储消息占用了空间。
我可以直接从文件系统删除消息数据文件吗?
绝对不要直接删除正在使用的消息数据文件。这样会破坏 Broker 索引,并造成永久性数据丢失。应使用诸如 rabbitmqctl purge_queue 或 kafka-delete-records 之类的 Broker 管理命令。只能通过官方认可的工具来释放空间。
为什么 MSMQ 显示队列为空,但磁盘却是满的?
MSMQ 使用 .stg 和 .mq 文件进行存储,即使消息数显示为零,这些文件也可能依旧存在并持续占用空间。应在存储目录上运行 dir 以核实真实占用。务必检查存储目录本身,而不要只看队列计数。
我应该设置哪些监控阈值?
建议将预警设在 70%,严重告警设在 85%。监控重点应放在写满趋势,而不是原始百分比。通过 Prometheus 和 Alert Manager 配置通知,实现自动化响应。
