转载请注明出处:http://blog.csdn.net/luotuo44/article/details/43015129
需求:
考虑这样的一个情景:在一开始,由于业务原因向memcached存储大量长度为1KB的数据,也就是说memcached服务器进程里面有很多大小为1KB的item。现在由于业务调整需要存储大量10KB的数据,并且很少使用1KB的那些数据了。由于数据越来越多,内存开始吃紧。大小为10KB的那些item频繁访问,并且由于内存不够需要使用LRU淘汰一些10KB的item。
对于上面的情景,会不会觉得大量1KB的item实在太浪费了。由于很少访问这些item,所以即使它们超时过期了,还是会占据着哈希表和LRU队列。LRU队列还好,不同大小的item使用不同的LRU队列。但对于哈希表来说大量的僵尸item会增加哈希冲突的可能性,并且在迁移哈希表的时候也浪费时间。有没有办法干掉这些item?使用LRU爬虫+lru_crawler命令是可以强制干掉这些僵尸item。但干掉这些僵尸item后,它们占据的内存是归还到1KB的那些slab分配器中。1KB的slab分配器不会为10KB的item分配内存。所以还是功亏一篑。
那有没有别的办法呢?是有的。memcached提供的slab automove 和 rebalance两个东西就是完成这个功能的。在默认情况下,memcached不启动这个功能,所以要想使用这个功能必须在启动memcached的时候加上参数-o slab_reassign。之后就可以在客户端发送命令slabsreassign <source class> <dest class>,手动将source class的内存页分给dest class。后文会把这个工作称为内存页重分配。而命令slabs automove则是让memcached自动检测是否需要进行内存页重分配,如果需要的话就自动去操作,这样一切都不需要人工的干预。
如果在启动memcached的时候使用了参数-o slab_reassign,那么就会把settings.slab_reassign赋值为true(该变量的默认值为false)。还记得《slab内存分配器》说到的每一个内存页的大小吗?在do_slabs_newslab函数中,一个内存页的大小会根据settings.slab_reassign是否为true而不同。
static int do_slabs_newslab(const unsigned int id) {
slabclass_t *p = &slabclass[id];
//settings.slab_reassign的默认值为false
int len = settings.slab_reassign ? settings.item_size_max
: p->size * p->perslab;
//len就是一个内存页的大小
...
}
当settings.slab_reassign为true,也就是启动rebalance功能的时候,slabclass数组中所有slabclass_t的内存页都是一样大的,等于settings.item_size_max(默认为1MB)。这样做的好处就是在需要将一个内存页从某一个slabclass_t强抢给另外一个slabclass_t时,比较好处理。不然的话,slabclass[i]从slabclass[j] 抢到的一个内存页可以切分为n个item,而从slabclass[k]抢到的一个内存页却切分为m个item,而本身的一个内存页有s个item。这样的话是相当混乱的。假如毕竟统一了内存页大小,那么无论从哪里抢到的内存页都是切分成一样多的item个数。
启动和终止rebalance:
main函数会调用start_slab_maintenance_thread函数启动rebalance线程和automove线程。main函数是在settings.slab_reassign为true时才会调用的。
//slabs.c文件
static pthread_cond_t maintenance_cond = PTHREAD_COND_INITIALIZER;
static pthread_cond_t slab_rebalance_cond = PTHREAD_COND_INITIALIZER;
static volatile int do_run_slab_thread = 1;
static volatile int do_run_slab_rebalance_thread = 1;
#define DEFAULT_SLAB_BULK_CHECK 1
int slab_bulk_check = DEFAULT_SLAB_BULK_CHECK;
static pthread_mutex_t slabs_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t slabs_rebalance_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_t maintenance_tid;
static pthread_t rebalance_tid;
//由main函数调用,如果settings.slab_reassign为false将不会调用本函数(默认是false)
int start_slab_maintenance_thread(void) {
int ret;
slab_rebalance_signal = 0;
slab_rebal.slab_start = NULL;
char *env = getenv("MEMCACHED_SLAB_BULK_CHECK");
if (env != NULL) {
slab_bulk_check = atoi(env);
if (slab_bulk_check == 0) {
slab_bulk_check = DEFAULT_SLAB_BULK_CHECK;
}
}
if (pthread_cond_init(&slab_rebalance_cond, NULL) != 0) {
fprintf(stderr, "Can't intiialize rebalance condition\n");
return -1;
}
pthread_mutex_init(&slabs_rebalance_lock, NULL);
if ((ret = pthread_create(&maintenance_tid, NULL,
slab_maintenance_thread, NULL)) != 0) {
fprintf(stderr, "Can't create slab maint thread: %s\n", strerror(ret));
return -1;
}
if ((ret = pthread_create(&rebalance_tid, NULL,
slab_rebalance_thread, NULL)) != 0) {
fprintf(stderr, "Can't create rebal thread: %s\n", strerror(ret));
return -1;
}
return 0;
}
void stop_slab_maintenance_thread(void) {
mutex_lock(&cache_lock);
do_run_slab_thread = 0;
do_run_slab_rebalance_thread = 0;
pthread_cond_signal(&maintenance_cond);
pthread_mutex_unlock(&cache_lock);
/* Wait for the maintenance thread to stop */
pthread_join(maintenance_tid, NULL);
pthread_join(rebalance_tid, NULL);
}
要注意的是,start_slab_maintenance_thread函数启动了两个线程:rebalance线程和automove线程。automove线程会自动检测是否需要进行内存页重分配。如果检测到需要重分配,那么就会叫rebalance线程执行这个内存页重分配工作。
默认情况下是不开启自动检测功能的,即使在启动memcached的时候加入了-o slab_reassign参数。自动检测功能由全局变量settings.slab_automove控制(默认值为0,0就是不开启)。如果要开启可以在启动memcached的时候加入slab_automove选项,并将其参数数设置为1。比如命令$memcached -o slab_reassign,slab_automove=1就开启了自动检测功能。当然也是可以在启动memcached后通过客户端命令启动automove功能,使用命令slabsautom