1. 程式人生 > >memcached原始碼分析之四

memcached原始碼分析之四

還是從Memcached.c檔案的main函式開始,逐步分析Memcached的實現

    if (!sanitycheck()) {
        return EX_OSERR;
    }

static bool sanitycheck(void) {
    /* One of our biggest problems is old and bogus libevents */
    const char *ever = event_get_version();  //獲取libevent的版本
    if (ever != NULL) {
        if (strncmp(ever, "1.", 2) == 0) {
            /* Require at least 1.3 (that's still a couple of years old) */
            if ((ever[2] == '1' || ever[2] == '2') && !isdigit(ever[3])) {
                fprintf(stderr, "You are using libevent %s.\nPlease upgrade to"
                        " a more recent version (1.3 or newer)\n",  說明至少是1.3版本以上
                        event_get_version());
                return false;
            }
        }
    }

    return true;
}

/* handle SIGINT and SIGTERM */
    signal(SIGINT, sig_handler);   //Ctrl + C產生的訊號
    signal(SIGTERM, sig_handler);  //kill命令產生的訊號

    /* init settings */
    settings_init();   //預設初始化

static void settings_init(void) {
    settings.use_cas = true;
    settings.access = 0700;
    settings.port = 11211;    //埠號
    settings.udpport = 11211;
    /* By default this string should be NULL for getaddrinfo() */
    settings.inter = NULL;
    settings.maxbytes = 64 * 1024 * 1024; /* default is 64MB */  預設分配最大64M記憶體
    settings.maxconns = 1024;         /* to limit connections-related memory to about 5MB */
    settings.verbose = 0;     //資訊列印
    settings.oldest_live = 0;
    settings.oldest_cas = 0;          /* supplements accuracy of oldest_live */
    settings.evict_to_free = 1;       /* push old items out of cache when memory runs out */
    settings.socketpath = NULL;       /* by default, not using a unix socket */
    settings.factor = 1.25;          //slab分配增長因子
    settings.chunk_size = 48;         /* space for a modest key and value */
    settings.num_threads = 4;         /* N workers */
    settings.num_threads_per_udp = 0;
    settings.prefix_delimiter = ':';
    settings.detail_enabled = 0;
    settings.reqs_per_event = 20;
    settings.backlog = 1024;
    settings.binding_protocol = negotiating_prot;
    settings.item_size_max = 1024 * 1024; /* The famous 1MB upper limit. */
    settings.maxconns_fast = false;
    settings.lru_crawler = false;
    settings.lru_crawler_sleep = 100;
    settings.lru_crawler_tocrawl = 0;
    settings.lru_maintainer_thread = false;
    settings.hot_lru_pct = 32;
    settings.warm_lru_pct = 32;
    settings.expirezero_does_not_evict = false;
    settings.hashpower_init = 0;
    settings.slab_reassign = false;
    settings.slab_automove = 0;
    settings.shutdown_command = false;
    settings.tail_repair_time = TAIL_REPAIR_TIME_DEFAULT;
    settings.flush_enabled = true;
    settings.crawls_persleep = 1000;
}

    /* Run regardless of initializing it later */
    init_lru_crawler();
    init_lru_maintainer();
初始化LRU連結串列的鎖
 /* set stderr non-buffering (for running under, say, daemontools) */
    setbuf(stderr, NULL);
關閉錯誤輸出
if (hash_init(hash_type) != 0) {
        fprintf(stderr, "Failed to initialize hash_algorithm!\n");
        exit(EX_USAGE);
    }
預設選擇jenkins_hash
    main_base = event_init();  //主執行緒base初始化

    /* initialize other stuff */
    stats_init();     //用於統計的結構的初始化
    assoc_init(settings.hashpower_init);  //Memcached的hash表初始化,預設256個
    conn_init();     //用於連線結構體的初始化
    slabs_init(settings.maxbytes, settings.factor, preallocate);  //slab記憶體分配(預設分配64M空間<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">)</span>

