memcached深度分析(2)

发表于:2015-07-10来源:uml.org.cn作者:火龙果软件点击数: 标签:数据库
这个函数 fork 了整个进程之后,父进程就退出,接着重新定位 STDIN 、 STDOUT 、 STDERR 到空设备, daemon 就建立成功了。 Memcached 本身的启动过程,在 memcach

  这个函数 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 文档可以参考: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt

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

  Memcached的内存管理方式

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

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

  Slab可以理解为一个内存块,一个slab是memcached一次申请内存的最小单位,在memcached中,一个slab的大小默认为 1048576字节(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,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:

void slabs_init(size_t limit) {
    int i;
    int size=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;
    }
 
    /* for the test suite:  faking of how much we've already malloc'd */
    {
        char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
        if (t_initial_malloc) {
            mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
        }
    }
 
    /* pre-allocate slabs by default, unless the environment variable
       for testing is set to something non-zero */
    {
        char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
        if (!pre_alloc || atoi(pre_alloc)) {
            slabs_preallocate(limit / POWER_BLOCK);
        }
    }
}

  在1.2中,chunk大小表示为初始大小*f^n,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^2,初始化大小有一个修正值 CHUNK_ALIGN_BYTES,用来保证n-byte排列 (保证结果是CHUNK_ALIGN_BYTES的整倍数)。这样,在标准情况下,memcached1.2会初始化到id40,这个slab中每个 chunk大小为504692,每个slab中有两个chunk。最后,slab_init函数会在最后补足一个id41,它是整块的,也就是这个 slab中只有一个1MB大的chunk:

原文转自:http://www.uml.org.cn/sjjm/201411134.asp