論——memcached真的有工具可以dump出所有key嘛?

memcached.png

眾所周知醋火,memcached并沒有像redis那樣提供了類似 keys * 的命令來獲取所有key,也沒有數(shù)據(jù)持久化箱吕,因此memcached想要dump所有的key芥驳,需要使用額外的命令組合或工具來實(shí)現(xiàn),但有時命令和工具……也不一定會滿足需求茬高。

下面是我最近對找到的幾種方法進(jìn)行的分析兆旬。

一、命令組合方式

我在git上發(fā)現(xiàn)如下工具:
https://github.com/gnomeby/memcached-itool

使用該工具的dumpkeys可以將memcached的key進(jìn)行dump怎栽,查看其實(shí)現(xiàn)方式發(fā)現(xiàn)也是使用了 stats itemsstats cachedump 等命令組合實(shí)現(xiàn)的熏瞄,下面介紹下這種方式的實(shí)現(xiàn)及原理。

1. 原理實(shí)現(xiàn)

首先需要明白memcached的內(nèi)存管理方式:Slab Allocator

  • memcached內(nèi)存分為多個chunk組强饮,即多個slab;
  • 每個組的chunk有不同的大小規(guī)格,如slab 1中chunk大小均為96B行您,slab 2中chunk大小均為120B,以此類推娃循;
  • key根據(jù)其大小分別分配到不同的slab組的chunk中存儲;

具體的內(nèi)存分配機(jī)制不再展開描述捌斧,有興趣的可以參考如下鏈接:
https://www.cnblogs.com/zhoujinyi/p/5554083.html

2. 具體操作

1)stats items笛质、stats slabs命令獲取各slabs id以及slab的具體信息

stats items

STAT items:1:number 39       # slab中的key數(shù)量
STAT items:1:age 693911
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 7
STAT items:1:expired_unfetched 4
STAT items:1:evicted_unfetched 0
STAT items:1:crawler_reclaimed 0
STAT items:1:crawler_items_checked 0
STAT items:1:lrutail_reflocked 0
...
stats slabs

STAT 1:chunk_size 96        # slab中的chunk大小規(guī)格
STAT 1:chunks_per_page 10922
STAT 1:total_pages 1
STAT 1:total_chunks 10922
STAT 1:used_chunks 39
STAT 1:free_chunks 10883
STAT 1:free_chunks_end 0
STAT 1:mem_requested 3656
STAT 1:get_hits 3666
STAT 1:cmd_set 569
STAT 1:delete_hits 1
STAT 1:incr_hits 0
STAT 1:decr_hits 0
STAT 1:cas_hits 0
STAT 1:cas_badval 0
STAT 1:touch_hits 0
...
  • STAT后的數(shù)字即slab的標(biāo)識id

2)stats cachedump {slab_id} {limit_num} 獲取slab下的key信息

stats cachedump 1 5
ITEM seller_shop_im_phone_122 [1 b; 1513935640 s]       # key名稱
ITEM seller_shop_im_phone_11542 [1 b; 1513935346 s]
ITEM user_third_userid_35543020 [2 b; 1516523664 s]
ITEM seller_shop_im_phone_12331 [1 b; 1513933986 s]
ITEM user_third_userid_70086126 [4 b; 1516517439 s]
END
  • slab_id 即各slab組的標(biāo)識id
  • limit_num 即key的數(shù)量,0表示所有key

3. 問題缺陷

cachedump每次返回的數(shù)據(jù)只有2M骤星;
而且在memcached源碼中是寫死的數(shù)值经瓷。

這個問題很嚴(yán)重。

網(wǎng)上并沒有找到相關(guān)源碼洞难,于是我在官網(wǎng)下載了memcached-1.5.3的源碼并查找舆吮,發(fā)現(xiàn)確實(shí)如此:

