分布式缓存系统 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中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...
随机推荐
- BZOJ3669/UOJ3 魔法森林(LCT)
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...
- RabbitMQ初体验
这里官方使用的Pom是4.0.2版本 <dependencies> <dependency> <groupId>com.rabbitmq</groupId&g ...
- qtjambi_ZC
loadJambiJniLibrary --> loadLibrary --> loadNativeLibrary --> loadLibrary_helper class QApp ...
- ZC_操作_not敲代码
1.javah 命令(路径为 项目的bin目录下),例如 : F:\ZC_Code_E\workspace__MyEclipse2013\JNIjw01\bin>javah jniZ.JNIjw ...
- Android GridView 行间距过大(一页一行)
1. gridView.xml中 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&quo ...
- WSL安装xfce4
参考:https://github.com/Microsoft/WSL/issues/637 安装组件 1. win10 上安装 Xming https://sourceforge.net/proje ...
- nmap基本使用方法
nmap基本使用方法 我自己的使用: nmap 149.28.72.184 Starting Nmap 7.70 ( https://nmap.org ) at 2018-06-15 16:53 CS ...
- cassandra框架模型之二——存储机制 CommitLog MemTable SSTable
四.副本存储 Cassandra不像HBase是基于HDFS的分布式存储,它的数据是存在每个节点的本地文件系统中. Cassandra有三种副本配置策略: 1) SimpleStrategy (Rac ...
- springboot项目执行controller方法时进入慢的问题
今天在部署springboot项目到阿里云时,出现登录方法执行特别慢的问题.刚开始以为是卡死了,等了3,4分钟才进去,最后会出现如下信息: 2018-01-28 15:38:36.958 INFO 4 ...
- Python基础学习(第3天)
第6课 循环 1.for 元素 in 序列: statement Python的新函数range():新建一个数列,都是整数,从0开始,下一个元素比上一个元素大1,一直到数列的上限(不包括上限).PS ...