分布式缓存系统 Memcached 内存管理机制
在前面slab数据存储部分分析了Memecached中记录数据的具体存储机制,从中可以看到所采用的内存管理机制——slab内存管理,这也正是linux所采用的内存高效管理机制,对于Memchached这样的内存cache服务器,内存高效管理是其最重要的任务之一。
Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操作系统首次引入的一种算法。Jeff 的分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存。Jeff 发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。因此他的结论是不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。
Memecached 中的slab 分配器也正是使用这一思想来构建一个在空间和时间上都具有高效性的内存分配器。下图 给出了 slab 结构的高层组织结构。在最高层是cache_chain,这是一个 slab 缓存的链接列表。这对于 best-fit 算法非常有用,可以用来查找最适合所需要的分配大小的缓存(遍历列表)。cache_chain 的每个元素都是一个kmem_cache 结构的引用(称为一个 cache),它定义了一个要管理的给定大小的对象池。在需要某个特定大小的内存对象时,首先从cache_chian中找到最佳大小的一个kmem_cahce,然后再在对应的kem_cahe中按某种算法(如首先利用空闲对象,没有则按LRU机制释放已用或过期对象)最终获得所需的大小的空间。具体结构如下图所示:
从该图可看出,这与前面所分析的Memcached的Item的存储结构图正是一致的。此处的cache_chain对应前面的slabclass数组(管理各种大小的slab集合),而kmem_cahe对应slabclass中的某个元素(slab_list链表)(管理某个特定大小的slab链表)。在删除Item时,也不会将所对应的内存还给操作系统,而只是从对应的已分配中链表中去掉,转而加到对应的空闲链表slots中以供后续循环利用。
memcached中内存分配机制主要理念:
1. 先为分配相应的大块内存,再在上面进行无缝小对象填充
2. 懒惰检测机制,Memcached不花过多的时间在检测各个item对象是否超时,当get获取数据时,才检查item对象是否应该删除,你不访问,我就不处理。
3. 懒惰删除机制,在memecached中删除一个item对象的时候,并不是从内存中释放,而是单单的进行标记处理,再将其指针放入slot回收插糟,下次分配的时候直接使用。
Memcached首次默认分配64M的内存,之后所有的数据都是在这64M空间进行存储,在Memcached启动之后,不会对这些内存执行释放操作,这些内存只有到Memcached进程退出之后会被系统回收。下面分析Memcached的内存主要操作函数,按逐级调用顺序给出。
/*//内存初始化,settings.maxbytes是Memcached初始启动参数指定的内存值大小,settings.factor是内存增长因子
slabs_init(settings.maxbytes, settings.factor, preallocate);
#define POWER_SMALLEST 1 //最小slab编号
#define POWER_LARGEST 200 //首次初始化200个slab
//实现内存池管理相关的静态全局变量
static size_t mem_limit = 0;//总的内存大小
static size_t mem_malloced = 0;//初始化内存的大小,这个貌似没什么用
static void *mem_base = NULL;//指向总的内存的首地址
static void *mem_current = NULL;//当前分配到的内存地址
static size_t mem_avail = 0;//当前可用的内存大小
static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];//定义slab结合,总共200个 */
/**
* Determines the chunk sizes and initializes the slab class descriptors
* accordingly.
初始化整个slabcalss数组
limit:Memcached的总的内存的大小。
factor:chunk大小增长因子
*/
void slabs_init(const size_t limit, const double factor, const bool prealloc) {
int i = POWER_SMALLEST - 1;
//size表示申请空间的大小,其值由配置的chunk_size(指item中的数据部分大小)和单个item的大小来指定
unsigned int size = sizeof(item) + settings.chunk_size;
mem_limit = limit;
if (prealloc) {//支持预分配
/* Allocate everything in a big chunk with malloc */
mem_base = malloc(mem_limit);//分配限定的空间,mem_base为总内存起始地址
if (mem_base != NULL) {
mem_current = mem_base;//mem_current为当前分配空间地址
mem_avail = mem_limit;//可用(总分配空间中还未分配给Item的部分)
} else {
fprintf(stderr, "Warning: Failed to allocate requested memory in"
" one large chunk.\nWill allocate in smaller chunks\n");
}
}
//置空slabclass数组
memset(slabclass, 0, sizeof(slabclass)); //sizeof(slabclass)为整个数组大小,而非指针大小
//开始分配,i<200 && 单个chunk的size<=单个item最大大小/内存增长因子
while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {
/* Make sure items are always n-byte aligned */
//确保item总是8byte对齐
if (size % CHUNK_ALIGN_BYTES)
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);//没对齐,则补齐
slabclass[i].size = size;//slab中chunk的大小设为补齐的大小
slabclass[i].perslab = settings.item_size_max / slabclass[i].size;//每个slab中的chunk数量
size *= factor;//下一个slab中的chunk扩大factor倍
if (settings.verbose > 1) {//如果有打开调试信息,则输出调试信息
fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
i, slabclass[i].size, slabclass[i].perslab);
}
}
//slab数组中的最后一个slab,此时chunk大小增加为1M,因此只有一个chunk
power_largest = i;
slabclass[power_largest].size = settings.item_size_max;
slabclass[power_largest].perslab = 1;
if (settings.verbose > 1) {
fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
i, slabclass[i].size, slabclass[i].perslab);
}
/* for the test suite: faking of how much we've already malloc'd */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
if (t_initial_malloc) {
mem_malloced = (size_t)atol(t_initial_malloc);
}
}
if (prealloc) {
//真正分配空间:分配每个slab的内存空间,传入最大已经初始化的最大slab编号
slabs_preallocate(power_largest);
}
}
//分配每个slabclass数组元素的内存空间
static void slabs_preallocate (const unsigned int maxslabs) {
int i;
unsigned int prealloc = 0;
for (i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {
if (++prealloc > maxslabs)
return;
//执行分配操作,对第i个slabclass执行分配操作
if (do_slabs_newslab(i) == 0) {
fprintf(stderr, "Error while preallocating slab memory!\n"
"If using -L or other prealloc options, max memory must be "
"at least %d megabytes.\n", power_largest);
exit(1);
}
}
}
//为第id个slabclass执行分配操作
static int do_slabs_newslab(const unsigned int id) {
slabclass_t *p = &slabclass[id];//p指向第i个slabclass
int len = settings.slab_reassign ? settings.item_size_max:p->size*p->perslab; //len为一个slabclass的大小
char *ptr;
//grow_slab_list初始化slabclass的slab_list,而slab_list中的指针指向每个slab
//memory_allocate从内存池申请1M的空间
if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) ||
(grow_slab_list(id) == 0) ||
((ptr = memory_allocate((size_t)len)) == 0)) { //优先从Memchahed内存池中分配,如果内存池为空则从系统分配给定大小内存
MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
return 0;
}
memset(ptr, 0, (size_t)len);
//将申请的1M空间按slabclass的size进行切分
split_slab_page_into_freelist(ptr, id);
p->slab_list[p->slabs++] = ptr;
mem_malloced += len;//增加已经分配出去的内存数
MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);
return 1;
}
//将同级别slab的空间且分为该大小的chunk
static void split_slab_page_into_freelist(char *ptr, const unsigned int id) {
slabclass_t *p = &slabclass[id];
int x;
for (x = 0; x < p->perslab; x++) {
do_slabs_free(ptr, 0, id);//创建空闲item
ptr += p->size;//指针前移item的大小
}
}
//创建空闲item ,挂载到对应slabclass的空闲链表中
static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {
slabclass_t *p;
item *it;
assert(((item *)ptr)->slabs_clsid == 0);
assert(id >= POWER_SMALLEST && id <= power_largest);//判断id有效性
if (id < POWER_SMALLEST || id > power_largest)
return;
MEMCACHED_SLABS_FREE(size, id, ptr);
p = &slabclass[id];
it = (item *)ptr;
it->it_flags |= ITEM_SLABBED;
it->prev = 0;
it->next = p->slots;//挂载到slabclass的空闲链表中
if (it->next) it->next->prev = it;
p->slots = it;
p->sl_curr++;//空闲item个数+1
p->requested -= size;//已经申请到的空间数量更新
return;
}
至此,从创建slabclass数组,到最底层的创建空闲item并挂载到对应的slabclass的空闲链表slots的头部, 的操作完成。
Memcached的内存池由起始地址指针、当前地址指针、剩余可用空间等变量维护,每次内存池操作只需要相应的改变这些变量即可。
以下为内存池分配操作函数:
//优先从Memcached的内存池分配size大小的空间
static void *memory_allocate(size_t size) {
void *ret;
if (mem_base == NULL) {//如果内存池没创建,则从系统分配
ret = malloc(size);
} else {
ret = mem_current;
//size大于剩余的空间
if (size > mem_avail) {
return NULL;
}
//按8字节对齐
if (size % CHUNK_ALIGN_BYTES) {
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
}
//扣除size个空间
mem_current = ((char*)mem_current) + size;
if (size < mem_avail) {
mem_avail -= size;//更新剩余空间大小
} else {
mem_avail = 0;
}
}
return ret;
}
由此可见,内存池的实现其实是很简单的。当然这里只给出了内存池中内存分配的操作,而内存释放操作也类似。总之,内存池的操作,需要维护内存池的几个指针变量和空间指示变量即可。
分布式缓存系统 Memcached 内存管理机制的更多相关文章
- 分布式缓存系统 Memcached 整体架构
分布式缓存系统 Memcached整体架构 Memcached经验分享[架构方向] Memcached 及 Redis 架构分析和比较
- 分布式缓存系统 Memcached slab和item的主要操作
上节在分析slab内存管理机制时分析Memcached整个Item存储系统的初始化过程slabs_init()函数:分配slabclass数组空间,到最后将各slab划分为各种级别大小的空闲item并 ...
- 简述 Memcached 内存管理机制原理?
早期的 Memcached 内存管理方式是通过 malloc 的分配的内存,使用完后通过 free 来回收内存,这种方式容易产生内存碎片,并降低操作系统对内存的管理效 率.加重操作系统内存管理器的负担 ...
- 分布式缓存技术memcached学习(三)——memcached内存管理机制
几个重要概念 Slab memcached通过slab机制进行内存的分配和回收,slab是一个内存块,它是memcached一次申请内存的最小单位,.在启动memcached的时候一般会使用参数-m指 ...
- 分布式缓存技术memcached学习系列(三)——memcached内存管理机制
几个重要概念 Slab memcached通过slab机制进行内存的分配和回收,slab是一个内存块,它是memcached一次申请内存的最小单位,.在启动memcached的时候一般会使用参数-m指 ...
- memcached内存管理机制分析
memached是高性能分布式内存对象系统,通过在内存中存储数据对象来减少对磁盘的数据读取次数,提高服务速度. 从业务需求出发.我们通过一条命令(如set)将一条键值对(key,value)插入mem ...
- [Memcached]分布式缓存系统Memcached在Asp.net下的应用
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.Memcached ...
- 分布式缓存系统Memcached在Asp.net下的应用
Memcached 是一个高性能的分布式内存对象缓存系统.用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来降低读取数据库的次数,从而提高动态.数据库驱动站点的速度. Memcache ...
- 分布式缓存系统Memcached简介与以及在.net下的实践(转)
缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...
随机推荐
- $1...$9 属性 (RegExp) (JavaScript)
返回在模式匹配期间找到的,所存储的最近的九个部分. 只读. RegExp.$n 参数 RegExp 始终为全局 RegExp 对象. n 1 至 9 之间的任意整数. 备注 每 ...
- 遇到不确定的json格式
我们在调用webservice接口,或者http接口时,返回的json数据,有时候会因为情况不同,返回的数据格式也不一样. 比如我在调用增加档案接口时,传入要添加的档案id,如果成功了,success ...
- 【spark】示例:二次排序
我们有这样一个文件 首先我们的思路是把输入文件数据转化成键值对的形式进行比较不就好了嘛! 但是你要明白这一点,我们平时所使用的键值对是不具有比较意义的,也就说他们没法拿来直接比较. ...
- STM32F103: NRF24L01
看了两天的24l01的相关资料了,一直有点模糊,今天下午感觉有点懂了,在板子上调试成功了,但是还没进行通讯测试.stm32和arduino进行通信还没成功 ,:( 先把stm32的NRF24L01配置 ...
- 修改MAC过程
首先打开PC的Telnet功能,如下: 对PC设置本地IP 2.cmd→telnet 192.168.1.230(出厂默认IP) 3.root →密码:20...................(公司 ...
- Myeclipse快捷键的设置以及默认的编码格式
设置默认的编码格式
- C#模拟网络POST请求
using System; using System.IO; using System.Net; using System.Text; using System.Collections.Generic ...
- Exception in thread "main" java.lang.OutOfMemoryError: Java heap space(Java堆空间内存溢出)解决方法
http://hi.baidu.com/619195553dream/blog/item/be9f12adc1b5a3e71f17a2e9.html问题描述Exception in thread &q ...
- Kali Linux安装SSH Server
Kali Linux默认并没有安装SSH服务,为了实现远程登录Kali Linux,我们需要安装SSH服务. 安装 OpenSSH Server # apt-get install openssh-s ...
- header("Location:http://www.baidu.com");
php 中的跳转函数 header("Location:http://www.baidu.com"); 但是一定要放在文件的开头 不允许有任何输出. 否则在之前添加 ob_s ...