天天看點

Memcached資料被踢(evictions>0)現象分析

很多同學可能熟知Memcached的LRU淘汰算法,它是在slab内部進行的,如果所有空間都被slabs配置設定,即使另外一個slab裡面有空位,仍然存在踢資料可能。你可以把slab了解為教室,如果你的教室滿了,即使别的教室有空位你的教室也隻能踢人才能進人。

Memcached資料被踢(evictions>0)現象分析

本文介紹的卻是另外一種現象。今天監控發現線上一memcached發生資料被踢現象,用stats指令看evictions>0,因為以前也出現過此問題,後來對這個參數增加了一個監控,是以這次主動就發現了。由于給memcached配置設定的記憶體遠大于業務存儲資料所需記憶體,是以初步判斷是“靈異現象”。

第一步,netstat檢視所有連接配接,排除是否被一些未規劃的client使用,經排查後斷定無此可能。

第二步,用tcpdump抽樣檢查set的指令,排除是否有忘記設cache過期時間的client,初步檢查所有典型的業務都有expire time。

第三步,Google,未果

第四步,看源代碼,了解evictions計數器增加時的具體細節,oh, no…

in items.c, memcached-1.2.8,

125         for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) {
126             if (search->refcount == 0) {
127                 if (search->exptime == 0 || search->exptime > current_time) {
128                     itemstats[id].evicted++;
129                     itemstats[id].evicted_time = current_time - search->time;
130                     STATS_LOCK();
131                     stats.evictions++;
132                     STATS_UNLOCK();
133                 }
134                 do_item_unlink(search);
135                 break;
136             }
137         }      

從源代碼發現踢資料隻判斷一個條件,if (search->refcount == 0),這個refcount是多線程版本計數用,在目前伺服器未啟用多線程情況下,refcount應該始終為0,是以初步判斷memcached是從通路隊列尾部直接踢資料。

為了證明想法,設計以下場景:

  1. 部署一個memcached測試環境,配置設定比較小的記憶體,比如8M
  2. 設定1條永遠不過期的資料到memcached中,然後再get一次,這條資料後續應該存在LRU隊尾。
  3. 每隔1S向memcached set(并get一次) 1,000條資料,過期時間設為3秒。
  4. 一段時間後,stats指令顯示evictions=1

按我以前的了解,第2步的資料是永遠不會被踢的,因為有足夠過期的資料空間可以給新來的資料用,LRU淘汰算法應該跳過沒過期的資料,但結果證明這種判斷是錯誤的。以上業務的伺服器發生被踢的現象是由于儲存了大量存活期短的key/value,且key是不重複的。另外又有一業務儲存了小量不過期的資料,是以導緻不過期的資料慘遭被擠到隊列踢出。

本來這個問題就告一段落了,但在寫完這篇文章後,順便又看了新一代memcached 1.4.1的源代碼,很驚喜發現以下代碼被增加。

items.c, memcached 1.4.1

107     /* do a quick check if we have any expired items in the tail.. */
108     int tries = 50;
109     item *search;
110
111     for (search = tails[id];
112          tries > 0 && search != NULL;
113          tries--, search=search->prev) {
114         if (search->refcount == 0 &&
115             (search->exptime != 0 && search->exptime < current_time)) {
116             it = search;
117             /* I don't want to actually free the object, just steal
118              * the item to avoid to grab the slab mutex twice 
        
Memcached資料被踢(evictions&gt;0)現象分析
119              */ 120             it->refcount = 1; 121             do_item_unlink(it); 122             /* Initialize the item block: */ 123             it->slabs_clsid = 0; 124             it->refcount = 0; 125             break; 126         } 127     }

重複進行上述測試,未發生evictions。

  • 過期的資料如果沒被顯式調用get,則也要占用空間。
  • 過期的不要和不過期的資料存在一起,否則不過期的可能被踢。
  • 從節約記憶體的角度考慮,即使資料會過期,也不要輕易使用随機字元串作為key,盡量使用定值如uid,這樣占用空間的大小相對固定。
  • 估算空間大小時候請用slab size計算,不要按value長度去計算。
  • 不要把cache當作更快的key value store來用, cache不是storage。