常见Key-Value存储系统的内存管理策略解析

Key-Value存储作为NoSQL存储的一种常见方式,提供了比SQL数据库更好的可扩展性和读写性能。比如当前开源最热门的Memcached和Redis;淘宝的Tair、腾讯的Cmem、Amazon的Dynamo等等,无论是做缓存还是持久存储,均使用内存作为主要存储介质,故内存管理策略就显得尤为重要了,是影响性能的重要因素。

这里从源代码层面对Memcached、Redis和UDC(腾讯以前用的一套KV持久化存储系统)的内存管理策略进行分析,3者的内存管理策略各不相同,其他KV系统也和这3种方法大同小异了。最后对这3种策略进行了实际的性能测试分析,有出入的请使劲拍砖!

--------------------------------------------------------------------------

1 MemCached:Slab Allocation机制

3个主要的概念:

1 Chunk块:固定大小的数据块,数据存储的基本单位,1个Chunk块存1个数据,剩余空间不做其他用途

2 Slab页:固定大小的内存块(页),申请内存的基本单位,默认为1MB,每个SlabClass会把申请的Slab切分成相同大小Chunk来存数据

3 SlabClass:由Chunk的大小确定,是大小相同的所有Chunk块集合

如上图所示,Memcahed启动时,会根据传入的-n(最小数据尺寸,默认48B),-f(增长因子,默认为1.25)启动选项初始化SlabClass。默认情况下,首个SlabClass的Chunk大小为80B(32B元数据头+48B最小数据尺寸),然后以1.25为比值生成等比数列,直到1MB(1个Slab页大小)为止。生成的SlabClass如下所示(perslab值为每个Slab页能分割出的Chunk个数):

$ memcached -vv

slab class 1: chunk size 80 perslab 13107

slab class 2: chunk size 104 perslab 10082

slab class 3: chunk size 136 perslab 7710

.....

slab class 41: chunk size 717184 perslab 1

slab class 42: chunk size 1048576 perslab 1

当用户发来请求时,Memcahed会根据key+value的值(能存放在1个Chunk内)来判断属于哪个SlabClass。确定这个SlabClass有无空闲的Chunk块,没有的话则先给这个SlabClass申请一个Slab页,将该Slab页按本SlabClass的Chunk块大小进行切割,然后分配1个来存放用户数据。(这里还有LRU算法淘汰旧数据的逻辑,就不放在这里分析)。

这种策略的特点:

  • 实现较复杂
  • 参数的选择(最小数据尺寸,增长因子)直接影响性能及内存利用率
  • 每个数据存放于1个Chunk块,读写简单

相关源代码:主要由slab.h/c(SlabClass和Slab相关)、item.h/c(Chunk块相关)这几个文件实现

* SlabClass数据结构:

* 初始化SlabClass(通过增长因子计算各个SlabClass的Chunk块大小等)

* 为SlabClass[i]申请一块新Slab内存页

---------------------------------------------------------------------------

2 Redis:简单mallc/free封装

相比Memcached,Redis做了一些优化。包括支持数据持久化(AOF和RDB两种持久化方式),支持主从复制读写分离,支持更丰富的数据结构(string、hash、set、list等)。

然而在内存管理方面,相比于Memcached的Slab Allocation机制,Redis的实现就显得简单粗暴得多了,就只是mallc/free的简单封装(屏蔽底层差异,添加相关元数据等)。

这种策略的特点:

  • 简单易实现,不易出错
  • 内存的利用率高
  • 大量系统调用开销大
  • 导致内存碎片,加重操作系统内存管理器负担

相关源代码:由zmalloc.h/c这两个文件实现,相对简单

*
申请内存

*
释放内存

---------------------------------------------------------------------------

3 UDC:切分Shm成固定长度的块,组织空闲块链和用户数据块链

UDC是腾讯以前用的一套基于Shm的、全Cache的键值存储系统。和MemCached或Redis使用进程堆内存不一样,UDC使用共享内存来存储数据,一方面进程Core的时候数据还在,一方面利于其他进程做数据落地、同步等操作。

