memcached源码分析-----item过期失效处理以及LRU爬虫。

过期失效处理:

一个item在两种情况下会过期失效:1.item的exptime时间戳到了。2.用户使用flush_all命令将全部item变成过期失效的。读者可能会说touch命令也可以使得一个item过期失效,其实这也属于前面说的第一种情况。

超时失效:

对于第一种过期失效,memcached的使用懒惰处理:不主动检测一个item是否过期失效。当worker线程访问这个item时,才检测这个item的exptime时间戳是否到了。比较简单,这里就先不贴代码,后面会贴。

flush_all命令:

第二种过期失效是用户flush_all命令设置的。flush_all会将所有item都变成过期失效。所有item是指哪些item?因为多个客户端会不断地往memcached插入item,所以必须要明白所有item是指哪些。是以worker线程接收到这个命令那一刻为界?还是以删除那一刻为界?

当worker线程接收到flush_all命令后,会用全局变量settings的oldest_live成员存储接收到这个命令那一刻的时间(准确地说,是worker线程解析得知这是一个flush_all命令那一刻再减一),代码为settings.oldest_live =current_time - 1;然后调用item_flush_expired函数锁上cache_lock,然后调用do_item_flush_expired函数完成工作。v

oid do_item_flush_expired(void) {
int i;
item *iter, *next;
if (settings.oldest_live == 0)
return;
for (i = 0; i < LARGEST_ID; i++) {
for (iter = heads[i]; iter != NULL; iter = next) {
if (iter->time != 0 && iter->time >= settings.oldest_live) {
next = iter->next;
if ((iter->it_flags & ITEM_SLABBED) == 0) {
do_item_unlink_nolock(iter, hash(ITEM_key(iter), iter->nkey));
}
} else {
/* We've hit the first old item. Continue to the next queue. */
break;
}
}
}
}

do_item_flush_expired函数内部会遍历所有LRU队列,检测每一个item的time成员。检测time成员是合理的。如果time成员小于settings.oldest_live就说明该item在worker线程接收到flush_all命令的时候就已经存在了(time成员表示该item的最后一次访问时间)。那么就该删除这个item。

这样看来memcached是以worker线程接收到flush_all命令那一刻为界的。等等等等,看清楚一点!!在do_item_flush_expired函数里面,不是当item的time成员小于settings.oldest_live时删除这个item,而是大于的时候才删除。从time成员变量的意义来说,大于代表什么啊?有大于的吗?奇怪[email protected]#@&¥

实际上memcached是以删除那一刻为界的。那settings.oldest_live为什么要存储worker线程接收到flush_all命令的时间戳?为什么又要判断iter->time是否大于settings.oldest_live呢?

按照一般的做法,在do_item_flush_expired函数中直接把哈希表和LRU上的所有item统统删除即可。这样确实是可以达到目标。但在本worker线程处理期间,其他worker线程完全不能工作(因为do_item_flush_expired的调用者已经锁上了cache_lock)。而LRU队列里面可能有大量的数据,这个过期处理过程可能会很长。其他worker线程完全不能工作是难于接受的。

memcached的作者肯定也意识到这个问题,所以他就写了一个奇怪的do_item_flush_expired函数,用来加速。do_item_flush_expired只会删除少量特殊的item。如何特殊法,在后面代码注释中会解释。对于其他大量的item,memcached采用懒惰方式处理。只有当worker线程试图访问该item,才检测item是否已经被设置为过期的了。事实上,无需对item进行任何设置就能检测该item是否为过期的,通过settings.oldest_live变量即可。这种懒惰和前面第一种item过期失效的处理是一样的。

现在再看一下do_item_flush_expired函数,看一下特殊的item。

void do_item_flush_expired(void) {
int i;
item *iter, *next;
if (settings.oldest_live == 0)
return;
for (i = 0; i < LARGEST_ID; i++) {
for (iter = heads[i]; iter != NULL; iter = next) {
//iter->time == 0的是lru爬虫item,直接忽略
//一般情况下iter->time是小于settings.oldest_live的。但在这种情况下
//就有可能出现iter->time >= settings.oldest_live : worker1接收到
//flush_all命令,并给settings.oldest_live赋值为current_time-1。
//worker1线程还没来得及调用item_flush_expired函数,就被worker2
//抢占了cpu,然后worker2往lru队列插入了一个item。这个item的time
//成员就会满足iter->time >= settings.oldest_live
if (iter->time != 0 && iter->time >= settings.oldest_live) {
next = iter->next;
if ((iter->it_flags & ITEM_SLABBED) == 0) {
//虽然调用的是nolock,但本函数的调用者item_flush_expired
//已经锁上了cache_lock,才调用本函数的
do_item_unlink_nolock(iter, hash(ITEM_key(iter), iter->nkey));
}
} else {
//因为lru队列里面的item是根据time降序排序的,所以当存在一个item的time成员
//小于settings.oldest_live,剩下的item都不需要再比较了
break;
}
}
}
}

懒惰删除:

