博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
memcached源码剖析系列之内存存储机制(三)
阅读量:5166 次
发布时间:2019-06-13

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

在memcached内存存储机制剖析的中,已分析过memcached的内存管理器初始化机制及slab的管理分配机制。接下来我们就来探讨下对象item的分配管理及LRU机制。

1 item关键数据结构

(1)item结构体原型

typedef struct _stritem {    struct _stritem *next;    struct _stritem *prev;    struct _stritem *h_next;    /* hash chain next */    rel_time_t      time;       /* least recent access */    rel_time_t      exptime;    /* expire time */    int             nbytes;     /* size of data */    unsigned short  refcount;    uint8_t         nsuffix;    /* length of flags-and-length string */    uint8_t         it_flags;   /* ITEM_* above */    uint8_t         slabs_clsid;/* which slab class we're in */    uint8_t         nkey;       /* key length, w/terminating null and padding */    /* this odd type prevents type-punning issues when we do     * the little shuffle to save space when not using CAS. */    union {        uint64_t cas;        char end;    } data[];    /* if it_flags & ITEM_CAS we have 8 bytes CAS */    /* then null-terminated key */    /* then " flags length\r\n" (no terminating null) */    /* then data with terminating \r\n (no terminating null; it's binary!) */} item;

(2)全局数组

static item *heads[LARGEST_ID];

保存各个slab class所对应的item链表的表头。

static item *tails[LARGEST_ID];

保存各个slab class所对应的item链表的表尾。

static unsigned int sizes[LARGEST_ID];

保存各个slab class所对应的items数目。

2 item分配机制的函数实现

(1)LRU机制

  在前面的分析中已介绍过,memcached不会释放已分配的内存。记录超时后,客户端就无法再看见该记录(invisible,透明),其存储空间即可重复使用。Memcached采用的是Lazy Expiration,即memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期。这种技术被称为lazy(惰性)expiration。因此,memcached不会在过期监视上耗费CPU时间。

  memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况,此时就要使用名为 Least Recently Used(LRU)机制来分配空间,即删除“最近最少使用”的记录。

(2)函数实现

Item的分配在函数do_item_alloc()中实现,函数原型为:

item *do_item_alloc(char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes);

参数含义:

* key       - The key

* nkey     - The length of the key

* flags     - key flags

*exptime  –item expired time

* nbytes  - Number of bytes to hold value and addition CRLF terminator

 函数的具体实现如下,由于do_item_alloc()太长,这里只贴出部分关键代码:

item *do_item_alloc(char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes) {    uint8_t nsuffix;    item *it = NULL;    char suffix[40];    size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix);         //settings.use_cas:?cas"是一个存储检查操作,用来检查脏数据的存操作。         if (settings.use_cas) {        ntotal += sizeof(uint64_t);    }    unsigned int id = slabs_clsid(ntotal);//获得slabclass索引值    if (id == 0)        return 0;    /* do a quick check if we have any expired items in the tail.. */    int tries = 50;    item *search;         //在item链表中遍历过期item    for (search = tails[id];         tries > 0 && search != NULL;         tries--, search=search->prev) {        if (search->refcount == 0 &&            (search->exptime != 0 && search->exptime < current_time)) {           …….}    }         //没有过期数据时,采用LRU算法,淘汰老数据    if (it == NULL && (it = slabs_alloc(ntotal, id)) == NULL) {        /*        ** Could not find an expired item at the tail, and memory allocation        ** failed. Try to evict some items!        */        tries = 50;        /* If requested to not push old items out of cache when memory runs out,         * we're out of luck at this point...         */                   // 当内存存满时,是否淘汰老数据。默认为真。可用-M修改为否。此时内容耗尽时,新插入数据时将返回失败。          ……        it = slabs_alloc(ntotal, id); //返回新分配的slab的第一个item                   //item分配失败,做最后一次努力        if (it == 0) {            itemstats[id].outofmemory++;            /* Last ditch effort. There is a very rare bug which causes             * refcount leaks. We've fixed most of them, but it still happens,             * and it may happen in the future.             * We can reasonably assume no item can stay locked for more than             * three hours, so if we find one in the tail which is that old,             * free it anyway.             */            tries = 50;            for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) {                                     //search->time:最近一次访问的时间                                     if (search->refcount != 0 && search->time + TAIL_REPAIR_TIME < current_time) {                    ……            }            it = slabs_alloc(ntotal, id);            if (it == 0) {                return NULL;            }        }    }    …….    it->next = it->prev = it->h_next = 0;    it->refcount = 1;     /* the caller will have a reference */    DEBUG_REFCNT(it, '*');    it->it_flags = settings.use_cas ? ITEM_CAS : 0;    it->nkey = nkey;    it->nbytes = nbytes;         //零长数组    memcpy(ITEM_key(it), key, nkey);    it->exptime = exptime;    memcpy(ITEM_suffix(it), suffix, (size_t)nsuffix);    it->nsuffix = nsuffix;    return it;}

  该函数首先调用item_make_header()函数计算出该item的总长度,如果脏数据检查标志设置的话,添加sizeof(uint64_t)的长度,以便从slabclass获得索引值(使用slabs_clsid()函数返回)。接着从后往前遍历item链表,注意全局数组heads[LARGEST_ID]tails[LARGEST_ID]保存了slabclass对应Id的链表头和表尾。

  从源码中我们可以看出,有三次遍历循环,每次最大遍历次数为50(tries表示),//在item链表中遍历过期item,如果某节点的item设置了过期时间并且该item已过期,则回收该item,,调用do_item_unlink()把它从链表中取出来。

  若向前查找50次都没有找到过期的item,则调用slabs_alloc()分配内存,如果alloc失败,接着从链表尾开始向前找出一些没有人用的refcount=0的item,调用do_item_unlink(),再用slabs_alloc()分配内存,如果还失败,只能从链表中删除一些正在引用但过期时间小于current_time – CURRENT_REPAIR_TIME的节点,这个尝试又从尾向前尝试50次,OK,再做最后一次尝试再去slabs_alloc()分配内存,如果这次还是失败,那就彻底放弃了,内存分配失败。

  Memcached的内存管理方式是非常精巧和高效的,它很大程度上减少了直接alloc系统内存的次数,降低函数开销和内存碎片产生几率,虽然这种方式会造成一些冗余浪费,但是这种浪费在大型系统应用中是微不足道的。

转载于:https://www.cnblogs.com/moonlove/archive/2012/05/21/2511768.html

你可能感兴趣的文章
代理模式
查看>>
Confluence 6 系统运行信息中的 JVM 内存使用情况
查看>>
Confluence 6 升级以后
查看>>
用JS实现版面拖拽效果
查看>>
二丶CSS
查看>>
《avascript 高级程序设计(第三版)》 ---第二章 在HTML中使用Javascript
查看>>
JS一些概念知识及参考链接
查看>>
TCP/IP协议原理与应用笔记24:网际协议(IP)之 IP协议的简介
查看>>
SAP HANA开发中常见问题- 基于SAP HANA平台的多团队产品研发
查看>>
游戏中的心理学(一):认知失调有前提条件
查看>>
WHAT I READ FOR DEEP-LEARNING
查看>>
【Ruby】Ruby在Windows上的安装
查看>>
Objective C 总结(十一):KVC
查看>>
BZOJ 3747 洛谷 3582 [POI2015]Kinoman
查看>>
vue实战(7):完整开发登录页面(一)
查看>>
Visual Studio自定义模板(二)
查看>>
【Mood-20】滴滤咖啡做法 IT工程师加班必备 更健康的coffee 项目经理加班密鉴
查看>>
读《构建之法-软件工程》第四章有感
查看>>
使用 Printf via SWO/SWV 输出调试信息
查看>>
.net 分布式架构之分布式锁实现(转)
查看>>