http://previous.rabbitmq.com/v3_6_x/persistence-conf.html
RabbitMQ持久化层的目的是在大多数没有配置的情况下提供良好的结果。然而,某些配置有时是有用的。本页面解释如何配置它。建议你在采取任何行动之前把它全部读完。
持久化工作原理
持久化消息和非持久化消息均可被写入磁盘。持久化消息一到达队列就会被写入磁盘,非持久化消息只有在内存压力下才会被写入磁盘。在可能的情况下,持久化消息也保存在内存中,并且仅在内存压力下才从内存中取出。“持久化层”指用于将这两种类型的消息存储到磁盘的机制。
On this page we say "queue" to refer to an unmirrored queue or a queue master or a queue slave. Queue mirroring happens "above" persistence.
持久化层有两个组件:queue index和message store。queue index负责维护给定消息在队列中的位置、是否交付(deliverd)和是否确认(acknowledged)。因此,每个队列都有一个队列索引。(There is therefore one queue index per queue.)
message store是消息的key-value存储,在服务器中的所有队列上共享。消息(主体、任何属性和/或header)可以直接存储在队列索引中,也可以写入消息存储中。从技术上讲,有两个消息存储(一个用于临时消息,一个用于持久消息),但它们通常被认为是“message store”。
内存占用
在内存压力下,持久化层试图将尽可能多的数据写到磁盘上,并从内存中删除尽可能多的数据。然而,有些内容必须留在内存中:
- 每个队列为每个未确认的消息维护一些元数据。如果消息本身的目的地是msg store,则可以从内存中删除消息本身。
- msg store需要一个index.默认的msg store index为存储中的每条消息使用少量内存。The message store needs an index. The default message store index uses a small amount of memory for every message in the store.
queue index中的消息(message)
将消息写入队列索引有优点也有缺点。
优点:
- 消息可以在一个操作中写入磁盘,而不是两个;对于微小的消息,这可能是一个巨大的收益。
- 写入队列索引的消息不需要消息存储索引中的条目,因此在换出时不需要内存成本。
缺点:
- 队列索引在内存中保存固定数量的记录块;如果将非微型消息写入队列索引,则内存使用可能非常大。
- 如果交换器将消息路由到多个队列,则需要将消息写入多个队列索引。如果将这样的消息写入消息存储,只需要写入一个副本。
- 目的地为队列索引的未确认消息始终保存在内存中。Unacknowledged messages whose destination is the queue index are always kept in memory.
其目的是将非常小的消息作为优化存储在队列索引中,并将所有其他消息写入消息存储中。这是由配置项 queue_index_embed_msgs_below来控制的。默认情况下,序列化大小小于4096字节(包括属性和头部)的消息存储在队列索引中。
从磁盘读取消息时,每个队列索引至少需要在内存中保留一个段文件。段文件包含16384条消息的记录。因此,在增加queue_index_embed_msgs_below时要小心;一点点增加也可能导致内存的大量增长。
持久化性能可能表现不佳,因为持久化受限于必须处理的文件句柄或异步线程的数量。在这两种情况下,当您有大量队列需要同时访问磁盘时,都可能发生这种情况。
文件句柄过少
RabbitMQ服务器通常限制它可以打开的文件句柄的数量(无论如何,在Unix上)。每个正在运行的网络连接都需要一个文件句柄,其余的文件句柄可供队列使用。如果在考虑网络连接之后,磁盘访问队列多于文件句柄,那么磁盘访问队列将在它们之间共享文件句柄;每个都可以使用文件句柄一段时间,然后将其收回并交给另一个队列。
这可以防止服务器由于有太多磁盘访问队列而崩溃,但是这会变得非常昂贵。管理插件可以显示集群中每个节点的I/O统计信息;除了显示读、写、查找等的速率外,它还将显示重新打开的速率(reopen)——以这种方式回收文件句柄的速率。一个文件句柄太少的繁忙服务器可能每秒要重新打开数百次(reopen)——在这种情况下,如果有更多的文件句柄,它的性能可能会显著提高。
异步线程过少
Erlang虚拟机创建一个异步线程池来处理长时间运行的文件I/O操作。这些在所有队列之间共享。每个活动的文件I/O操作在进行时使用一个异步线程。因此,异步线程太少会损害性能。
注意,异步线程的情况与文件句柄的情况并不完全相同。如果一个队列按顺序执行许多I/O操作,那么如果它为所有操作保留一个文件句柄,它将执行得最好;否则,我们可能会频繁flush和seek,并使用额外的CPU编排它。然而,队列不能从跨操作序列持有异步线程中获益(事实上,它们不能这样做)。However, queues do not benefit from holding an async thread across a sequence of operations (in fact they cannot do so).
因此,理想情况下,应该有足够的文件句柄来处理所有正在执行I/O操作流的队列,并且应该有足够的异步线程来处理存储层可以执行的并发I/O操作的数量。
当缺乏异步线程导致性能问题时,情况就不那么明显了。(一般来说也不太可能;先检查其他东西!)异步线程过少的典型症状包括,在服务器应该忙于持久化而每个I/O操作的报告时间增加的短时间内,每秒的I/O操作数降为零。
异步线程的数量由这里描述的Erlang虚拟机的+A参数配置,通常通过环境变量RABBITMQ_SERVER_ERL_ARGS配置。默认值是+A 64。在更改此值之前,尝试几个不同的值可能是一个好主意。
替代message store index实现
如上所述,写入message store的每个消息都为其索引项(index)使用少量内存。message store index在RabbitMQ中是可插入(pluggable)的,其他实现可以作为插件使用,从而消除这一限制。(我们没有在服务器上附带任何代码的原因是它们都使用本地代码。)注意,这些插件通常会使消息存储运行得更慢。