Nginx Shared Memory Fragment

Posted by zhuizhuhaomeng Blog on September 6, 2024

如何估算共享内存大小 我们知道, 其实 Nginx 的共享内存分配是划分成固定大小的块的。 这些大小分别是 8, 16, 32,64, …, page_size / 2。以 4K 页面大小为例,这个固定的块就是 8,16, 32, …, 2048。

nginx 共享内存分配除了固定大小外还有另一个特性就是内存分配是池化的。也就是说,这些固定大小的内存块是以内存池的形式存在的。 要申请一个 24 字节大小的内存,先计算对应的内存池的块大小,得到 32字节。然后从 32 字节的内存池中申请一个小块。 如果 32 字节的内存池没有可用的内存块,那么需要申请一个空闲页面,然后将这些页面切割成小块再分配内存。

这个分配内存的方式是很简单,高效的。但是带来了一个问题就是内存碎片。

考虑这样的场景: 我们需要分配 64, 128, 256 三种大小的内存块。那么随着申请和释放的进行,可用内存页面就被这三种大小的内存卡给占据了。 比如 64 直接的用了 n 个页面, 128 字节的用了 m 个页面, 256 字节的用了 k 个页面。 在一某一个时间,64 字节没有的页面完全分配完了,128 字节和 256 字节的内存池都还有不少空闲空间可以分配。 此时分配 64 字节就会失败,因为申请不到新的内存页了。只能眼睁睁的看着可用的内存而不能用,用川普来说就是干瞪眼。

其实应用层分配的时候是不知道是否有更大块的可用内存空间,应用层的是可以再次尝试更大的内存块,这时就可能会成功了。 但是这种分配方法并不友好,会造成杀鸡用上宰牛刀,宰牛的时候无刀可用的尴尬场景。

Lua shdict 在内存不足的时候还会回收过期的 LRU 表项(根据不同情况,甚至可能强制回收未过期表项)。但是在内存碎片的情况下,回收的 LRU 链表上的表项可能还是那些不是期望分配的空闲内存池的表项。因此回收后还是会造成分配不到内存的情况。

因此,使用 nginx 共享内存的时候,最好所有的表项的内存大小是一致的,这样才能更好的复用内存池中的空闲内存块。 关于这点我们在 使用 OpenResty 共享字典的注意事项 中也强调了。 这样通过深入分析内存分配给出更加具体的原因。