標籤:
一、背景
redis慢日誌分析平台上線後,隨便看了一下,發現onestore使用的緩衝叢集,存在大量的EXISTS命令慢查詢的情況:
平均每個EXISTS命令需要13ms,最大耗時近20ms。這個結果很不科學啊,EXISTS命令只是執行一次hash尋找操作,應該是us層級。
和相關同學瞭解業務背景如下:
- 業務是userfeed,存放使用者發表的動態
- 使用zset儲存一個使用者發表的所有動態,key是使用者id,集合中對應的是feedid。如果使用者發表的動態很多,zset也很大
- redis叢集作為onestore的緩衝,到期時間是10分鐘
- 在訪問cache前會調用EXISTS查看是否命中,如果不命中就用onestore回填cache
由於一些使用者發表的動態很多(2W+),所以存在很多的ZADD慢查詢。
二、排查
1. redis的清除到期key的策略
- 被動方式:在事件迴圈中,每秒執行約10次,儘力刪除到期的key,會有漏掉的情況
- 從expire set中隨機檢查20個key
- 刪除到到期的key
- 如果超過25%的key都是到期了,就重複第一步(超過25%說明到期的key佔比很多)
- 主動方式:
- 如果該key在被動方式中漏過,在其再次被訪問時檢查並清除
2. 查看代碼
void existsCommand(redisClient *c) { expireIfNeeded(c->db,c->argv[1]); // 檢查該key是否到期,如果到期就delete掉 if (dbExists(c->db,c->argv[1])) { addReply(c, shared.cone); } else { addReply(c, shared.czero); }}在EXISTS命令處理函數中實現了清除到期key的主動策略,會先調用expireIfNeeded函數檢查要訪問的key是否到期,如果到期就delete掉這個key。del命令在刪除元素很多的複合資料型別(list、hash、zset、set)時是一個很耗時的操作。由於存在元素很多的zset,和ZADD一樣,在刪除zset時需要一個一個遍曆所有元素,時間複雜度是大O(n)。由於這個刪除操作在EXISTS命令的處理函數中執行,所以導致EXISTS耗時過長。
3. redis慢日誌驗證
通過慢日誌可以驗證上述結論
- EXISTS先檢查‘user:94479529:feed’是否存在,該key已經到期,觸發主動到期機制,將該key刪除
- 從onestore擷取該key的資料,然後通過ZADD回填
三、風險
該叢集單個執行個體qps達到8k+,同時每天有10W+的慢查詢。在redis存在大量慢查詢時,會存在個別用戶端逾時的情況,導致請求失敗。
四、後續處理
1. 增加到期時間,由10分鐘到20分鐘
2. 對redis叢集擴容(試增加到期時間的效果而定)
對於redis刪除大key耗時的問題,redis作者提供瞭解決方案,具體就是使用非同步線程對大key進行刪除操作,避免阻塞主線程。
Redis EXISTS命令耗時過長case排查