早就听说过Memcached独特的内存管理方式,写着篇文章的目的就是了解Memcached的内存管理,学习其源代码.

1.什么是Slab Allocator

memcached默认情况下采用了名为Slab Allocator的机制分配、管理内存,Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块,以期望完全解决内存碎片问题。而且,slab allocator还有重复使用已分配的内存的目的。 也就是说,分配到的内存不会释放,而是重复利用。

2.Slab Allocation的主要术语

Page        分配给Slab的内存空间,默认是1MB,分配给Slab之后根据slab的大小切分成chunk
Chunk 用于缓存记录的内存空间
Slab Class 特定大小的chunk的组

3.Slab初始化

在Memcached启动时候会调用slab的初始化代码(详见memcached.c中main函数调用slabs_init函数).

slabs_init函数声明:

1
2
3
4
5
6
7
/** Init the subsystem. 1st argument is the limit on no. of bytes to allocate,
0 if no limit. 2nd argument is the growth factor; each slab will use a chunk
size equal to the previous slab's chunk size times this factor.
3rd argument specifies if the slab allocator should allocate all memory
up front (if true), or allocate memory in chunks as it is needed (if false)
*/
void slabs_init(const size_t limit, const double factor, const bool prealloc);

其中limit表示memcached最大使用内存;factor表示slab中chunk size的增长因子,slab中chunk size的大小等于前一个slab的chunk size乘以factor;

memcached.c中main函数调用slabs_init函数:

1
slabs_init(settings.maxbytes, settings.factor, preallocate);

其中settings.maxbytes默认值为64M,启动memcached使用选项-m设置;settings.factor默认为1.25,启动memcached时候使用-f设置;preallocate指的是启动memcached的时候默认为每种类型slab预先分配一个page的内存,默认是false;

1
2
3
4
5
settings.maxbytes = 64 * 1024 * 1024; /* default is 64MB */
...
settings.factor = 1.25;
...
preallocate = false

slabs_init函数实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* Determines the chunk sizes and initializes the slab class descriptors
* accordingly.
*/
void slabs_init(const size_t limit, const double factor, const bool prealloc) {
int i = POWER_SMALLEST - 1;
//真实占用大小=对象大小+48
unsigned int size = sizeof(item) + settings.chunk_size; mem_limit = limit; //开启预分配,则首先将limit大小(默认64M)的内存全部申请
if (prealloc) {
/* Allocate everything in a big chunk with malloc */
mem_base = malloc(mem_limit);
if (mem_base != NULL) {
mem_current = mem_base;
mem_avail = mem_limit;
} else {
fprintf(stderr, "Warning: Failed to allocate requested memory in"
" one large chunk.\nWill allocate in smaller chunks\n");
}
} //清空所有的slab
memset(slabclass, 0, sizeof(slabclass)); while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {
/* Make sure items are always n-byte aligned */
if (size % CHUNK_ALIGN_BYTES)
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); slabclass[i].size = size;
slabclass[i].perslab = settings.item_size_max / slabclass[i].size;
size *= factor;
if (settings.verbose > 1) {
fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
i, slabclass[i].size, slabclass[i].perslab);
}
} //最大chunksize的一个slab,chunksize为settings.item_size_max(默认1M)
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);
}
} //开启了预分配,则为每种slab都分配一个page的空间
if (prealloc) {
slabs_preallocate(power_largest);
}
}

其中settings.chunk_size默认为48:

settings.chunk_size = 48;         /* space for a modest key and value */

POWER_LARGEST指slab种类的最大值,默认只为200,在memcached.c中设置

#define POWER_LARGEST  200

settings.item_size_max就是每个page的大小,默认1M,在memcached.c中初始化:

settings.item_size_max = 1024 * 1024; /* The famous 1MB upper limit. */

默认不开启预分配,因为很多时候Memcached只存储一种类型的数据(即其大小相对比较固定),这时候其他类型的预分配的slab空间就会浪费.

预分配的逻辑就是从最小的slab开始,为每类slab分配一个Page大小的空间(空间不足时停止分配):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void slabs_preallocate (const unsigned int maxslabs) {
int i;
unsigned int prealloc = 0; /* pre-allocate a 1MB slab in every size class so people don't get
confused by non-intuitive "SERVER_ERROR out of memory"
messages. this is the most common question on the mailing
list. if you really don't want this, you can rebuild without
these three lines. */ for (i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {
if (++prealloc > maxslabs)
return;
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);
}
} }

