memcached를 소스 코드 분석 II -lru

  이전 기사에서 memcached를 메모리 관리 기법 도입 슬래브, 당신은 슬래브를 어떻게 사용되는 데이터를 캐시해야합니까?

1. 분산 메모리 캐시 개체 항목

  memcached를에서는 항목 A에 설명 된 구조를 사용하여 각 캐시 목적은 다음 항목 디스크립터 및 대응하는 데이터가 메모리 관리 슬라브에 저장된다. 캐시 객체 저장소 청크 할당 slabclass_t 어레이있어서 slabclass_t에 적절한 사이즈를 선택한다.

  PS : slabclass_t 어레이 기반의 인덱스, 인덱스 값이 증가함에 따라, 청크 사이즈 slabclass_t도 증가된다. 따라서 최소 청크 크기는 프리 청크 캐시 객체 slabclass_t를 저장하는 캐시 된 객체를 수용하기 위해 발견 될 수있을 때까지 개체 캐시 크기의 청크 사이즈 slabclass_t 각각 비교 인덱스 1부터.

  도 1-1은 하나의 덩어리로도 분산 캐시 객체의 경우를 나타낸다. 1-2 캐시 객체가 복수 청크에 분포하는 경우를 나타낸다

도 1-1 하나의 데이터 청크

 

도 1-1에 다중 데이터 청크

도 1-1의를 사용하여, 즉, 하나의 청크에 데이터를 저장하거나, 1-2는도 1의 구조를 사용하는 경우 데이터는 적합한 slabclass_t을 발견 할 수있는 경우. CAS 구조는, 플래그는 상황에 따라 사용할 수 없다. 청크의 크기가 고정되어 있기 때문에, 캐시 데이터의 크기가 고정되지 않고, 대상물은 작은 slabclass_t하지만 않는 메모리 부 가능한 기억 형성의 폐기물을 저장하기에 충분히 큰 버퍼에 저장한다.

  청크에 분산 된 데이터의 복수의 연관, 제 청크 저장소 항목 구조의 이중 연결리스트의 형태로되어 있지만, 하나의 청크 통합 상품에 저장되는 실제 데이터를 저장하지 않는다. 실제 구성 데이터 Item_chunk 후속 청크. item_chunk를 다음과 같이 정의 구조

/ * 항목이 실제로 다른 항목의 체크입니다 헤더. * / 
형식 정의 구조체 _strchunk {
     구조체 _strchunk * 다음;     / * 자신의 체인 내에서 포인트. * / 
    구조체 _strchunk * 이전;     / * 잠재적 헤드를 가리킬 수있다. * / 
    구조체 _stritem * 헤드;     / * 항상 소유자 덩어리를 가리키는 * / 
    INT의               크기;      / * 바이트에서 사용할 수 청크 공간 * / 
    INT               사용;      / * 청크 공간 이용 * / 
    INT              nbytes;    / * 사용. * / 
    부호없는 단기    참조 카운트;  / * 사용? * / 
    uint16_t의 it_flags;  / * ITEM_ * 상술. * / 
    uint8_t slabs_clsid; / * 위와 동일. * / 
    uint8_t orig_clsid; / * OBJ HDR 청크를 들어 slabs_clsid는 가짜입니다. * / 
    데이터 []; 
item_chunk};

  item_chunk이 orig_clsid가 보여주는 모든 slabclass_t이 it_flags이 ITEM_CHUNK로 설정되어 청크 인덱스를 나타냅니다 이전 및 다음 포인터가 양방향 연결리스트는, 헤드 포인터는 항상 청크의 첫 번째 항목 구조를 가리키는, slabs_clsid slabclass_t 인덱스를 나타냅니다 청크는 구조이다 item_chunk

2. 해시 테이블

  캐시 개체가 석판에 저장 한 후, 어떻게 데이터가 필요로하는 항목을 찾는 방법은? 캐시 항목 색인을 사용 memcached를 해시 테이블, 모든 항목은 키, 해시 테이블에서 해당 항목 포인터를 찾기 위해 키를 사용하여이있다.

 图 2-1 해시 테이블

해시 테이블은, 각각의 어레이 소자는 memcached를 단일 링크 된리스트를 사용하여, 같은 방법에 항목리스트 모두에 대해 동일한 키 해시 값으로 구성된 항목에 대한 단일 연결리스트 헤드 포인터가 가리키는 글로벌 어레이의 동적 확장이며 갈등의 해결.

항목 memcached를 구조의 정의는 다음과 같습니다,

/**
 * Structure for storing items within memcached.
 */