##########
# 源碼位置:memcached-1.5.3/items.c
##########
char *item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes) {
    unsigned int memlimit = 2 * 1024 * 1024;   /* 2MB max response size */
    char *buffer;
    unsigned int bufcurr;
    item *it;
    unsigned int len;
    unsigned int shown = 0;
    char key_temp[KEY_MAX_LENGTH + 1];
    char temp[512];
    unsigned int id = slabs_clsid;
    id |= COLD_LRU;

    pthread_mutex_lock(&lru_locks[id]);
    it = heads[id];

    buffer = malloc((size_t)memlimit);
    if (buffer == 0) {
        return NULL;
    }
    bufcurr = 0;

    while (it != NULL && (limit == 0 || shown < limit)) {
        assert(it->nkey <= KEY_MAX_LENGTH);
        if (it->nbytes == 0 && it->nkey == 0) {
            it = it->next;
            continue;
        }
        /* Copy the key since it may not be null-terminated in the struct */
        strncpy(key_temp, ITEM_key(it), it->nkey);
        key_temp[it->nkey] = 0x00; /* terminate */
        len = snprintf(temp, sizeof(temp), "ITEM %s [%d b; %llu s]\r\n",
                       key_temp, it->nbytes - 2,
                       it->exptime == 0 ? 0 :
                       (unsigned long long)it->exptime + process_started);
        if (bufcurr + len + 6 > memlimit)  /* 6 is END\r\n\0 */
            break;
        memcpy(buffer + bufcurr, temp, len);
        bufcurr += len;
        shown++;
        it = it->next;
    }

    memcpy(buffer + bufcurr, "END\r\n", 6);
    bufcurr += 5;

    *bytes = bufcurr;
    pthread_mutex_unlock(&lru_locks[id]);
    return buffer;
}

可以看到:

  • 函數(shù)第一句定義了 memlimit 參數(shù)來限制大小為2M;
  • 之后通過 malloc((size_t)memlimit) 申請了名為buffer的2M空間队贱;
  • 循環(huán)獲取slab中的item直到達(dá)到2M上限色冀;
  • 最后copy到buffer中并return;

由此可以明白柱嫌,上述命令組合的方式雖然可以批量獲取key锋恬,但每個slab最大只能dump 2M,數(shù)據(jù)量超過2M則無法獲得所有的key编丘。

當(dāng)然与学,可以嘗試將源碼中的memlimit參數(shù)調(diào)大后重新編譯,但是這樣也沒法從根本上解決問題嘉抓,因?yàn)椴豢赡苊看螖?shù)據(jù)量超過后都重新編譯一次索守,而如果直接設(shè)置一個很大的值的話————cachedump會不會直接把memcached搞掛掉我也保不準(zhǔn)啊抑片!畢竟源碼中可是直接 malloc((size_t)memlimit) 一次申請了整個內(nèi)存的卵佛!

感興趣的大兄弟可以試一下,然后告訴我結(jié)果敞斋。??

二截汪、libmemcached 工具

一提起libmemcached,我就一陣胃疼植捎。不難搜到衙解,網(wǎng)上很多文章介紹說它可以dump出memcached的所有key,因此我去官網(wǎng)看了下焰枢。

官網(wǎng)上介紹如下:

memdump dumps a list of “keys” from all servers that it is told to fetch from. Because memcached does not guarentee to provide all keys it is not possible to get a complete “dump”.

看上去是那么回事丢郊,由于沒有翻墻盔沫,百度也搜不到有效的內(nèi)容,于是我下載了libmemcached的源碼決定研究下枫匾。

經(jīng)過漫長的頭疼后架诞,終于搞懂了memdump的核心部分代碼:

/*
  We use this to dump all keys.

  At this point we only support a callback method. This could be optimized by first
  calling items and finding active slabs. For the moment though we just loop through
  all slabs on servers and "grab" the keys.
*/

#include <libmemcached/common.h>

