博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
memcached 内存初始化与key-value存储
阅读量:6089 次
发布时间:2019-06-20

本文共 11085 字,大约阅读时间需要 36 分钟。

本次笔记未涉及到slab的动态重新平衡分配
/**首先介绍一下一个跟内存相关的非常重要的概念,内存块类型数据结构:*/typedef struct {    unsigned int size;      /* chunk的大小 sizes of items */    unsigned int perslab;   /* 整个slab有chunk的数量 how many items per slab */     void *slots;            /* item串起来的链表 list of item ptrs */    unsigned int sl_curr;   /* 当前slabclass 有多少可用的 chunk total free items in list */     unsigned int slabs;     /* 之前已经分配这种类型slab的数量 how many slabs were allocated for this class */     void **slab_list;       /* 同类型的slab构成一个数组 array of slab pointers */    unsigned int list_size; /* 已经分配空间的slab_list的数组长度,实际使用数量为slabs  size of prev array */     unsigned int killing;   /* index+1 of dying slab, or zero if none */    size_t requested;       /* 已经被申请使用的字节数 The number of requested bytes */} slabclass_t; static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES]; /** 全局变量存储了系统所有的slab块类型*/ static size_t mem_limit = 0;static size_t mem_malloced = 0;static int power_largest; /** 记录有多少种slab块类型*/ static void *mem_base = NULL; /** 指向最初向系统申请的那大片空间,默认64M*/static void *mem_current = NULL; /** 指向当前系统申请大片空间的首地址*/static size_t mem_avail = 0;    /** 统计系统申请的大片空间还有多少字节可以用*/ /* * 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 */ /** * 计算出哪一个slab class 适合存储一个size长度的item, 返回0 表示无法存储这个item */unsigned int slabs_clsid(const size_t size) {    int res = POWER_SMALLEST;     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;} /** * Determines the chunk sizes and initializes the slab class descriptors * accordingly. */ /**  * 确定chunk的大小并初始化slab的类型  * @limit 总的申请的内存块的大小 默认为 64M  * @相邻slab 之间的chunck大小进阶因子,比如第一种slab_class1的chunk为48 bytes,第二种slab_class2的chunk大小就为48*factor,第三种48*factor^2...  */  /**   * slabs_init 主要逻辑,初始化内存块类型   * 首先理解一下memcached内存的划分逻辑,memcached在启动时会一次性向系统申请一大块内存,根据启动参数决定申请的大小,   * 默认为64M,之后便不再会向系统申请内存,而是在已申请的大块内存数组里面划分,少了系统调用,所以速度提高了。   * 获得一大块内存之后,memcached开始基于这块内存进行使用以及管理,首先是定义了内存块的概念,每一块默认大小为1M,   * 之后再对这一块内存进行划分为每一个chunk,每一个1M块里面的chunks都是一样大小的,不同的1M块包含的chunks的大小会有不同,   * 因此便有了块的类型,memcached 用一个数组来存储所有的类型slabclass, 默认类型不超过200种。那么memcached其实是用来存储   * key-value结构的,所以作为内存粒度最小的chunk自然就是用来存key-value了,将key-value进行包装一下用一个结构来表示,名称   * 为item,item就包含了一对key-value的信息,因此item所占的空间即长度就决定了需要用哪一种chunk来存储,不同的key-value所对应   * 的item长度自然是不一样,因而就需要不同大小的chunk来存储,也就是不同类型的slabclass.因而memcached引入了一个factor参数,   * 这个factor参数就是用来生成不同的slabclass,比如slabcalss[0]的chunk的大小为48bytes,则slabclass[1]的chunk大小则为48*factor bytes,   * factoe默认为1.25,所以相同大小的两块1M内存,slabclass[1]划分出来的chunk数量就比slabclass[0]要少,但是每个chunk比较大些,大点的   * item也能存得下,但其实一切对内存进行划分的行为都会造成一定量的内存浪费,但是若便于管理内存,利大于弊也是能接受一点浪费的。   */void slabs_init(const size_t limit, const double factor, const bool prealloc) {    int i = POWER_SMALLEST - 1;    unsigned int size = sizeof(item) + settings.chunk_size; /** setting.chunk_size为默认的大小*/     mem_limit = limit;     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");        }    }    /** 初始化slabclass数组*/    memset(slabclass, 0, sizeof(slabclass));     /**     * 初始化内存块的类型,默认最多200种     * setting.items_size_max就表示了一块内存默认大小为1M     */    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.size 表示适合存储的item的大小, 这里似乎忽略了chunk的概念,其实chunk就是用来存item的*/        slabclass[i].size = size;        /** slabclass.perslab 表示这块1M内存会有多少个item*/        slabclass[i].perslab = settings.item_size_max / slabclass[i].size;         /** 下一种slabclass chunk的大小*/        size *= factor;        ...        }    }     /** 记录最终有多少种slabclass*/    power_largest = i;     /** 最后一种item的大小就是块的大小了,也就是item可能就是1M大小,一个1M内存块就存了1个item*/    slabclass[power_largest].size = settings.item_size_max;    slabclass[power_largest].perslab = 1;    ...     /* 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) {        slabs_preallocate(power_largest);    }} /** * 系统启动时预分配内存块的主要逻辑 * @spec maxslabs 表示有多少种slabclass * * 预分配的效果就是为每一种slabclass初始化一个slab块,并将slab块划分为对应大小的chunks * 预分配不是系统启动默认的,因为有可能分配的slab始终不会用到,浪费了存储空间,但是被用 * 到时由于已经分配好,直接可以使用,系统效率会高点,具体看存入的item实际大小的情况了 */ 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.  */    /**     * 为每一种slabclass预分配1个1M的块,并将其划分为对应大小的的chunks     */    for (i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {        if (++prealloc > maxslabs)            return;        if (do_slabs_newslab(i) == 0) { /** 具体看do_slabs_newslab()注释*/            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);        }    } }/** 登记到某一种类型的slab_list数组中*/static int grow_slab_list (const unsigned int id) {    slabclass_t *p = &slabclass[id];    /** 如果数组没有空间再放多一个slab,就需要扩容数组*/    if (p->slabs == p->list_size) {        size_t new_size =  (p->list_size != 0) ? p->list_size * 2 : 16;        void *new_list = realloc(p->slab_list, new_size * sizeof(void *));        if (new_list == 0) return 0;        p->list_size = new_size;        p->slab_list = new_list;    }    return 1;} /** * 函数名描述函数功能:将一个slab page 拆分(成小块)添加到可用链表-_- * 将(申请到的)内存首地址作为参数,根据id找到所属的slabclass类型, * 找到对应的chunk大小,将每一个chunk添加到slabclass的slot链表,具体由do_slabs_free()操作 */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; /** 指针移动size的长度*/    }} /** 申请一个slab, id 表示所属的class*/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;     /** 先判断可申请长度是否满足,之后判断是否需要扩容slabclass中记录申请的slab的数组,最后申请内存*/    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);    split_slab_page_into_freelist(ptr, id); /** 将这一页的内存块瓜分成各个chunk小块,添加到slabclass可用slots链表*/     /**在slabclass中的数组记录多分配一个slab*/    p->slab_list[p->slabs++] = ptr;    mem_malloced += len;    MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);     return 1;} /*@null@*//** 分配一个slab块 size 表示item的大小,id 表示slabclass id*/static void *do_slabs_alloc(const size_t size, unsigned int id) {    slabclass_t *p;    void *ret = NULL;    item *it = NULL;     if (id < POWER_SMALLEST || id > power_largest) {        MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);        return NULL;    }     p = &slabclass[id];    assert(p->sl_curr == 0 || ((item *)p->slots)->slabs_clsid == 0);     /* fail unless we have space at the end of a recently allocated page,       we have something on our freelist, or we could allocate a new page */    /* 先确保slabclass有咩有可用的slot, 再去申请新的slab, 申请也有可能失败*/    if (! (p->sl_curr != 0 || do_slabs_newslab(id) != 0)) {        /* We don't have more memory available */        ret = NULL;    } else if (p->sl_curr != 0) { /** 在slot有可用空间的时候申请失败则直接从slot那里取一个*/        /* return off our freelist */        it = (item *)p->slots;        p->slots = it->next;        if (it->next) it->next->prev = 0;        p->sl_curr--; /**更新可用的数量*/        ret = (void *)it;    } if (ret) {        p->requested += size; /** 统计已经被使用的字节数量*/        MEMCACHED_SLABS_ALLOCATE(size, id, p->size, ret);    } else {        MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);    }    return ret;} /** 按照函数名理解,释放一个slab块 * ptr 表示首地址,size表示item已使用字节大小,id是所属的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);    if (id < POWER_SMALLEST || id > power_largest)        return;     MEMCACHED_SLABS_FREE(size, id, ptr);    p = &slabclass[id];     it = (item *)ptr; /** 转化(初始化)成item类型指针*/    it->it_flags |= ITEM_SLABBED;    it->prev = 0;    it->next = p->slots; /** 串在 slabcalss的slots链表头部*/    if (it->next) it->next->prev = it; /*下一个节点不为空时,将下一个节点的prev指向it*/    p->slots = it; /** 更新slots */     p->sl_curr++; /**更新当前可用的chunk数量, slot代表了链表的名称, sl_curr表示可用的数量*/    p->requested -= size; /** 更新已经被请求使用了的字节数,多了一个相当于requested 减掉一个*/    return;} /** 从已经申请的内存块中申请slab内存块*/static void *memory_allocate(size_t size) {    void *ret;    if (mem_base == NULL) { /**全局变量指向系统申请时的内存块*/        /* We are not using a preallocated large memory chunk */        ret = malloc(size); /** 表示尚未向系统申请过,这里就直接调用系统调用申请*/    } else {        ret = mem_current; /** mem_current同样是一个全局变量,指向mem_base申请之后剩余的可用内存*/         if (size > mem_avail) { /** 判断申请是不是过大*/            return NULL;        }        /* mem_current pointer _must_ be aligned!!! */        if (size % CHUNK_ALIGN_BYTES) { /** 官方注释这里必须内存对齐*/            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);        }        mem_current = ((char*)mem_current) + size; /** mem_current向前移动*/        if (size < mem_avail) {            mem_avail -= size; /* 更新全局统计量*/        } else {            mem_avail = 0;        }    }    return ret;} /*分配slab块的对外接口,主要是加了锁,基本上do_xxx()函数都是不加锁的*/void *slabs_alloc(size_t size, unsigned int id) {    void *ret;    pthread_mutex_lock(&slabs_lock);    ret = do_slabs_alloc(size, id);    pthread_mutex_unlock(&slabs_lock);    return ret;}... /**更新已经slabclass使用的字节数*/void slabs_adjust_mem_requested(unsigned int id, size_t old, size_t ntotal){    pthread_mutex_lock(&slabs_lock);    slabclass_t *p;    if (id < POWER_SMALLEST || id > power_largest) {        fprintf(stderr, "Internal error! Invalid slab class\n");        abort();    }    p = &slabclass[id];    p->requested = p->requested - old + ntotal;    pthread_mutex_unlock(&slabs_lock);}

  以上注释仅代表个人见解,若有误导,请见谅!

转载于:https://www.cnblogs.com/bicowang/p/3823511.html

你可能感兴趣的文章
记录使用Vue相关API开发项目时遇到的问题难点整理(不定时更新)
查看>>
《Java8实战》-第五章读书笔记(使用流Stream-02)
查看>>
vue轮播图插件之vue-awesome-swiper
查看>>
Cabloy.js:基于EggBorn.js开发的一款顶级Javascript全栈业务开发框架
查看>>
HTTP相关知识汇总
查看>>
使用wagon-maven-plugin部署Java项目到远程服务器
查看>>
新书推荐 |《PostgreSQL实战》出版(提供样章下载)
查看>>
JavaScript/数据类型/function/closure闭包
查看>>
30个免费资源:涵盖机器学习、深度学习、NLP及自动驾驶
查看>>
读zent源码库之Dialog组件实现
查看>>
express中间层搭建前端项目3
查看>>
【刷算法】我知道的所有类似斐波那契数列的问题
查看>>
centos下安装JAVA开发工具(3)------Mysql
查看>>
Docker Swarm的前世今生
查看>>
从0开始构建自己的前端知识体系-不要对"=="说不
查看>>
Python 从零开始爬虫(七)——实战:网易云音乐评论爬取(附加密算法)
查看>>
Java设计模式--单例模式
查看>>
JS 实现文字滚动显示
查看>>
php实现依赖注入(DI)和控制反转(IOC)
查看>>
【EASYDOM系列教程】之遍历节点
查看>>