很多同學可能熟知Memcached的LRU淘汰算法,它是在slab内部進行的,如果所有空間都被slabs配置設定,即使另外一個slab裡面有空位,仍然存在踢資料可能。你可以把slab了解為教室,如果你的教室滿了,即使别的教室有空位你的教室也隻能踢人才能進人。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuQTNxgTM2QDNx8CX0ADMxAjMvwFduVWboNWY0RXYvwVbvNmLvR3YxUjL4M3Lc9CX6MHc0RHaiojIsJye.png)
本文介紹的卻是另外一種現象。今天監控發現線上一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是從通路隊列尾部直接踢資料。
為了證明想法,設計以下場景:
- 部署一個memcached測試環境,配置設定比較小的記憶體,比如8M
- 設定1條永遠不過期的資料到memcached中,然後再get一次,這條資料後續應該存在LRU隊尾。
- 每隔1S向memcached set(并get一次) 1,000條資料,過期時間設為3秒。
- 一段時間後,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>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。