static void conn_init(void) {
    /* We're unlikely to see an FD much higher than maxconns. */
    int next_fd = dup(1);
    int headroom = 10;      /* account for extra unexpected open FDs */
    struct rlimit rl;

    max_fds = settings.maxconns + headroom + next_fd;  //設定最大檔案描述符

    /* But if possible, get the actual highest FD we can possibly ever see. */
    if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
        max_fds = rl.rlim_max;     //由系統獲取最大值
    } else {
        fprintf(stderr, "Failed to query maximum file descriptor; "
                        "falling back to maxconns\n");
    }

    close(next_fd);

    if ((conns = calloc(max_fds, sizeof(conn *))) == NULL) {   //分配資源
        fprintf(stderr, "Failed to allocate connection structures\n");
        /* This is unrecoverable so bail out early. */
        exit(1);
    }
}

Memcached的記憶體分配模型:

Memcached預設分配64M記憶體,有slabclass連結串列組成,每個slabclass又有多個slab,每個slab預設是1M的記憶體分配,每個slab又由逐漸增大的chunck記憶體空間組成,增長空間由增長因子來計算獲取,比如第一個chunck是100byte,factor預設為1.25,則第二個chunck為125bytes,依次分配填充記憶體空間,避免記憶體的浪費。

slabclass_t結構體:

typedef struct {
    unsigned int size;      /* sizes of items */小於這個大小的item存入此slabclass中 item就是要儲存的實際資料,組織的set命令的資料,每個item會放到合適的一個chunck中
    unsigned int perslab;   /* how many items per slab */ 儲存多少個item了

    void *slots;           /* list of item ptrs */ 回收空閒item連結串列
    unsigned int sl_curr;   /* total free items in list */ 記錄有所多少的items空間沒有使用

    unsigned int slabs;     /* how many slabs were allocated for this class */  分配了多少個slab

    void **slab_list;       /* array of slab pointers */ 指向slab連結串列地址
    unsigned int list_size; /* size of prev array */slab連結串列的大小,1個slab就是1M,2個slab就是2M記憶體空間 

    unsigned int killing;  /* index+1 of dying slab, or zero if none */
    size_t requested; /* The number of requested bytes */
} slabclass_t;

static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES]; 64個slabclass

slabclass 是由 chunk size 確定的, 同一個 slabclass 內的 chunk 大小都一樣,  每一個 slabclass 要負責管理

一些記憶體, 初始時, 系統為每個 slabclass 分配一個 slab, 一個 slab 就是一個記憶體塊,  其大小等於 1M.  然後每個

slabclass 再把 slab 切分成一個個 chunk, 算一下, 一個 slab 可以切分得到 1M/chunk_size 個chunk.


a)slab、chunk

slab是一塊記憶體空間,預設大小為1M,而memcached會把一個slab分割成一個個chunk,比如說1M的slab分成兩個0.5M的chunk,所以說slab和chunk其實都是代表實質的記憶體空間,chunk只是把slab分割後的更小的單元而已。
slab就相當於作業本中的“頁”,而chunk則是把一頁畫成一個個格子中的“格”。

b)item

item是我們要儲存的資料,例如php程式碼:$memcached->set(“name”,”abc”,30);代表我們把一個key為name,value為abc的鍵值對儲存在記憶體中30秒,那麼上述中的”name”, “abc”, 30這些資料實質都是我們要memcached儲存下來的資料, memcached會把這些資料打包成一個item,這個item其實是memcached中的一個結構體(當然結構遠不止上面提到的三個欄位這麼簡單),把打包好的item儲存起來,完成工作。而item儲存在哪裡?其實就是上面提到的”chunk”,一個item儲存在一個chunk中。

chunk是實質的記憶體空間,item是要儲存的東西,所以關係是:item是往chunk中塞的。


/**
 * Determines the chunk sizes and initializes the slab class descriptors
 * accordingly.
 */