typedef struct _stritem {
    /* Protected by LRU locks */
    struct _stritem *next;
    struct _stritem *prev;
    /* Rest are protected by an item lock */
    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;
    uint16_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;

其中的h_next成员即用于维护hashtable中的单向链表。

3.    LRU

  LRU即least recently used,由于slabs可用的内存有限,当slabs中不再有内存可用,但又有新的对象需要缓存时,根据LRU的思想,那些很久未访问的对象后续被访问的概率也最小,因此应当释放掉那些LRU的对象,缓存新的对象。LRU策略即是用来快速寻找可以释放的LRU对象的一种方案。

  memcached中每一个slabclass[id]对应4条双向链表,该双向链表以heads[id]指向链表头,tails[id]指向链表尾,item结构中的next与prev即用于组建该双向链表。heads[id]与tails[id]构成的双向链表对应slabclass[id]的hot链表,heads[64 + id]与tails[64+id]构成的双向链表对应slabclass[id]的warm链表,依此类推,如图3-1所示。一个slabclass_t中的item分布且仅分布于对应的4条链表之间。

图3-1 LRU结构

  在memcached中定义的HOT_LRU = 0, WARM_LRU = 64, COLD_LRU = 128, TEMP_LRU = 192,利用这些宏定义可以很方便地在对应的4条链表中跳转,如heads[WARM_LRU | id]即可跳转到对应slabclass[id]的warm链表头(id小于64)。

  根据一定的策略操作item在这些链表之间移动,即可实现LRU策略。

  先来看几个相关定义:

  • ITEM_ACTIVE,存储在item的it_flags中,表示item处于active状态
  • size_bytes[LARGEST_ID],一个长度与heads链表数组相同的数组,记录了链表中所有item的字节和
  • time, 存储在item结构体中,记录了item最后一次被访问的时间, item的age =  current_time - time

  现在,来看一下表示图3-2表示的item在hot, warm, cold三类链表中的移动的关系

图3-2 item转移图

  •   age_limit是cold链表的tails节点age的一个比例
  • size_bytes > limit,意思是该item所在的链表总数据量超过了一定比例, limit是对应slabclass_t中存储的item的数据量的一个比例,slabclass_t中item的数据总量存储在requested成员中。

上述的转换规则可描述如下:

  1.    item仅在对应的4条链表间转移
  2.    hot与warm链表上的item在非active状态下如果age > limit或者size_bytes > limit,移动到cold链表
  3.   hot与cold链表上的item若处于active状态,移动到warm链表
  4.    warm链表上的item若牌active状态,移动到链表头
  5.   item移动后,处于相应链表的头部
  6.   item移动后,清除active状态

memcached中通过函数lru_pull_tail实现以上转移规则,根据以上规则,以及新加入的item总是插入到hot链表头部,就可以实现LRU策略,得到如下结果:

  • 每条链表的tail节点age最大
  • cold链表上的节点相比hot与warm链表上的节点更适合释放

上面并没有提到temp链表中的item如何处于,事实上,temp链表上的item仅做简单的超时与flushed判断,满足条件即释放,否则不做操作。

  • 超时: 用户为item设置一个超时时间,存储在item的exptime中,age > exptime即为超时。
  •  flushed: flush指令设置一个时间点settings.oldest_live或者settings.oldest_cas,item中的time < settings.oldest_live或者cas < settings.oldest_cas即为flushed。

memcached中释放item主要有3个入口:

  1. do_item_get操作,查找item时,如果item超时了或者是flushed,即释放
  2. do_item_alloc操作,如果对应slabclass_t中没有空闲chunk了,则在cold链表按照从尾到头的顺序做处理:释放超时与flushed的item,如果没有超时与flushed的item,则直接清除该item。由于链表使用LRU策略维护,因此链表最后的节点即是最适合释放的item。
  3. 线程lru_maintainer_thread,周期性地做一些工作:清除超时与flushed的item,按照图3-2转移item。

 4.部分源码函数功能说明

static void *item_crawler_thread(void *arg)

  一个crawler线程,该线程会定期的接收到任务,任务启动将会在一条链表上构造一上虚拟的item,该item从尾部逐渐移到到头部,对经过的item进行一定的处理。任务在遍历完链表或者处于一定数量的item后结束。memcached中定义了两类处理:1. 释放超时与flushed的item,通过函数crawler_expired_init, crawler_expired_eval, crawler_expired_doneclass与crawler_expired_finalize配合完成;2. 输出item的统计信息,通过crawler_metadump_eval与crawler_metadump_finalize函数配合完成。相应源码位于crawler.c中。

static void *lru_maintainer_thread(void *arg) 

lru的定期任务线程,定期执行item在链表间转移的任务,定期启动crawler任务,定期执行slabs间移动page的任务。

static int lru_maintainer_juggle(const int slabs_clsid)

被lru_maintainer任务调用,完成对应slabclass[slabs_clsid]的4条链表上的item的释放与转移工作,通过多次调用lru_pull_tail函数完成工作。

int lru_pull_tail(const int orig_id, const int cur_lru, const uint64_t total_bytes, const uint8_t flags, const rel_time_t max_age, struct lru_pull_tail_return *ret_it) 

  这是一个关键函数,它会从链表尾开始处理,释放超时与flushed的item,如果没有超时或者flushed,则按照图3-2的逻辑移动item。参数orig_id表示对应的slabclass_t索引,cur_lru表示现在处理的是hot或者warm、cold还是temp链表,total_bytes表示slabclass_t中存储的数据总量,max_age用于即图3-2中的age_limit,flags设置一些特殊操作的标记,如LRU_PULL_EVICT标记在处理cold链表并且希望无论item是否超时或者flushed时,都释放item空间时设置。

item *do_item_get(const char *key, const size_t nkey, const uint32_t hv, conn *c, const bool do_update) 

通过hv与key在hashtable中查找item,返回查找结果。如果找到了,但是item超时或者flushed,则释放item, 返回NULL;如果不需要释放并且do_update非0,会更新item的状态,设置相应的ITEM_FETCHED与ITEM_ACTIVE标记。

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

根据参数,找到合适的slabclass_t申请空闲chunk,并对返回的chunk做一些初始化设置,如设置它的slabs_clsid为对应的slabclass_t索引。调用do_item_alloc_pull完成内存申请工作。

item *do_item_alloc_pull(const size_t ntotal, const unsigned int id) 

在slabclass[id]中申请空闲chunk,如果申请失败,会尝试以LRU_PULL_EVICT标记调用lru_pull_tail,回收一些lru的item。

 

추천

출처www.cnblogs.com/yang-zd/p/11345011.html