twemproxy0.4原理分析-内存管理原理分析
概述
本文分析twemproxy0.4的内存管理机制。
不像java,go等语言c/c++代码需要自己维护内存,内存管理对于c/c++后台服务来说非常重要,一般来说好的c/c++后台服务都会考虑编写自己的内存管理模块,比如: nginx, memcached等。
最简单的内存管理模型是:建立两个队列,一个是正在使用的队列,一个是空闲队列。常用的数据结构(或对象)使用完后把它放入到空闲队列中,而当要使用数据结构(或对象)时只需要从空闲队列中取一个,而不需要向操作系统重新申请内存。但实际情况却没有这么简单,要不然jvm的gc(垃圾回收)就不会一而再再而三的进行优化了。实际的内存管理模块还还需要考虑:内存碎片的管理,空闲内存过多的管理,内存分配的效率,若是多线程/进程还要考虑内存分配的同步,内存消耗过大的管理等等问题。
另外一个开源软件:redis,为了避免内存管理的复杂性,使用了第三方的内存管理模块:tc_malloc或je_malloc。这些内存管理模块就是为了解决c/c++内存管理问题,这样redis就可以专注于自己的功能设计。后续若有时间,我们再来探讨je_malloc的原理。
twemproxy0.4并没有使用第三方的内存管理模块,而是自己实现了一套内存管理机制,至于该内存管理机制是否解决了以上提到的各种问题?让我们一起走进它的内存管理世界。
twemproxy0.4内存管理机制介绍
在twemproxy0.4的官方github代码库的说明文档中对它的内存管理机制有一段说明,它的题目是:zero copy(一般来说zero copy是针对系统调用深度来说的,zero copy的函数不会进入内核层,从而加快系统运行的速度,关于zero copy的解读可以查看这篇文章:zero copy)。
我把它的介绍文章摘要如下:
在twemproxy中,请求进入和响应输出的所有内存都在mbuf中进行分配。mbuf启用零拷贝,因为客户端接收请求使用到的内存结构,后端服务器可以复用。同样,从服务器端接收响应时使用的mbuf,也可在客户端复用。
此外,使用重用池管理mbuf的内存。这就是说:一旦分配了mbuf,它就不会被释放,而只是重新进入重用池。默认情况下,每个mbuf块的大小设置为16K。在mbuf大小和twemproxy可以支持的并发连接数之间需要进行权衡。较大的mbuf可以减少读取请求或响应时twemproxy系统调用的次数。但是,对于大的mbuf,每个活动的连接将使用16K的缓冲区,这可能让twemproxy在处理来自客户端的大量并发连接时存在问题。当twemproxy用于处理大量并发客户端连接时,应使用-m或–mbuf-size = N参数将块大小设置为512字节的小值。
我们总结一下:
- (1) twemproxy通过mbuf结构来管理内存,默认申请的内存块大小是16K。
- (2) 内存结构是复用的。
- (3) 在有大量客户端进行并发连接时,若使用默认的内存块大小可能存在问题,需要通过参数进行设置。
twemproxy内存管理的实现
基本数据结构
twemproxy会设置两个静态变量来控制mbuf,一个用来记录空闲mbuf的数量,一个是空闲mbuf列表头结构,这两个变量的定义如下:
static uint32_t nfree_mbufq; /* 空闲mbuf的数量,初始化为0 */
static struct mhdr free_mbufq; /* 空闲mbuf列表头结构 */
- mbuf数据结构
mbuf数据结构包括以下一些成员,如下:
成员名 | 成员类型 | 说明 |
---|---|---|
magic | uint32_t | mbuf的随机数,一个常量值 |
next | struct { struct type *stqe_next; } | 下一个mbuf节点 |
pos | uint8_t * | mbuf中数据的位置 |
last | uint8_t * | mbuf中目前数据的结束位置 |
start | uint8_t * | mbuf中内存块的开始位置(指针) |
end | uint8_t * | mbuf中数据内存区的结束位置(指针) |
mbuf的创建
mbuf创建时会先申请一个chunk_size的内存块,然后把该mbuf的结构作为头,放在该数据块的最后。官方给出的理由是:这样安排可以让我们在进行put或get操作时尽早的知道内存越界。
- mbuf结构创建初始化状态
创建一个mbuf后的初始化状态下的内存布局和指针指向如下图所示:
- 加入一些数据后的状态如下:
内存的使用
twemproxy0.4会把空闲的mbuf放到一个链表上,形成一个空闲mbuf链:free_mbufq。
结构如下图所示:
- msg的mbuf链结构如下:
从客户端或服务器端接收到的消息都会保存到一个叫msg的结构体中,每个msg结构体都会通过一个mbuf的链表来来保存数据。
- 内存的使用原理
当有消息到来时,会先查看msg的最后一个节点的mbuf空间是否还有剩余空间,若还有剩余空间先把部分请求接收到该空闲空间中。若已经没有空闲空间,则从空闲mbuf队列中取一个空闲的mbuf,若空闲的mbuf队列为空,则会再创建一个mbuf结构体,并把它添加到msg的mbuf链表中。
说明:通过这样一种简单的内存使用算法,可以保证mbuf的内存被充分使用。
内存的释放
在释放内存时,先要定位到mbuf内存块的头部,才能调用free释放掉整个chunk_size的内存块。所以,释放是需要进行以下操作:
...
buf = (uint8_t *)mbuf - mbuf_offset;
nc_free(buf);
...
总结
本文介绍了twemproxy0.4的内存管理机制。并对其内存管理的过程进行了阐述。