如上图所示,UDC启动时会申请一块大Shm(LinkTable),然后将这块Shm分割成固定长度的Chunk块(默认200B),通过下标组织成一条空闲块链。当收到添加数据的请求时,从空闲块链中不断取出空闲块存放用户数据,直到存放完成形成一条用户数据块链,链头下标存放于索引层中(基于Shm的多阶Hash)。当收到删除数据的请求时,直接将对应的用户数据块链清零,插入空闲块链中即可。

这种策略的特点:

  • 实现较复杂
  • 代码不健壮有写乱Shm块链的风险
  • Shm数据块大小的选择直接影响性能及内存利用率
  • 用户数据块链组织成用户数据需多一次拷贝影响性能

相关源代码:主要由hash_cache.h/c这2个文件实现

* 初始化LinkTable

*
Set()操作,添加用户数据

*
Get()操作,获取用户数据

---------------------------------------------------------------------------

性能测试:

实验前提:

* 机器:Intel(R) Xeon(R) CPU X3440 @2.53GHz (单线程,这里只用到1核),8G内存

* key为uint32_t,值为 [1, 50w] 的随机数

* value为string,串长度为 [50, 500] 之间的随机字节数

1 不同块大小选择对UDC性能的影响:

实验方案:

* 对不同的块大小,进行1000w次读+写操作(即每次1个Set加1个Get),计算总耗时和内存利用率

* 开足够大的Shm(400MB)保证能存下所有数据

实验数据:

* 块大小与总耗时(单位:s)的关系:

*
块大小与内存利用率的关系:

实验结论:

* 内存块越大,系统性能越好,但内存利用率越低。需要在这2着间取个平衡点

* 一般取值为"平均(key+value)长度/2",且很容易理解,用户数据长度基本一致即波动范围小时,内存碎片会减低,内存利用率会增加

2 UDC、Memcache、Redis内存管理策略性能对比

实验方案:

* UDC策略使用默认块大小为200B,Memcached策略使用默认增长因子1.25

* 分别进行10w、50w.... 5千万、1亿次压测操作,每次进行1个Set加1个Get,计算总耗时和内存利用率

实验数据:

* 3种内存管理策略在不同压测次数下的总耗时(单位:s)情况

* 本实验3种内存管理策略的内存利用率(与压测次数无关)

- UDC策略:72.83%

- Memcached策略:88.19%

- Redis策略:98.77%

实验结论:

* Memcached的Slab Allocation机制在性能上的表现是最好的,且内存利用率也接近90%

* 当然,Memcached和Redis是基于进程堆的,主要用于缓存;UDC是基于Shm,用来做持久化存储,需考虑更多数据分布/同步/容灾等方面,3着没必要用来直接比较,这里只是单从内存管理策略上进行分析。

常见Key-Value存储系统的内存管理策略解析,布布扣,bubuko.com

时间: 2024-10-10 22:30:52

常见Key-Value存储系统的内存管理策略解析的相关文章

内存管理策略

内存管理策略 在引用计数的环境下管理内存使用的基本模型是,通过在NSObject协议定义的方法和提供标准命名的方法.NSObject类也定义了一个方法"dealloc",当一个对象被释放时此函数被调用.本文介绍了您需要知道的,如何正确的管理内存在一个Cocoa程序,并提供了一些正确的使用实例. 基本内存管理规则 内存管理模型是基于对象所有权的.任何一个对象可能会有一个或者多个所有者.只要一个对象有至少一个所有者,那么它需要继续退出.当一个对象没有所有者时,系统会自动销毁它. 为了确保你

Redis 数据结构与内存管理策略(下)

Redis 数据结构与内存管理策略(下) Redis 数据类型特点与使用场景 String.List.Hash.Set.Zset 案例:沪江团购系统大促 hot-top 接口 cache 设计 Redis 内存数据结构与编码 OBJECT encoding key.DEBUG OBJECT key 简单动态字符串(simple dynamic string) 链表(linked list) 字典(dict) 跳表(skip list) 整数集合(int set) 压缩表(zip list) Re

