目錄
- Redis內(nèi)存回收
- 問題原由
- 遍歷清除垃圾鍵
- 如何遍歷鍵
- 如何判斷鍵是否垃圾
- 獲取鍵大小
- 管道加速
- 腳本實(shí)現(xiàn)
- 從根源避免問題
- 小結(jié)
Redis內(nèi)存回收
Redis 服務(wù)器的最大占用內(nèi)存量由配置項(xiàng) maxmemory 決定,我們可以通過 config set maxmemory 2GB 的格式來配置。一旦 Redis 內(nèi)存滿,所有引起內(nèi)存增加的操作都會(huì)被返回 error。作為專業(yè) Redis 服務(wù)器我們通常將此項(xiàng)設(shè)置為0,以服務(wù)器系統(tǒng)內(nèi)存來作為限制;
那么 Redis 使用內(nèi)存達(dá)到了上限怎么辦?Redis 為我們提供了幾種選項(xiàng)以自動(dòng)回收內(nèi)存,可以通過配置項(xiàng) maxmemory-policy 來配置;
- noeviction 不回收;
- allkeys-lru 從所有鍵中刪除最近最少使用的鍵;
- volatile-lru 從設(shè)置了過期時(shí)間的鍵中刪除最近最少使用的鍵;
- allkeys-random 從所有鍵中隨機(jī)刪除;
- volatile-random 從設(shè)置了過期時(shí)間的鍵中隨機(jī)刪除;
- volatile-ttl 從設(shè)置了過期時(shí)間的鍵中選擇存活時(shí)間最短的鍵刪除;
最大內(nèi)存回收策略需要根據(jù)業(yè)務(wù)來配置,如果純粹做緩存,allkeys-lru無疑是最合適的。如果存儲(chǔ)了稍微重要的數(shù)據(jù),為了防止 Redis 誤刪一些重要鍵,則需要選用 noeviction;
allkeys-lru、allkeys-random 在內(nèi)存滿時(shí)都有鍵可刪,可以騰出內(nèi)存,但如果配置了其他的策略,數(shù)據(jù)庫用久了(根據(jù)業(yè)務(wù)量),隨著業(yè)務(wù)發(fā)展和數(shù)據(jù)積累,通常會(huì)累積到到服務(wù)器內(nèi)存占用率高,利用率低的情況,則可能會(huì)遇到內(nèi)存占用滿的問題。
問題原由
產(chǎn)生問題的原因有:
持久鍵廢棄
這是導(dǎo)致此問題的最常見情況。
有時(shí)候是開發(fā)人員的鍋,開發(fā)不規(guī)范,未給有時(shí)效性的鍵設(shè)置過期時(shí)間,后續(xù)又不進(jìn)行手動(dòng)刪除,鍵就成為無人管的孤兒鍵了。
還可能是整個(gè)業(yè)務(wù)慢慢被廢棄,不知道哪一天起,業(yè)務(wù)整體已不再維護(hù)了,一批鍵自然也就沒用了。比這更嚴(yán)重的是,如果使用 List 傳遞數(shù)據(jù),消費(fèi)進(jìn)程已被停止,但生產(chǎn)進(jìn)程未同步停止,還在往 Redis 里寫數(shù)據(jù)。
過期鍵未回收
這個(gè)原因首先要談到 Redis 的兩種過期鍵刪除策略:
- 惰性刪除:在讀取鍵時(shí)發(fā)現(xiàn)鍵已過期,則將其刪除。
- 定期刪除:Redis 會(huì)從所有設(shè)置了過期時(shí)間的鍵中選取 100 個(gè),刪除已過期的鍵,如果已過期的鍵超過 25 個(gè),則再次進(jìn)行此操作。 此刪除操作由配置項(xiàng) hz 決定,Redis 默認(rèn)每秒進(jìn)行 10 次;
如果我們產(chǎn)生過期鍵的速度很快,最多可導(dǎo)致 Redis 25% 的過期鍵沒有被及時(shí)刪除。
遍歷清除垃圾鍵
由上,明白了問題產(chǎn)生的原因,解決 Redis 內(nèi)存滿的方法就明確了:清除這些垃圾鍵。 于是就面臨著兩個(gè)問題:
如何遍歷鍵
對于查找鍵,我們首先想到的是 KEYS,但 KEYS 的時(shí)間復(fù)雜度是O(n),n 是 Redis 內(nèi)鍵的總數(shù),如果 Redis 內(nèi)鍵很多還是會(huì)有性能問題,導(dǎo)致其他命令被阻塞的。
這里介紹一個(gè)鍵遍歷命令: SCAN。
SCAN cursor:
0 => cursor, // cursor = 0 遍歷結(jié)束
1 => array(key1, key2...)
需要注意的是 SCAN 命令是在版本2.8.0 加入的,如果是之前的版本,可以考慮解析 Redis 的 RDB 文件來獲取所有的鍵。
如何判斷鍵是否垃圾
我們有三種異常鍵需要處理:
- 過期鍵:這些鍵會(huì)在被 SCAN 到時(shí)被自動(dòng)刪除,不再考慮。如果是解析 RDB 文件獲取到的鍵,在查詢時(shí)也會(huì)被自動(dòng)刪除;
- 長時(shí)間未讀寫的鍵,很可能是業(yè)務(wù)不再需要的鍵;
- 占用大量內(nèi)存的鍵,有可能是在不停地寫,但未消費(fèi)。
這里介紹 Redis 的另一個(gè)命令 OBJECT,使用它可以從內(nèi)部查看 key 對象的狀態(tài)。使用 OBJECT IDLETIME key 來獲取 key 的閑置時(shí)間,我們可以判斷 key 閑置時(shí)間大于一個(gè)時(shí)間段(根據(jù)業(yè)務(wù)自定)的為已廢棄。
此外還能使用 OBJECT REFCOUNT key獲取 key 引用所儲(chǔ)存的值的次數(shù),OBJECT ENCODING key 獲取 key 儲(chǔ)存的值所使用的內(nèi)部表示。
獲取鍵大小
而獲取 Redis 某鍵占用內(nèi)存大小,則通過另一個(gè)命令 DEBUG OBJECT 來獲取,此命令會(huì)返回比OBJECT命令更詳細(xì)的內(nèi)部數(shù)據(jù)。
DEBUG OBJECT test
Value at:0x7fb0ee16ebd0 refcount:1 encoding:embstr serializedlength:6 lru:12362780 lru_seconds_idle:4
結(jié)果包括內(nèi)存地址、引用數(shù)、內(nèi)部編碼表示、序列化后的長度、最近最少使用標(biāo)識值,閑置時(shí)間,我們可以解析此結(jié)果串來獲取對應(yīng)的數(shù)據(jù)。
需要注意,key 作為復(fù)合鍵擁有大量字段時(shí)使用 DEBUG 命令計(jì)算內(nèi)存會(huì)使 Redis 阻塞較長時(shí)間,且 Redis 官方并不建議在客戶端使用此命令。
我們也可以先使用 TYPE key 獲取鍵的類型,再根據(jù)類型獲取其鍵的大小,如對字符串使用LEN,對 哈希表使用HLEN。
要注意在刪除特別大的復(fù)合鍵時(shí),建議先逐步清空鍵內(nèi)的字段,防止因字段過多,Redis 阻塞較長時(shí)間。
管道加速
Redis 支持 pipeline 管道技術(shù),一次 請求/響應(yīng) 服務(wù)器能實(shí)現(xiàn)處理并響應(yīng)多個(gè)請求。這樣就可以將多個(gè)命令同時(shí)發(fā)送到服務(wù)器,不等待回復(fù),直接在最后獲取多個(gè)結(jié)果。
PHP 中使用 MULTI(Redis::PIPELINE) 和 EXEC() 命令來實(shí)現(xiàn)管道;
腳本實(shí)現(xiàn)
下面是個(gè)簡單的腳本:
$redis = new Redis();
$redis->connect('127.0.0.1');
do {
$keys = $redis->scan($cursor);
$pipeline = $redis->multi(Redis::PIPELINE);
foreach ($keys as $key) {
$idle_time = $redis->object('idletime', $key);
if ($idle_time > 180 * 24 * 3600) {
$pipeline->del($key);
}
// todo 判斷類型進(jìn)而判斷占用內(nèi)存大小,再刪除
}
$pipeline->exec();
} while ($cursor != 0);
從根源避免問題
以上的腳本肯定也會(huì)在刪除鍵時(shí)影響 Redis 的效率,最好的情況還是從根源就避免此類情況,以下是一些建議:
- 規(guī)范化開發(fā);
- 首先是鍵命名要規(guī)范,讓人見名知義,這樣在人工排錯(cuò)或刪除時(shí)也有判斷依據(jù),然后最好有完善的 Redis 鍵文檔,以保證業(yè)務(wù)在很長時(shí)間,經(jīng)手多人后也能資料可查。
- 使用 HashSet 替代 Key-Value;
- 將業(yè)務(wù)中某一族的鍵以 HashSet 的方式存儲(chǔ),以替代普通的 key-value 類型。不僅可以省去為每個(gè)鍵設(shè)置前綴以節(jié)約內(nèi)存,也便于統(tǒng)一管理。
- 有時(shí)效性的鍵注意設(shè)置過期時(shí)間;
- 合理設(shè)置定時(shí)清除過期鍵頻率 hz,在 Redis 不做多余操作的情況下,使過期鍵盡量能被刪除;
- 做好 Redis 內(nèi)存的監(jiān)控,在達(dá)到某個(gè)閾值時(shí)查找問題并解決。
小結(jié)
Redis假死
我在使用守護(hù)進(jìn)程時(shí) Redis 有假死情況,PHP 和 Redis 都不報(bào)錯(cuò),但命令都返回 false,這種情況可以使用 Redis 的 ping() 命令,來探測 Redis 連接是否還在,如果不在則再建立新的連接。此問題很可能是由服務(wù)器配置引起的,如果您有知道此問題的原由或有好的解決辦法,煩請指點(diǎn)一二。
危險(xiǎn)命令
不要在沒看文檔的情況下在線上使用 Redis 命令,例如 debug segfault,別問我怎么知道的。
以上就是詳解Redis瘦身指南的詳細(xì)內(nèi)容,更多關(guān)于Redis瘦身指南的資料請關(guān)注腳本之家其它相關(guān)文章!
您可能感興趣的文章:- 基于Docker搭建Redis主從集群的實(shí)現(xiàn)
- redis實(shí)現(xiàn)共同好友的思路詳解