bigywater.tech
RabbitMQ · 7

持久化:durable 队列、persistent 消息与 fsync

“消息持久化”到底是几个开关?只设 delivery_mode=2 够不够?kill -9 之后会丢多少?transient 消息是不是只在内存里?durable 队列绑在非 durable 的 exchange 上重启会怎样?这篇用真的重启和真的 kill -9 来验证。

541
默认配置下声明非持久、非排他队列,broker 关掉整条连接的错误码
4080 / 5000
发完马上 kill -9 时留下的持久消息数(等 confirm 后再杀则 5000 / 5000)
24844 vs 24852 KiB
20000 条 1 KB 的 persistent 与 transient 消息的磁盘增长,几乎相同

默认的限制

实测 · 本机执行输出
  默认配置下 durable=False 的队列 → ConnectionClosedByBroker: (541, "(541, 'INTERNAL_ERROR - Feature `transient_nonexcl_queues` is deprecated."…) —— 注意是整条连接被关,不只是 channel
  exclusive=True 的非持久队列 → 成功(随连接消失)
  服务器命名 + exclusive + auto_delete 的队列 → 成功

RabbitMQ 4.3.6 默认不允许声明“非持久、非排他”的队列(541 INTERNAL_ERROR,整条连接被关),只有 exclusive 的非持久队列可以。想做非持久队列的实验要在配置里打开 deprecated_features.permit.transient_nonexcl_queues。

两个开关

实测 · 本机执行输出
  重启前: {'tq.p': 100, 'tq.t': 100, 'dq.p': 100, 'dq.t': 100}
  正常重启后: {'dq.p': 100, 'dq.t': 0}
  规则:非持久队列重启后连队列本身都没了;持久队列里只有 persistent 消息还在,transient 消息没了(哪怕它所在的队列还在)

队列要 durable、消息要 delivery_mode=2,重启后消息才保留。只满足一个不行:非持久队列重启后连队列都没了,持久队列里的 transient 消息被清空。RabbitMQ 4.x 还默认禁止建非持久、非排他的队列。

kill -9

实测 · 本机执行输出
  发完马上 kill -9(不等任何确认): 重启后 kq 里有 4080 条(发了 5000 条)
  每条等 confirm、全部回来后 kill -9: 重启后 kq 里有 5000 条(发了 5000 条)
  发完等 2 秒再 kill -9: 重启后 kq 里有 5000 条(发了 5000 条)
  注意:kill -9 只杀进程,操作系统页缓存里的数据不会丢;真正的掉电丢数据这里测不到

没等 confirm 的发布在 broker 被杀时可能没处理完;收到 confirm 的不会丢。这里 kill -9 杀的是进程,操作系统缓存里的写入仍在,所以断电的情况需要另外的试验(没有测)。

磁盘

实测 · 本机执行输出
  基线 428 KiB;persistent 20000 条 × 1 KB 后增长 24844 KiB;transient 20000 条 × 1 KB 后增长 24852 KiB
  vhost 数据目录(msg_stores/vhosts/<id>/)下各子目录的大小(KiB): msg_store_persistent=0, msg_store_transient=0, queues=49704
  每个队列一个子目录(队列索引文件 .qi):队列目录数 = 2;其中一个队列目录里的文件: ['.qi', '.qi', '.qi', '.qi', '.qi', '.qs', '.qs', '.qs', '.qs', '.qs', 'ame']

4.3.6 的经典队列把小消息直接写进自己目录里的 .qs 文件,索引放 .qi;transient 消息同样写盘,只是重启时不被恢复。所以 transient 并不意味着不占磁盘。

exchange 与绑定

实测 · 本机执行输出
  重启前 exchange: ['x.durable(durable=true)', 'x.transient(durable=false)'];绑定: ['x.durable → dq.bind', 'x.durable → tq.bind', 'x.transient → dq.bind', 'x.transient → tq.bind']
  正常重启后 exchange: ['x.durable(durable=true)', 'x.transient(durable=false)'];绑定: ['x.durable → dq.bind', 'x.transient → dq.bind']
  重启后的队列: ['disk.persistent', 'disk.transient', 'dq.bind']
  向 x.transient 发布并路由: dq.bind 里现在有 1 条
  再 kill -9 重启后 exchange: ['x.durable(durable=true)', 'x.transient(durable=false)'];绑定: ['x.durable → dq.bind', 'x.transient → dq.bind']

非持久队列被清掉时,它的绑定也一起消失;而 durable=false 的 exchange 在 4.3.6 里重启后还在,绑定到持久队列的那条也保留。(这与较早版本的说法不同,以实测为准。)

分步演示

持久化
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / RabbitMQ 4.3.6 / Erlang/OTP 28.5 / pika 1.4.4(Homebrew 与临时虚拟环境);所有节点都是本机自己起的 RabbitMQ 进程(127.0.0.1,端口 9500 以上,每个实验独立的数据目录与节点名,不连任何外部服务);命令输出来自真实的 rabbitmqctl 与管理 API,发布与消费用 pika 的真实连接;计时用 time.perf_counter,数字每次运行会有小幅抖动。第二、五节用 rabbitmqctl shutdown 正常关闭再启动,第三、五节另用 kill -9;队列的消息数来自 rabbitmqctl list_queues;第四节磁盘大小用 du -sk 数据目录,发布后等 2 秒;非持久队列的实验在配置里开了 deprecated_features.permit.transient_nonexcl_queues
    • 没有验证:掉电或内核崩溃(fsync 是否真的落盘;macOS 的 fsync 不保证);quorum 队列和 stream 的持久化(见后面);Linux 上的磁盘写入量与延迟;更大的消息(超过队列自己存储的阈值)是否走共享消息存储;集群里节点之间的持久化;kill -9 时“丢 920 条”的具体比例每次会不同,没有统计
    京ICP备2026067502号-1京公网安备11010502063487号