iOS: ARC & MRC下string内存管理策略探究

ARC & MRC下string内存管理策略探究 前两天跟同事争论一个关于NSString执行copy操作以后是否会发生变化,两个人整了半天,最后写代码验证了一下,发现原来NSString操作没我们想的那么简单,下面就让我们一起看看NSString和NSMutableString在MRC下执行retain,copy,mutableCopy,以及ARC下不同的修饰__weak, __strong修饰赋值究竟发生了什么. 一.验证代码如下: - (void)testStringAddress { i

cocos2d-x 源码分析 : Ref (CCObject) 源码分析 cocos2d-x内存管理策略

源码版本来自3.x,转载请注明 cocos2d-x 源码分析总目录: http://blog.csdn.net/u011225840/article/details/31743129 1.Ref,AutoreleasePool,PoolManager Ref中包含了一个叫referenceCount的引用计数,当一个Ref类的变量被new的时候,其referenceCount的引用计数被置为1. 其中有三个重要的操作,retain,release,autorelease,下面源码分析时会详细说明

Rs2008内存管理策略

Rs2008 在内存管理方面已经有了很大的改变.主要增加了文件缓存,允许把内存数据卸载到文件缓存中.而Rs2005 都是把数据放到内存中.对于大数据量的报表而言,很容易出现OutOfMemory 错误. 在实际应用中,发现Rs2008 也经常出现 OutOfMemory 错误.主要有以下几个原因: 1 物理内存过低. 机器只有2G内存,特别是64位的机器. 2 同一台服务器同时承担两种角色: 数据库服务器,报表服务器.但没有限定数据库服务器的占用的最大内存. 由于数据库通常会采用贪婪的内存策略获

iOS内存管理策略和实践

来源:http://www.baidu.com/link?url=irojqCBbZKsY7b0L2EBPkuEkfJ9MQvUf8kuNWQUXkBLk5b22Jl5rjozKaJS3n78jCnSsUZjVQvFIW1IKcJMlR2fGj9eiy-gCY7ulTRyEuAi 关于iOS内存管理 应用程序内存管理是:程序运行时,开辟的内存空间.使用它,释放它的过程,写的好的程序尽可能少使用内存.在Objective-C中,内存管理被看做是:在很多数据.代码下,分配 转自hherima的博客

cocos2d-x 源代码分析 : Ref (CCObject) 源代码分析 cocos2d-x内存管理策略

从源代码版本号3.x.转载请注明 cocos2d-x 总的文件夹的源代码分析: http://blog.csdn.net/u011225840/article/details/31743129 1.Ref,AutoreleasePool.PoolManager Ref中包括了一个叫referenceCount的引用计数,当一个Ref类的变量被new的时候,其referenceCount的引用计数被置为1. 当中有三个重要的操作,retain.release,autorelease,以下源代码分析

第 8 章 内存管理策略

为了实现性能改进,应将多个进程保存在内存中,也就是说必须共享内存. 8.1 背景 内存是现代计算机运行的核心.内存由一个很大的字节数组来组成,每个字节都有各自的地址. 8.1.1 基础硬件 CPU可以直接访问的通用存储只有内存和处理器内置的寄存器. 每个进程都有一个独立的内存空间,可以保护进程不会互相影响. 基地址寄存器(base register):最小的合法的物理内存地址. 界限地址寄存器(limit register):指定了范围的大小. 合法范围为(base, base + limit)

安卓Android的内存管理原理解析

Android采取了一种有别于Linux的进程管理策略,有别于Linux的在进程活动停止后就结束该进程,Android把这些进程都保留在内存中,直到系统需要更多内存为止.这些保留在内存中的进程通常情况下不会影响整体系统的运行速度,并且当用户再次激活这些进程时,提升了进程的启动速度. 那Android什么时候结束进程?结束哪个进程呢?之前普遍的认识是Android是依据一个名为LRU(last recently used 最近使用过的程序)列表,将程序进行排序,并结束最早的进程. 其实安卓的内存管