do_slabs_newslab的工作就是为某一个slab分配空间,并将空间划分乘固定大小的chunk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int do_slabs_newslab(const unsigned int id) {
slabclass_t *p = &slabclass[id];
int len = settings.slab_reassign ? settings.item_size_max
: p->size * p->perslab;
char *ptr; if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) ||
(grow_slab_list(id) == 0) ||
((ptr = memory_allocate((size_t)len)) == 0)) { //申请内存 MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
return 0;
} memset(ptr, 0, (size_t)len);
//将内存划分乘chunk
split_slab_page_into_freelist(ptr, id); //维护slab链表
p->slab_list[p->slabs++] = ptr;
mem_malloced += len;
MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id); return 1;
}

split_slab_page_into_freelist的主要控制就是Page划分乘chunk并清空:

1
2
3
4
5
6
7
8
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);
ptr += p->size;
}
}

memcached的内存分配策略就是:按slab需求分配page,各slab按需使用chunk存储.

按需分配的意思就是某一类slab没有对象可存,就不会分配(非preallocate模式),某类slab存储对象很多,就会分配多个slab形成链表.

这里有几个特点要注意:

1.Memcached分配出去的page不会被回收或者重新分配;
2.Memcached申请的内存不会被释放;
3.slab空闲的chunk不会借给任何其他slab使用(新版本memcached有slab_reassign,slab_automove的功能);

slab内存结构图,二维数组链表:

4.往Slab中缓存记录

memcached根据收到的数据的大小,选择最适合数据大小的slab. memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk, 然后将数据缓存于其中.

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* Figures out which slab class (chunk size) is required to store an item of
* a given size.
*
* Given object size, return id to use when allocating/freeing memory for object
* 0 means error: can't store such a large object
*/
unsigned int slabs_clsid(const size_t size) {
int res = POWER_SMALLEST; //最小slab编号 if (size == 0)
return 0;
while (size > slabclass[res].size)
if (res++ == power_largest) /* won't fit in the biggest slab */
return 0;
return res;
}

参数是待存储对象的大小,根据这个大小,从最小的Chunk Size开始查找,找到第一个(即最小的)能放下size大小的对象的Chunk.找不到(size大于最大的Chunk Size)返回0(这就是为什么slab class从1开始而不是从0开始).

如果某个Slab没有剩余的Chunk了,系统便会给这个Slab分配一个新的Page以供使用,如果没有Page可用,系统就会触发LRU机制,通过删除冷数据来为新数据腾出空间,这里有一点需要注意的是:LRU不是全局的,而是针对Slab而言的.

slab内存分配示例:

5.Slab Allocator的缺点

由于Slab Allocator分配的是特定长度的内存,因此无法有效利用分配的内存。 例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了。

6.Memcached减少内存浪费

4.1:调整growth factor

(1).估算我们item的大小
key键长+suffix+value值长+结构大小(48字节)
(2).逐步调整growth factor,使得某个slab的大小和我们的item大小接近(必须大于我们item的大小)

7.过期数据

(1).LRU过期策略;
(2).在slab级别上执行LRU策略;
(3).查看是否过去是在get的时候,即懒惰(lazy)检查;

8.memcached-tool脚本

memcached-tool脚本可以方便地获得slab的使用情况 (它将memcached的返回值整理成容易阅读的格式),可以从下面的地址获得脚本: http://www.netingcn.com/demo/memcached-tool.zip

使用方法也极其简单:

1
perl memcached-tool server_ip:prot option

比如:

1
2
3
4
5
perl memcached-tool 10.0.0.5:11211 display    # shows slabs
perl memcached-tool 10.0.0.5:11211 # same. (default is display)
perl memcached-tool 10.0.0.5:11211 stats # shows general stats
perl memcached-tool 10.0.0.5:11211 move 7 9 # takes 1MB slab from class #7
# to class #9.

输出示例:

1
2
3
4
#  Item_Size   Max_age  1MB_pages Count   Full?
1 104 B 1394292 s 1215 12249628 yes
2 136 B 1456795 s 52 400919 yes
...

各列的含义为:

#           slab class编号
Item_Size Chunk大小
Max_age LRU内最旧的记录的生存时间
1MB_pages 分配给Slab的页数
Count Slab内的记录数
Full? Slab内是否含有空闲chunk

