热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

memcached源码分析二lru

在前一篇文章中介绍了memcached中的内存管理策略slab,那么需要缓存的数据是如何使用slab的呢?1.缓存对象item内存分布在memcached,每一个缓存的对象都使用一

  在前一篇文章中介绍了memcached中的内存管理策略slab,那么需要缓存的数据是如何使用slab的呢?

1.    缓存对象item内存分布

  在memcached,每一个缓存的对象都使用一个item结构体进行描述,然后再将item描述符及相应数据存储到slabs管理的内存中。缓存对象根据其大小在slabclass_t数组中选择合适的slabclass_t分配chunk进行存储。

  ps: slabclass_t数组中从索引1开始,随着索引值的增加,该slabclass_t的chunk size也随之增加。因此从索引1开始,逐一比较缓存对象大小与slabclass_t的chunk size,直至找到能容纳缓存对象的最小chunk size,缓存对象即存储到该slabclass_t的空闲chunk中。

  图1-1表示一个缓存对象分布在单个chunk中的情况,图1-2表示一个缓存对象分布在多个chunk中的情况,

技术分享图片

图1-1 数据在单个chunk中

 技术分享图片

图1-1 数据在多个chunk中

当数据可以找到合适的slabclass_t以单个chunk存储数据时,即采用图1-1的结构,否则使用图1-2的结构。结构中的cas, flags根据情况可以不使用。由于chunk的大小固定,而缓存数据大小不固定,缓存对象将被存储在足够容纳它的最小的slabclass_t中,但可能存在部分内存未使用的情况,形成内存浪费。

  分布在多个chunk中的数据以双向链表的形式进行关联,第一个chunk存储一个item结构,但不存储实际数据,以便与单个chunk中存储的item统一。后续的chunk以item_chunk结构与实际数据组成。item_chunk的结构定义如下

/* Header when an item is actually a chunk of another item. */
typedef
struct _strchunk {
struct _strchunk *next; /* points within its own chain. */
struct _strchunk *prev; /* can potentially point to the head. */
struct _stritem *head; /* always points to the owner chunk */
int size; /* available chunk space in bytes */
int used; /* chunk space used */
int nbytes; /* used. */
unsigned
short refcount; /* used? */
uint16_t it_flags;
/* ITEM_* above. */
uint8_t slabs_clsid;
/* Same as above. */
uint8_t orig_clsid;
/* For obj hdr chunks slabs_clsid is fake. */
char data[];
} item_chunk;

  next与prev指针实现双向链表,head指针始终指向第一个chunk的item结构,slabs_clsid表示该item_chunk所在的slabclass_t的索引,orig_clsid表示第一个chunk所有的slabclass_t索引,it_flags将被设置为ITEM_CHUNK,表示该chunk是一个item_chunk结构

2.    hashtable

  缓存对象存储到slabs中后,如何找到需要的item数据呢? memcached使用了hashtable对缓存的item进行索引,所有的item都具有一个key,使用该key就可以在hashtable中找到对应的item的指针。

技术分享图片

 图2-1 hashtable

hashtable是一个可以动态扩展的全局数组,每一个数组元素都是一个指向item组成的单向链表的头指针,同一个单向链表上的所有item的key的 hash值相同,memcached中使用单链表的方式解决冲突。

以下是memcached中对item结构的定义,

/**
* 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

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。

 5. 其它

1. item_locks

static pthread_mutex_t *item_locks;

动态扩展的排它锁数组,数组长度与hashtable数组长度相同。即是说,每一个hashtable的数组元素,即hash值相同的item,都有一个独立的锁,增加了操作hashtable的并行度。

2. lru_locks

/* Locks for cache LRU operations */
pthread_mutex_t lru_locks[POWER_LARGEST];
static item *heads[LARGEST_ID];

排它锁数组,POWER_LARGEST与LARGEST_ID相同,即是说每一条lru相关的又向链表都有一个独立的锁保护,不同的又向链表可以并行操作,增加了并行度。

3. item的it_flags

it_flags可以是多个值的异或,这些值定义如下

#define ITEM_LINKED 1
#define ITEM_CAS 2
/* temp */
#define ITEM_SLABBED 4
/* Item was fetched at least once in its lifetime */
#define ITEM_FETCHED 8
/* Appended on fetch, removed on LRU shuffling */
#define ITEM_ACTIVE 16
/* If an item‘s storage are chained chunks. */
#define ITEM_CHUNKED 32
#define ITEM_CHUNK 64
/* ITEM_data bulk is external to item */
#define ITEM_HDR 128
/* additional 4 bytes for item client flags */
#define ITEM_CFLAGS 256
/* 7 bits free! */

首先,在slabs中的空闲chunk的it_flags设置为ITEM_SLABBED,当该chunk被分配时清除该标记;然后如果item加入了hashtable与lru的双向链表,设置ITEM_LINKED,从hashtable与lru双向链表移除后清除该标志。

ITEM_CHUNKED标记表明该缓存对象存储在多个chunk中,且此item是头节点,数据区存储了一个item_chunk结构指向存储数据的chunk;ITEM_CHUNK表示这是一个存储数据的item_chunk结构

ITEM_CAS标记表示item使用cas字段;ITEM_CFLAGS表示item使用flgas字段;ITEM_HDR表示使用外部存储,此处slabs的chunk中仅存储了一个头部。


推荐阅读
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文内容为asp.net微信公众平台开发的目录汇总,包括数据库设计、多层架构框架搭建和入口实现、微信消息封装及反射赋值、关注事件、用户记录、回复文本消息、图文消息、服务搭建(接入)、自定义菜单等。同时提供了示例代码和相关的后台管理功能。内容涵盖了多个方面,适合综合运用。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
author-avatar
书友70030711
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有