Memcached深度分析Word下载.docx

上传人:b****5 文档编号:17970220 上传时间:2022-12-12 格式:DOCX 页数:22 大小:30.33KB
下载 相关 举报
Memcached深度分析Word下载.docx_第1页
第1页 / 共22页
Memcached深度分析Word下载.docx_第2页
第2页 / 共22页
Memcached深度分析Word下载.docx_第3页
第3页 / 共22页
Memcached深度分析Word下载.docx_第4页
第4页 / 共22页
Memcached深度分析Word下载.docx_第5页
第5页 / 共22页
点击查看更多>>
下载资源
资源描述

Memcached深度分析Word下载.docx

《Memcached深度分析Word下载.docx》由会员分享,可在线阅读,更多相关《Memcached深度分析Word下载.docx(22页珍藏版)》请在冰豆网上搜索。

Memcached深度分析Word下载.docx

另外,memcached也经常作为服务器之间数据共享的存储媒介,例如在SSO系统中保存系统单点登陆状态的数据就可以保存在memcached中,被多个应用共享。

需要注意的是,memcached使用内存管理数据,所以它是易失的,当服务器重启,或者memcached进程中止,数据便会丢失,所以memcached不能用来持久保存数据。

很多人的错误理解,memcached的性能非常好,好到了内存和硬盘的对比程度,其实memcached使用内存并不会得到成百上千的读写速度提高,它的实际瓶颈在于网络连接,它和使用磁盘的数据库系统相比,好处在于它本身非常“轻”,因为没有过多的开销和直接的读写方式,它可以轻松应付非常大的数据交换量,所以经常会出现两条千兆网络带宽都满负荷了,memcached进程本身并不占用多少CPU资源的情况。

◎Memcached的工作方式

以下的部分中,读者最好能准备一份memcached的源代码。

Memcached是传统的网络服务程序,如果启动的时候使用了-d参数,它会以守护进程的方式执行。

创建守护进程由daemon.c完成,这个程序只有一个daemon函数,这个函数很简单(如无特殊说明,代码以1.2.1为准):

CODE:

[Copytoclipboard]#includefcntl.h

#includestdlib.h

#includeunistd.h

int

daemon(nochdir,noclose)

intnochdir,noclose;

{

intfd;

switch(fork()){

case-1:

return(-1);

case0:

break;

default:

_exit(0);

}

if(setsid()==-1)

if(!

nochdir)

(void)chdir(“/”);

noclose(fd=open(“/dev/null”,O_RDWR,0))!

=-1){

(void)dup2(fd,STDIN_FILENO);

(void)dup2(fd,STDOUT_FILENO);

(void)dup2(fd,STDERR_FILENO);

if(fdSTDERR_FILENO)

(void)close(fd);

return(0);

}

这个函数fork了整个进程之后,父进程就退出,接着重新定位STDIN、STDOUT、STDERR到空设备,daemon就建立成功了。

Memcached本身的启动过程,在memcached.c的main函数中顺序如下:

1、调用settings_init()设定初始化参数

2、从启动命令中读取参数来设置setting值

3、设定LIMIT参数

4、开始网络socket监听(如果非socketpath存在)(1.2之后支持UDP方式)

5、检查用户身份(Memcached不允许root身份启动)

6、如果有socketpath存在,开启UNIX本地连接(Sock管道)

7、如果以-d方式启动,创建守护进程(如上调用daemon函数)

8、初始化item、event、状态信息、hash、连接、slab

9、如设置中managed生效,创建bucket数组

10、检查是否需要锁定内存页

11、初始化信号、连接、删除队列

12、如果daemon方式,处理进程ID

13、event开始,启动过程结束,main函数进入循环。

在daemon方式中,因为stderr已经被定向到黑洞,所以不会反馈执行中的可见错误信息。

memcached.c的主循环函数是drive_machine,传入参数是指向当前的连接的结构指针,根据state成员的状态来决定动作。

Memcached使用一套自定义的协议完成数据交换,它的PRotocol文档可以参考:

在API中,换行符号统一为\r\n

◎Memcached的内存管理方式

Memcached有一个很有特色的内存管理方式,为了提高效率,它使用预申请和分组的方式管理内存空间,而并不是每次需要写入数据的时候去malloc,删除数据的时候free一个指针。

Memcached使用slab-chunk的组织方式管理内存。

1.1和1.2的slabs.c中的slab空间划分算法有一些不同,后面会分别介绍。

Slab可以理解为一个内存块,一个slab是memcached一次申请内存节(1MB),所以memcached都是整MB的使用内存。

每一个slab被划分为若干个chunk,每个chunk里保存一个item,每个item同时包含了item结构体、key和value(注意在memcached中的value是只有字符串的)。

slab按照自己的id分别组成链表,这些链表又按id挂在一个slabclass数组上,整个结构看起来有点像二维数组。

slabclass的长度在1.1中是21,在1.2中是200。