void slabs_init(const size_t limit, const double factor, const bool prealloc) {
    int i = POWER_SMALLEST - 1;
    unsigned int size = sizeof(item) + settings.chunk_size;

    mem_limit = limit;  預設分配大小

    if (prealloc) {   
        /* Allocate everything in a big chunk with malloc */
        mem_base = malloc(mem_limit);
        if (mem_base != NULL) {
            mem_current = mem_base;
            mem_avail = mem_limit;
        } else {
            fprintf(stderr, "Warning: Failed to allocate requested memory in"
                    " one large chunk.\nWill allocate in smaller chunks\n");
        }
    }

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

    while (++i < MAX_NUMBER_OF_SLAB_CLASSES-1 && size <= settings.item_size_max / factor) { 
        /* Make sure items are always n-byte aligned */
        if (size % CHUNK_ALIGN_BYTES)   記憶體對齊
            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);

        slabclass[i].size = size;  標識item大小
        slabclass[i].perslab = settings.item_size_max / slabclass[i].size; //預設1M空間可一個儲存多少items
        size *= factor; 作為下一個slabclass的size大小
        if (settings.verbose > 1) {
            fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
                    i, slabclass[i].size, slabclass[i].perslab);
        }
    }

    power_largest = i;
    slabclass[power_largest].size = settings.item_size_max; 最後剩餘一個全部分配出去
    slabclass[power_largest].perslab = 1;
    if (settings.verbose > 1) {
        fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
                i, slabclass[i].size, slabclass[i].perslab);
    }

    /* 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 = (size_t)atol(t_initial_malloc);
        }

    }

    if (prealloc) {
        slabs_preallocate(power_largest);
    }
}


使用記憶體儲存資料總會有滿的情況,滿就得淘汰,而memcached中的淘汰機制是LRU(最近最少使用演算法 ),所以每個slabclass都儲存著一個LRU佇列,而head[i]和tail[i]則就是id為i的slabclass LRU佇列的頭部和尾部,尾部的item是最應該淘汰的項,也就是最近最少使用的項。

3)下面結合下面的結構圖對memcached記憶體分配的模型進行解說:

a)初始化slabclass陣列,每個元素slabclass[i]都是不同size的slabclass。

b)每開闢一個新的slab,都會根據所在的slabclass的size來分割chunk,分割完chunk之後,把chunk空間初始化成一個個free item,並插入到slot連結串列中。

c)我們每使用一個free item都會從slot連結串列中刪除掉並插入到LRU連結串列相應的位置。

d)每當一個used item被訪問的時候都會更新它在LRU連結串列中的位置,以保證LRU連結串列從尾到頭淘汰的權重是由高到低的。

e)會有另一個叫“item爬蟲”的執行緒(以後會講到)慢慢地從LRU連結串列中去爬,把過期的item淘汰掉然後重新插入到slot連結串列中(但這種方式並不實時,並不會一過期就回收)。

f)當我們要進行記憶體分配時,例如一個SET命令,它的一般步驟是:

計算出要儲存的資料的大小,然後選擇相應的slabclass進入下面處理:

首先,從相應的slabclass LRU連結串列的尾部開始,嘗試找幾次(預設是5次),看看有沒有過期的item(雖然有item爬蟲執行緒在幫忙查詢,但這裡分配的時候,程式還是會嘗試一下自己找,自己臨時充當牛爬蟲的角色),如果有就利用這個過期的item空間。

如果沒找到過期的,則嘗試去slot連結串列中拿空閒的free item。

如果slot連結串列中沒有空閒的free item了,嘗試申請記憶體,開闢一塊新的slab,開闢成功後,slot連結串列就又有可用的free item了。

如果開不了新的slab那說明記憶體都已經滿了,用完了,只能淘汰,所以用LRU連結串列尾部找出一個item淘汰之,並作為free item返回。

其他記憶體處理函式可以參考:http://blog.csdn.net/yxnyxnyxnyxnyxn/article/details/7869900