static memcached_return_t ascii_dump(Memcached *memc, memcached_dump_fn *callback, void *context, uint32_t number_of_callbacks)
{
  /* MAX_NUMBER_OF_SLAB_CLASSES is defined to 200 in Memcached 1.4.10 */
  for (uint32_t x= 0; x < 200; x++)
  {
    char buffer[MEMCACHED_DEFAULT_COMMAND_SIZE];
    int buffer_length= snprintf(buffer, sizeof(buffer), "%u", x);
    if (size_t(buffer_length) >= sizeof(buffer) or buffer_length < 0)
    {
      return memcached_set_error(*memc, MEMCACHED_MEMORY_ALLOCATION_FAILURE, MEMCACHED_AT,
                                 memcached_literal_param("snprintf(MEMCACHED_DEFAULT_COMMAND_SIZE)"));
    }

    // @NOTE the hard coded zero means "no limit"
    libmemcached_io_vector_st vector[]=
    {
      { memcached_literal_param("stats cachedump ") },
      { buffer, size_t(buffer_length) },
      { memcached_literal_param(" 0\r\n") }
    };

    // Send message to all servers
    ...
    ...
    // Collect the returned items
    memcached_instance_st* instance;
    memcached_return_t read_ret= MEMCACHED_SUCCESS;
    while ((instance= memcached_io_get_readable_server(memc, read_ret)))
    {
      memcached_return_t response_rc= memcached_response(instance, buffer, MEMCACHED_DEFAULT_COMMAND_SIZE, NULL);
      if (response_rc == MEMCACHED_ITEM)
      {
        char *string_ptr, *end_ptr;

        string_ptr= buffer;
        string_ptr+= 5; /* Move past ITEM */

        for (end_ptr= string_ptr; isgraph(*end_ptr); end_ptr++) {} ;

        char *key= string_ptr;
        key[(size_t)(end_ptr-string_ptr)]= 0;

        for (uint32_t callback_counter= 0; callback_counter < number_of_callbacks; callback_counter++)
        {
          memcached_return_t callback_rc= (*callback[callback_counter])(memc, key, (size_t)(end_ptr-string_ptr), context);
          if (callback_rc != MEMCACHED_SUCCESS)
          {
            // @todo build up a message for the error from the value
            memcached_set_error(*instance, callback_rc, MEMCACHED_AT);
            break;
          }
        }
      }
      else if (response_rc == MEMCACHED_END)
      {
        // All items have been returned
      }
      else if ...
      ...
      ...

  return memcached_has_current_error(*memc) ? MEMCACHED_SOME_ERRORS : MEMCACHED_SUCCESS;
}

代碼可能比較多谴忧,但整體還是比較清晰的:

  • 開頭一個大for循環(huán)遍歷slab沾谓,在每個slab中進(jìn)行操作均驶;
  • 先定義合適大小的變量來存放命令枫虏,下面定義vector[]存放完整的cachedump命令;
  • 往下是一個while來循環(huán)接收返回結(jié)果腾它,主要是在if (response_rc == MEMCACHED_ITEM)下的操作瞒滴;
  • 將string_ptr指針放在slab的頭部赞警,然后跳過ITEM 字符(參考cachedump輸出內(nèi)容)愧旦;
  • 接下來for循環(huán)將end_ptr指針指向key名稱的尾端(參考百度:isgraph()函數(shù));
  • 獲取key起始位置*key以及key長度end_ptr-string_ptr,通過callback結(jié)構(gòu)體獲取key名稱引颈。

看到后面步驟的時候我是激動的蝙场,這是直接操作內(nèi)存獲取了所有key售滤,厲害台诗!但是我仔細(xì)一想拉队,往上翻看了看——我去粱快,這不還是用stats cachedump命令查的么叔扼!

為了避免因?yàn)槲壹夹g(shù)渣渣而誤導(dǎo)大家,我終于想起來去stackoverflow搜了下鳍咱,果然比百度強(qiáng)得多谤辜,一搜就搜到:

https://stackoverflow.com/questions/41458274/what-does-the-one-page-per-slab-class-limitation-mean-when-dumping-keys-with-m

呵呵仅胞,虧了源碼開頭注釋還寫著 We use this to dump all keys. 我開始胃疼了干旧。

三、memcached-hack 補(bǔ)丁

memcached-hack是我無意中發(fā)現(xiàn)的一個補(bǔ)丁+工具的包挠将,將源碼按補(bǔ)丁修改重新編譯后编整,可以實(shí)現(xiàn)cachedump時指定slab中的起始位置掌测。大家可以到codegoogle上搜索下載。

有兩個版本的補(bǔ)丁和一個python寫的工具:

zhangxueyan?? PileWorld memcached-hack $ ll
total 56
-rwxr-xr-x@ 1 zhangxueyan  staff   499  8 22  2008 example.py
-rwxr-xr-x@ 1 zhangxueyan  staff  2543  8 22  2008 memcached-1.2.2-cachedump-hack
-rwxr-xr-x@ 1 zhangxueyan  staff  4949  8 22  2008 memcached-1.2.4-cachedump-hack
-rwxr-xr-x@ 1 zhangxueyan  staff  8510  8 22  2008 memcachem.py

python工具就不說了夜郁,用的也是打完補(bǔ)丁后的cachedump竞端,下面以1.2.4版本的補(bǔ)丁為例說下核心部分的改動:

Index: items.c
===================================================================
--- items.c (revision 793)
+++ items.c (working copy)
@@ -276,18 +276,23 @@
 }

 /*@null@*/