现在阅读item的懒惰删除。注意代码中的注释。

item *do_item_get(const char *key, const size_t nkey, const uint32_t hv) {
item *it = assoc_find(key, nkey, hv);
...
if (it != NULL) {
//settings.oldest_live初始化值为0
//检测用户是否使用过flush_all命令,删除所有item。
//it->time <= settings.oldest_live就说明用户在使用flush_all命令的时候
//就已经存在该item了。那么该item是要删除的。
//flush_all命令可以有参数,用来设定在未来的某个时刻把所有的item都设置
//为过期失效,此时settings.oldest_live是一个比worker接收到flush_all
//命令的那一刻大的时间,所以要判断settings.oldest_live <= current_time
if (settings.oldest_live != 0 && settings.oldest_live <= current_time &&
it->time <= settings.oldest_live) {
do_item_unlink(it, hv);
do_item_remove(it);
it = NULL;
} else if (it->exptime != 0 && it->exptime <= current_time) {//该item已经过期失效了
do_item_unlink(it, hv);//引用数会减一
do_item_remove(it);//引用数减一,如果引用数等于0,就删除
it = NULL;
} else {
it->it_flags |= ITEM_FETCHED;
}
}
return it;
}

可以看到,在查找到一个item后就要检测它是否过期失效了。失效了就要删除之。

除了do_item_get函数外,do_item_alloc函数也是会处理过期失效item的。do_item_alloc函数不是删除这个过期失效item,而是占为己用。因为这个函数的功能是申请一个item,如果一个item过期了那么就直接霸占这个item的那块内存。下面看一下代码。

item *do_item_alloc(char *key, const size_t nkey, const int flags,
const rel_time_t exptime, const int nbytes,
const uint32_t cur_hv) {
uint8_t nsuffix;
item *it = NULL;
char suffix[40];
//要存储这个item需要的总空间
size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix);
if (settings.use_cas) {
ntotal += sizeof(uint64_t);
}
//根据大小判断从属于哪个slab
unsigned int id = slabs_clsid(ntotal);
/* do a quick check if we have any expired items in the tail.. */
int tries = 5;
item *search;
item *next_it;
rel_time_t oldest_live = settings.oldest_live;
search = tails[id];
for (; tries > 0 && search != NULL; tries--, search=next_it) {
next_it = search->prev;
...
if (refcount_incr(&search->refcount) != 2) {//引用数,还有其他线程在引用,不能霸占这个item
//刷新这个item的访问时间以及在LRU队列中的位置
do_item_update_nolock(search);
tries++;
refcount_decr(&search->refcount);
//此时引用数>=2
continue;
}
//search指向的item的refcount等于2,这说明此时这个item除了本worker
//线程外,没有其他任何worker线程索引其。可以放心释放并重用这个item
//因为这个循环是从lru链表的后面开始遍历的。所以一开始search就指向
//了最不常用的item,如果这个item都没有过期。那么其他的比其更常用
//的item就不要删除了(即使它们过期了)。此时只能向slabs申请内存
if ((search->exptime != 0 && search->exptime < current_time)
|| (search->time <= oldest_live && oldest_live <= current_time)) {
//search指向的item是一个过期失效的item,可以使用之
it = search;
//重新计算一下这个slabclass_t分配出去的内存大小
//直接霸占旧的item就需要重新计算
slabs_adjust_mem_requested(it->slabs_clsid, ITEM_ntotal(it), ntotal);
do_item_unlink_nolock(it, hv);//从哈希表和lru链表中删除
/* Initialize the item block: */
it->slabs_clsid = 0;
}
//引用计数减一。此时该item已经没有任何worker线程索引其,并且哈希表也
//不再索引其
refcount_decr(&search->refcount);
break;
}
...
return it;
}
//新的item直接霸占旧的item就会调用这个函数
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];
//重新计算一下这个slabclass_t分配出去的内存大小
p->requested = p->requested - old + ntotal;
pthread_mutex_unlock(&slabs_lock);
}

flush_all命令是可以有时间参数的。这个时间和其他时间一样取值范围是 1到REALTIME_MAXDELTA(30天)。如果命令为flush_all 100,那么99秒后所有的item失效。此时settings.oldest_live的值为current_time+100-1,do_item_flush_expired函数也没有什么用了(总不会被抢占CPU99秒吧)。也正是这个原因,需要在do_item_get里面,加入settings.oldest_live<= current_time这个判断,防止过早删除了item。

这里明显有一个bug。假如客户端A向服务器提交了flush_all10命令。过了5秒后,客户端B向服务器提交命令flush_all100。那么客户端A的命令将失效,没有起到任何作用。

本文数据库(综合)相关术语:系统安全软件

主题: 数据服务器删除CPU数据库其实TI变量
分页:12
转载请注明
本文标题:memcached源码分析-----item过期失效处理以及LRU爬虫 数据库 数据库学习 memcached源码 ...
本站链接:http://www.codesec.net/view/532757.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 数据库(综合) | 评论(0) | 阅读(70)