个常见使用场景,redis使用场景( 四 )


当从Redis中读取数据的时候,如果读取的key对应的value不在内存中,那么Redis就需要从swap文件中加载相应数据,然后再返回给请求方 。这里就存在一个I/O线程池的问题 。在默认的情况下,Redis会出现阻塞,即完成所有的swap文件加载后才会相应 。这种策略在客户端的数量较小,进行批量操作的时候比较合适 。
但是如果将Redis应用在一个大型的网站应用程序中,这显然是无法满足大并发的情况的 。所以Redis运行我们设置I/O线程池的大小,对需要从swap文件中加载相应数据的读取请求进行并发操作,减少阻塞的时间 。对于像Redis和Memcached这种基于内存的数据库系统来说,内存管理的效率高低是影响系统性能的关键因素 。
传统C语言中的malloc/free函数是最常用的分配和释放内存的方法,但是这种方法存在着很大的缺陷:首先,对于开发人员来说不匹配的malloc和free容易造成内存泄露;其次频繁调用会造成大量内存碎片无法回收重新利用,降低内存利用率;最后作为系统调用,其系统开销远远大于一般函数调用 。所以,为了提高内存的管理效率,高效的内存管理方案都不会直接使用malloc/free调用 。
Redis和Memcached均使用了自身设计的内存管理机制,但是实现方法存在很大的差异,下面将会对两者的内存管理机制分别进行介绍 。Memcached默认使用Slab Allocation机制管理内存,其主要思想是按照预先规定的大小,将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,以完全解决内存碎片问题 。
Slab Allocation机制只为存储外部数据而设计,也就是说所有的key-value数据都存储在Slab Allocation系统里,而Memcached的其它内存请求则通过普通的malloc/free来申请,因为这些请求的数量和频率决定了它们不会对整个系统的性能造成影响Slab Allocation的原理相当简单 。
如图所示,它首先从操作系统申请一大块内存,并将其分割成各种尺寸的块Chunk,并把尺寸相同的块分成组Slab Class 。其中,Chunk就是用来存储key-value数据的最小单位 。每个Slab Class的大小,可以在Memcached启动的时候通过制定Growth Factor来控制 。假定图中Growth Factor的取值为1.25,如果第一组Chunk的大小为88个字节,第二组Chunk的大小就为112个字节,依此类推 。
当Memcached接收到客户端发送过来的数据时首先会根据收到数据的大小选择一个最合适的Slab Class,然后通过查询Memcached保存着的该Slab Class内空闲Chunk的列表就可以找到一个可用于存储数据的Chunk 。当一条数据库过期或者丢弃时,该记录所占用的Chunk就可以回收,重新添加到空闲列表中 。
从以上过程我们可以看出Memcached的内存管理制效率高,而且不会造成内存碎片,但是它最大的缺点就是会导致空间浪费 。因为每个Chunk都分配了特定长度的内存空间,所以变长数据无法充分利用这些空间 。如图 所示,将100个字节的数据缓存到128个字节的Chunk中,剩余的28个字节就浪费掉了 。Redis的内存管理主要通过源码中zmalloc.h和zmalloc.c两个文件来实现的 。
Redis为了方便内存的管理,在分配一块内存之后,会将这块内存的大小存入内存块的头部 。如图所示,real_ptr是redis调用malloc后返回的指针 。redis将内存块的大小size存入头部,size所占据的内存大小是已知的,为size_t类型的长度,然后返回ret_ptr 。当需要释放内存的时候,ret_ptr被传给内存管理程序 。
通过ret_ptr,程序可以很容易的算出real_ptr的值,然后将real_ptr传给free释放内存 。Redis通过定义一个数组来记录所有的内存分配情况,这个数组的长度为ZMALLOC_MAX_ALLOC_STAT 。数组的每一个元素代表当前程序所分配的内存块的个数,且内存块的大小为该元素的下标 。在源码中,这个数组为zmalloc_allocations 。

推荐阅读