slab有一个初始chunk大小,1.1中是1字节,1.2中是80字节,1.2中有一个factor值,默认为1.25

在1.1中,chunk大小表示为初始大小*2

,n为classid,即:

id为0的slab,每chunk大小1字节,id为1的slab,每chunk大小2字节,id为2的slab,每chunk大小4字节……id为20的slab,每chunk大小为1MB,就是说id为20的slab里只有一个chunk:

[Copytoclipboard]voidslabs_init(size_tlimit){

inti;

intsize=1;

mem_limit=limit;

for(i=0;

i=POWER_LARGEST;

i++,size*=2){

slabclass[i].size=size;

slabclass[i].perslab=POWER_BLOCK/size;

slabclass[i].slots=0;

slabclass[i].sl_curr=slabclass[i].sl_total=slabclass[i].slabs=0;

slabclass[i].end_page_ptr=0;

slabclass[i].end_page_free=0;

slabclass[i].slab_list=0;

slabclass[i].list_size=0;

slabclass[i].killing=0;

/*forthetestsuite:

fakingofhowmuchwe’vealreadymalloc’d*/

{

char*t_initial_malloc=getenv(“T_MEMD_INITIAL_MALLOC”);

if(t_initial_malloc){

mem_malloced=atol(getenv(“T_MEMD_INITIAL_MALLOC”));

/*pre-allocateslabsbydefault,unlesstheenvironmentvariable

fortestingissettosomethingnon-zero*/

char*pre_alloc=getenv(“T_MEMD_SLABS_ALLOC”);

pre_alloc||atoi(pre_alloc)){

slabs_preallocate(limit/POWER_BLOCK);

在1.2中,chunk大小表示为初始大小*f

,f为factor,在memcached.c中定义,n为classid,同时,201个头不是全部都要初始化的,因为factor可变,初始化只循环到计算出的大小达到slab大小的一半为止,而且它是从id1开始的,即:

id为1的slab,每chunk大小80字节,id为2的slab,每chunk大小80*f,id为3的slab,每chunk大小80*f_,初始化大小有一个修正值CHUNK_ALIGN_BYTES,用来保证n-byte排列(保证结果是CHUNK_ALIGN_BYTES的整倍数)。

这样,在标准情况下,memcached1.2会初始化到id40,这个slab中每个chunk大小为504692,每个slab中有两个chunk。

最后,slab_init函数会在最后补足一个id41,它是整块的,也就是这个slab中只有一个1MB大的chunk:

[Copytoclipboard]voidslabs_init(size_tlimit,doublefactor){

inti=POWER_SMALLEST-1;

unsignedintsize=sizeof(item)+settings.chunk_size;

/*Factorof2.0meansusethedefaultmemcachedbehavior*/

if(factor==2.0size128)

size=128;

memset(slabclass,0,sizeof(slabclass));

while(++iPOWER_LARGESTsize=POWER_BLOCK/2){

/*Makesureitemsarealwaysn-bytealigned*/

if(size%CHUNK_ALIGN_BYTES)

size+=CHUNK_ALIGN_BYTES-(size%CHUNK_ALIGN_BYTES);

slabclass[i].perslab=POWER_BLOCK/slabclass[i].size;

size*=factor;

if(settings.verbose1){

fprintf(stderr,“slabclass%3d:

chunksize%6dperslab%5d\n”,

i,slabclass[i].size,slabclass[i].perslab);

}

power_largest=i;

slabclass[power_largest].size=POWER_BLOCK;

slabclass[power_largest].perslab=1;

#ifndefDONT_PREALLOC_SLABS

#endif

由上可以看出,memcached的内存分配是有冗余的,当一个slab不能被它所拥有的chunk大小整除时,slab尾部剩余的空间就被丢弃了,那么就有39192字节被浪费了。

Memcached使用这种方式来分配内存,是为了可以快速的通过item长度定位出slab的classid,有一点类似hash,因为item的长度是可以计算的,比如一个item的长度是300字节,在1.2中就可以得到它应该保存在id7的slab中,因为按照上面的计算方法,id6的chunk大小是252字节,id7的chunk大小是316字节,id8的chunk大小是396字节,表示所有252到316字节的item都应该保存在id7中。

同理,在1.1中,也可以计算得到它出于256和512之间,应该放在chunk_size为512的id9中(32位系统)。

Memcached初始化的时候,会初始化slab(前面可以看到,在main函数中调用了slabs_init())。

它会在slabs_init()中检查一个常量DONT_PREALLOC_SLABS,如果这个没有被定义,说明使用预分配内存方式初始化slab,这样在所有已经定义过的slabclass中,每一个id创建一个slab。

这样就表示,1.2在默认的环境中启动进程后要分配41MB的slab空间,在这个过程里,memcached的第二个内存冗余发生了,因为有可能一个id根本没有被使用过,但是它也默认申请了一个slab,每个slab会用掉1MB内存

当一个slab用光后,又有新的item要插入这个id,那么它就会重新申请新的slab,申请新的slab时,对应id的slab链表就要增长,这个链表是成倍增长的,在函数grow_slab_list函数中,这个链的长度从1变成2,从2变成4,从4变成8……:

[Copytoclipboard]staticintgrow_slab_list(unsignedintid){

slabclass_t*p=slabclass[id];

if(p-slabs==p-list_size){

size_tnew_size=p-list_size?

p-list_size*2:

16;

void*new_list=realloc(p-slab_list,new_size*sizeof(void*));

if(new_list==0)return0;

p-list_size=new_size;

p-slab_list=new_list;

return1;

在定位item时,都是使用slabs_clsid函数,传入参数为item大小,返回值为classid,由这个过程可以看出,memcached的第三个内存冗余发生在保存item的过程中,item总是小于或等于chunk大小的,当item小于chunk大小时,就又发生了空间浪费。

◎Memcached的NewHash算法

Memcached的item保存基于一个大的hash表,它的实际地址就是slab中的chunk偏移,但是它的定位是依靠对key做hash的结果,在primary_hashtable中找到的。

在assoc.c和items.c中定义了所有的hash和item操作。

Memcached使用了一个叫做NewHash的算法,它的效果很好,效率也很高。

1.1和1.2的NewHash有一些不同,主要的实现方式还是一样的,1.2的hash函数是经过整理优化的,适应性更好一些。

NewHash的原型参考:

数学家总是有点奇怪,呵呵~

为了变换方便,定义了u4和u1两种数据类型,u4就是无符号的长整形,u1就是无符号char(0-255)。

具体代码可以参考1.1和1.2源码包。

注意这里的hashtable长度,1.1和1.2也是有区别的,1.1中定义了HASHPOWER常量为20,hashtable表长为hashsize(HASHPOWER),就是4MB(hashsize是一个宏,表示1右移n位),1.2中是变量16,即hashtable表长65536:

[Copytoclipboard]typedefunsignedlongintub4;

/*unsigned4-bytequantities*/

typedefunsignedcharub1;

/*unsigned1-bytequantities*/

#definehashsize(n)((ub4)1(n))

#definehashmask(n)(hashsize(n)-1)

在assoc_init()中,会对primary_hashtable做初始化,对应的hash操作包括:

assoc_find()、assoc_expand()、assoc_move_next_bucket()、assoc_insert()、assoc_delete(),对应于item的读写操作。

其中assoc_find()是根据key和key长寻找对应的item地址的函数(注意在C中,很多时候都是同时直接传入字符串和字符串长度,而不是在函数内部做strlen),返回的是item结构指针,它的数据地址在slab中的某个chunk上。

items.c是数据项的操作程序,每一个完整的item包括几个部分,在item_make_header()中定义为:

key:

nkey:

键长

flags:

用户定义的flag(其实这个flag在memcached中没有启用)

nbytes:

值长(包括换行符号\r\n)

suffix:

后缀Buffer

nsuffix:

后缀长

一个完整的item长度是键长+值长+后缀长+item结构大小(32字节),item操作就是根据这个长度来计算slab的classid的。

hashtable中的每一个桶上挂着一个双链表,item_init()的时候已经初始化了heads、tails、sizes三个数组为0,这三个数组的大小都为常量LARGEST_ID(默认为255,这个值需要配合factor来修改),在每次item_assoc()的时候,它会首先尝试从slab中获取一块空闲的chunk,如果没有可用的chunk,会在链表中扫描50次,以得到一个被LRU踢掉的item,将它unlink,然后将需要插入的item插入链表中。

注意item的refcount成员。

item被unlink之后只是从链表上摘掉,不是立刻就被free的,只是将它放到删除队列中(item_unlink_q()函数)。

item对应一些读写操作,包括remove、update、replace,当然最重要的就是alloc操作。

item还有一个特性就是它有过期时间,这是memcached的一个很有用的特性,很多应用都是依赖于memcached的item过期,比如session存储、操作锁等。

item_flush_expired()函数就是扫描表中的item,对过期的item执行unlink操作,当然这只是一个回收动作,实际上在get的时候还要进行时间判断:

[Copytoclipboard]/*expiresitemsthataremorerecentthantheoldest_livesetting.*/

voiditem_flush_expired(){

item*iter,*next;

settings.oldest_live)

return;

for(i=0;

iLARGEST_ID;

i++){

/*TheLRUissortedindecreasingtimeorder,andanitem’stimestamp

*isnevernewerthanitslastaccesstime,soweonlyneedtowalk

*backuntilwehitanitemolderthantheoldest_livetime.

*Theoldest_livecheckingwillauto-expiretheremainingitems.

*/

for(iter=heads[i];

iter!

=NULL;

iter=next){

if(iter-time=settings.oldest_live){

next=iter-next;

if((iter-it_flagsITEM_SLABBED)==0){

item_unlink(iter);

}else{

/*We’vehitthefirs

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 党团工作 > 党团建设

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1