早就听说过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. EasyMock入门

    这是一个JavaProject,有关EasyMock用法详见本文测试用例 首先是用到的实体类User.java package com.jadyer.model; public class User ...

  2. Laravel5.3 流程粗粒度分析之bootstrap

    从laravel入口文件index里面外面不难定位到Illuminate\Foundation\Http\Kernel的handle方法,同样也不难发现handle方法的核心是 $response = ...

  3. [ios2] 利用钥匙串,在应用里保存用户密码的方法 【转】

    想在应用里保存帐号.密码等信息的话,直接存到 plist 里显然是不负责任的,自己折腾加密既麻烦又不能保证不被反编译破解.实际上苹果 iOS 和 Mac OS X 系统自带了一套敏感信息保存方案:&q ...

  4. Linux学习笔记(一):常用命令(2)

    3.帮助命令 A,帮助命令:man    B,其他帮助命令 3.1,格式:man [命令名] 查看命令拥有哪个级别的帮助:                                       ...

  5. Android 动态监听网络 断网重连

    需求: 网络连接断开 弹出popupwindow 当前网络连接断开 网络恢复时popupwindow 消失重新请求网络. 需求描述完毕 上一张帅图 思路:广播 发送及时消息 断网flag  popup ...

  6. Python第一天 安装 shell 文件

    Python第一天  安装  shell  文件 python里面一切都是对象 object 代码缩进:建议用四个空格来缩进,不要用tab键 安装 Linux自带python,windows需要下载m ...

  7. Java定时器Timer简述

    概述 主要用于Java线程里指定时间或周期运行任务.Timer是线程安全的,但不提供实时性(real-time)保证. 构造函数 Timer() 默认构造函数. Timer(boolean) 指定关联 ...

  8. 前端MVC学习笔记(三)——AngularJS服务、路由、内置API、jQueryLite

    一.服务 AngularJS功能最基本的组件之一是服务(Service).服务为你的应用提供基于任务的功能.服务可以被视为重复使用的执行一个或多个相关任务的代码块. AngularJS服务是单例对象, ...

  9. 自定义报表开发(HTML/XML)

    定义报表执行的包或存储过程: --创建包头 CREATE OR REPLACE PACKAGE XXPLM_AARONTEST001 IS PROCEDURE MAIN(errbuf OUT VARC ...

  10. android工具类常用方法

    1.获取手机唯一识别号 添加AndroidManifest.xml权限 <uses-permission android:name="android.permission.READ_P ...