-char *do_item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes) {
+char *do_item_cachedump(const unsigned int slabs_clsid, const unsigned int start, const unsigned int limit, unsigned int *bytes) {
     unsigned int memlimit = 2 * 1024 * 1024;   /* 2MB max response size */
     char *buffer;
     unsigned int bufcurr;
     item *it;
+   int i;
     unsigned int len;
     unsigned int shown = 0;
     char temp[512];

     if (slabs_clsid > LARGEST_ID) return NULL;
     it = heads[slabs_clsid];
-
+    i = 0;
+    while (it != NULL && i < start) {
+       it = it->next;
+       i++;
+       }
     buffer = malloc((size_t)memlimit);
     if (buffer == 0) return NULL;
     bufcurr = 0;
  • do_item_cachedump函數(shù)增加start參數(shù)事富,用來指定slab中的起始位置;
  • 添加while循環(huán)雕擂,將指針 it 從默認(rèn)的slab頭部指向用戶設(shè)定的start位置饺谬,然后進(jìn)行讀取。

看起來這個方法確實(shí)可以真正dump到所有的key了族展,接下來我做了個簡單的測試仪缸。

官網(wǎng)下載了1.5.3的版本列肢,參照補(bǔ)丁進(jìn)行修改后編譯啟動,與原版的進(jìn)行了對比:

改動后的1.5.3:

# slab1 下的所有key
stats cachedump 1 0                       
ITEM xue [9 b; 1514291787 s]s s
ITEM zhang [9 b; 1514291587 s]
ITEM runoob [9 b; 1514291526 s]
END

# slab1 下拴还,從第二個key開始的所有key
stats cachedump 1 1 0                    
ITEM zhang [9 b; 1514291587 s]
ITEM runoob [9 b; 1514291526 s]
END

# slab1 下片林,從第三個key開始的所有key
stats cachedump 1 2 0
ITEM runoob [9 b; 1514291526 s]
END

對比未改動的 1.5.3 版本:

stats cachedump 1 0
ITEM runoob [9 b; 1514292460 s]
ITEM zhang [9 b; 1514292440 s]
ITEM xue [9 b; 1514292430 s]
END
stats cachedump 1 1 0
ITEM runoob [9 b; 1514292460 s]
END
stats cachedump 1 2 0
ITEM runoob [9 b; 1514292460 s]
ITEM zhang [9 b; 1514292440 s]
END

很明顯原生的版本把最后一個參數(shù)忽略了费封,還是按照 {slab_id} {limit_num} 的語法蒋伦,而改動后的版本從這個例子上來看確實(shí)是實(shí)現(xiàn)了指定位置的功能。

四韧献、結(jié)語

花了零零散散好幾天的時間來研究這個問題研叫,終于可以告一段落蓝撇,也算是有不少收獲渤昌,雖然沒有找到原生的memcached中直接dump全部的方法,但總還有個改動后的可以實(shí)現(xiàn)独柑。

當(dāng)然我們線上正使用著的目前看來是dump不能了,但是業(yè)務(wù)上目前還是有這個需求车酣,如果大家有知道原生的方法的湖员,歡迎隨時私信或評論中探討瑞驱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末唤反,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肠缨,更是在濱河造成了極大的恐慌盏阶,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吴汪,死亡現(xiàn)場離奇詭異漾橙,居然都是意外死亡霜运,警方通過查閱死者的電腦和手機(jī)蒋腮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門池摧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人膘魄,你說我怎么就攤上這事〈雌希” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵洛波,是天一觀的道長蹬挤。 經(jīng)常有香客問我闻伶,道長够话,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任畜份,我火速辦了婚禮爆雹,結(jié)果婚禮上愕鼓,老公的妹妹穿的比我還像新娘。我一直安慰自己册倒,他們只是感情好磺送,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布估灿。 她就那樣靜靜地躺著,像睡著了一般域慷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兄纺,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機(jī)與錄音钦奋,去河邊找鬼。 笑死朦拖,一個胖子當(dāng)著我的面吹牛厌衔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播睬隶,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼苏潜,長吁一口氣:“原來是場噩夢啊……” “哼变勇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起飞袋,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤巧鸭,失蹤者是張志新(化名)和其女友劉穎锣险,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巷折,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锻拘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年署拟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片心包。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡蟹腾,死狀恐怖区宇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情炉爆,我是刑警寧澤卧晓,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站衩辟,受9級特大地震影響波附,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜封寞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一狈究、第九天 我趴在偏房一處隱蔽的房頂上張望盏求。 院中可真熱鬧,春花似錦磅废、人聲如沸荆烈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至犀勒,卻和暖如春妥曲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逾一。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工遵堵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陌宿,地道東北人。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓爽蝴,卻偏偏與公主長得像蝎亚,于是被迫代替她去往敵國和親先馆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容