Memcached内存存储的更多相关文章

  1. memcached全面剖析–2. 理解memcached的内存存储

    Slab Allocation机制:整理内存以便重复使用 最近的memcached默认情况下采用了名为Slab Allocator的机制分配.管理内存. 在该机制出现以前,内存的分配是通过对所有记录简 ...

  2. memcached全面剖析--2.理解memcached的内存存储

    下面是<memcached全面剖析>的第二部分. 发表日:2008/7/9 作者:前坂徹(Toru Maesaka) 原文链接:http://gihyo.jp/dev/feature/01 ...

  3. memcached 内存初始化与key-value存储

    本次笔记未涉及到slab的动态重新平衡分配 /**首先介绍一下一个跟内存相关的非常重要的概念,内存块类型数据结构:*/ typedef struct { unsigned int size; /* c ...

  4. Memcached内存分配优化及使用问题

    前几天做了个Memcached的思考,并测试了一些数据,是关于如何提高Memcached内存使用率的问题.在启动memcached的时候可以加-f参数和-n参数.-f指定各slab里面chunk大小的 ...

  5. memcached内存管理机制[未整理]

    memcached默认采用的是Slab Allocator的机制分配管理内存的,在此之前,内存的分配是通过对所有的记录简单地进行malloc和free来进行的,但这种方式容易造成很多内存碎片,加重操作 ...

  6. memcached内存分配机制

    memcached的内存分配没有用到c语言中自带的malloc函数,因为这个函数分配内存的时候效率很低,对于这种要求快速响应,对效率要求非常高的缓存软件来说非常不合适. memcached用的是自己的 ...

  7. Memcached内存缓存技术

    Memcached是什么,有什么作用? Memcached是一个开源的.高性能的内存缓存软件,从名称上看Mem就是内存的意思,而Cache就是缓存的意思. Memcached通过在事先规划好的内存空间 ...

  8. 03 Memcached内存分布机制

    一:Memcached 内存分布机制 (1)把内存分配成slab 和不同大小的chunk.在跟据键值的大小在选择对应的chunk.这样可以减少最小的内存浪费,内存浪费是不可避免的.[把内存划分成若干个 ...

  9. Redis内存存储结构分析

    1 Redis 内存存储结构 本文是基于 Redis-v2.2.4 版本进行分析. 1.1 Redis 内存存储总体结构 Redis 是支持多key-value数据库(表)的,并用 RedisDb 来 ...

随机推荐

  1. Java-调用抽象类中指定参数的构造方法

    abstract class person {  private String name;  private int age;  public person(String name,int age) ...

  2. DEV gridcontrol 设置行数据超宽换行

    第一 在main > columns 的 某个gridcolumn下的 columnEdit为新建为 repositoryitemmemoedit 第二 repositor下的optionvie ...

  3. 基于内容的图片检索CBIR(Content Based Image Retrieval)简介

    传统的图像检索过程,先通过人工对图像进行文字标注,再利用关键字来检索图像,这种依据图像描述的字符匹配程度提供检索结果的方法,简称“以字找图”,既耗时又主观多义.基于内容的图像检索客服“以字找图”方式的 ...

  4. thinkpad x230i U盘启动

    现在的thinkpad的笔记本真麻烦,设置个U盘启动都不好使,网上找了好多都不管用,后来打电话问的售后电话才搞定,具体步骤如下: 按F1进bios的 [Security]中最下面Secure Boot ...

  5. ASP.NET Zero--5.配置权限

    修改角色的时候,会有一份权限列表,可以给这个角色分配哪些权限,那如何添加一个新权限呢?   这里以添加一个“测试”的权限为例 1.打开AppPermissions.cs [..\MyCompanyNa ...

  6. Python第一天——入门Python(2)字符串的简单操作

    数据的操作 字符串的一些常用操作: 1 1 #!/usr/bin/env python 2 # #coding=utf-8 3 # 4 # test='hello world' 5 # print(t ...

  7. GOPS2017全球运维大会深圳站 出席嘉宾盘点!

    去年,GOPS全球运维大会在深圳出发,当时门票提前几周收盘,2017年,承载着运维人的期望,GOPS全球运维大会再次来到了深圳.第六届GOPS2017全球运维大会深圳站(本次)将于2017年4月21日 ...

  8. Mutex的使用方法以及封装的AutoLock介绍(转载)

    Mutex-互斥类 互斥类-MutexMutex是互斥类,用于多线程访问同一个资源的时候,保证一次只有一个线程能访问该资源.在<Windows核心编程>①一书中,对于这种互斥访问有一个很形 ...

  9. [Q]手动加载菜单方法

    一般情况下,安装程序会自动安装依云软件菜单,但可能由于某些原因未能自动安装的话,您可以手动加载菜单,步骤如下: 在AoutCAD命令行输入"CUILOAD",会弹出"加载 ...

  10. 关于Container With Most Water的求解

    Container With Most Water 哎,最近心情烦躁,想在leetcode找找感觉,就看到了这题. 然而,看了题目半天,硬是没看懂,于是乎就百度了下,怕看到解题方法,就略看了下摘要,以 ...