[提前聲明]
文章由作者:張耀峯 結合自己生產中的使用經驗整理,最終形成簡單易懂的文章
寫作不易,轉載請註明,謝謝!
spark代碼案例地址: https://github.com/Mydreamandreality/sparkResearch
線上問題
- 定時任務通過keys* 通配符匹配對應的key
- 在這段時間內的其它服務(需要用到Redis)告警,無法進行正常服務
- 在運維平臺查看日誌:服務告警這段時間內的請求全部拋出redis連接超時,服務中設置的redis超時時間爲200ms
問題定位
- 首先確定是keys命令引起的
- 而且Redis中數據量要特別大,否則是不會觸發這個bug的,之前數據量小的時候根本沒有這個問題
- 其次
keys
命令本身在redis中數據量特別大的情況下(百萬及以上)就會特別慢 - 最致命的是這個命令會阻塞redis多路複用的io主線程,如果這個線程被阻塞了,在這個期間其它服務到redis的請求會全部被阻塞,導致一系列反應,響應卡頓,連接超時等等
問題解決
- 在線上環境,這種會阻塞主線程的操作,並且算法時間複雜度是O(n)的就應該禁止使用
替代方案
- redis.scan
scan如何解決線上問題
- scan和keys都是O(n)複雜度
- scan同樣支持通配字符模糊查找
- scan不會阻塞主線程
- scan支持遊標按批次迭代返回數據
注:scan返回的數據有可能會重複,需要客戶端主動去重
scan注意事項
- scan命令的遊標從0開始從0結束
- scan命令每次返回的數據都會攜帶下次遊標所需的值,根據這個值再進行下一次的訪問,如果返回的數據爲空,不一定是沒有數據了,需要通過遊標來確認
scan命令使用案例
scan 0 match my*key count 10000
JavaRedisApi-scan命令使用案例
public Set<String> scan(String key, long count) {
Set<String> keys = redisTemplate.execute(
(RedisCallback<Set<String>>) connection -> {
Set<String> keyTmp = new HashSet<>();
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(key + "*").count(count).build());
while (cursor.hasNext()) {
keyTmp.add(new String(cursor.next()));
}
return keyTmp;
});
return keys;
}
迴歸測試結果
- 服務正常執行