立刻运行 df -hdu -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

  1. 确认脚本位于 /var/hvmail/bin/hvmail_move_old_send_summary_files
  2. 对 send-summary 目录运行 du -hs,估算所需空闲空间。
  3. 创建目标目录,例如 mkdir /media/scratch/var-hvmail-log-send-summary
  4. 以最小文件年龄 7 天和目标路径为参数运行该命令。
  5. 确保备份覆盖新位置。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 卷。这可以防止因磁盘空间耗尽而导致崩溃。可按以下步骤操作:

  1. 运行 sudo lvssudo vgssudo pvs 检查当前 LVM 配置,确认卷组中是否仍有可用空间。
  2. 使用卷组中全部可用空闲空间扩展逻辑卷:sudo lvextend -l +100%FREE /dev/mapper/vg-lv_root
  3. 扩展文件系统,使其与扩容后的卷匹配。对于 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 -hrabbitmqctl status | grep disk_free 监控磁盘空间。留意主机是否存在磁盘空间不足或内存不足问题。检查日志文件和存储目录中是否有陈旧数据。按计划删除旧日志文件。这样才能让你的队列保持健康,让存储使用更可预测。

恢复分四步:诊断、手动清理空间、重启 Broker、预防再次发生。最快的处理路径,是在删除任何内容之前,先弄清楚到底是什么占满了磁盘。临时队列和 MSMQ 存储都可能在队列计数显示为零时写满卷,因此必须直接核查存储目录本身。现在,你已经拥有一套可重复执行的运行手册。

向前看。要在 LVM 卷写满前就完成扩容。通过自动化保留策略,让旧日志和陈旧文件按计划离场。让每一类日志都按定时策略轮转。把邮件告警设在 70% 使用率,而不是等到 100%。如果你能及早发现,消息队列服务器磁盘已满这种事故,只会发生一次。把这份手册放在手边。

常见问题

我该如何确认磁盘写满的确切原因?

运行 df -h 找出已写满的挂载点。对 /var/lib/var/log 和 Broker 数据目录运行 du -sh。这样就能看出究竟是旧日志文件、队列索引,还是已存储消息占用了空间。

我可以直接从文件系统删除消息数据文件吗?

绝对不要直接删除正在使用的消息数据文件。这样会破坏 Broker 索引,并造成永久性数据丢失。应使用诸如 rabbitmqctl purge_queuekafka-delete-records 之类的 Broker 管理命令。只能通过官方认可的工具来释放空间。

为什么 MSMQ 显示队列为空,但磁盘却是满的?

MSMQ 使用 .stg.mq 文件进行存储,即使消息数显示为零,这些文件也可能依旧存在并持续占用空间。应在存储目录上运行 dir 以核实真实占用。务必检查存储目录本身,而不要只看队列计数。

我应该设置哪些监控阈值?

建议将预警设在 70%,严重告警设在 85%。监控重点应放在写满趋势,而不是原始百分比。通过 Prometheus 和 Alert Manager 配置通知,实现自动化响应。