在平時我們維護線上Redis的時候需要從n個key裏面找到某些特定規則的key,可能查看某些key可能清理某些不需要的key,可能我們第一印象就是keys這個指令,我們可以使用這個指令匹配我們想要的key,下面我們來試試。
keys指令基本用法
> mset name1 a name2 a na1me a na2me a
OK
> keys name*
1) "name"
2) "name2"
3) "name1"
> keys na*me
1) "name"
2) "na1me"
3) "na2me"
這個指令使用非常簡單,但是還是存在非常多的問題:
-
沒有一個類似獲取某幾條或某個區間的搜索,比如我只想要搜索第一條,比如我想分頁搜索獲取第一頁的10條數據。
-
keys的方法是遍歷整個redis樹,執行效率是O(n),所以當我們搜索的key非常龐大的時候。必須等到這個指令執行完後才能執行其他指令,redis搜索會延遲,這樣會阻塞其他指令或者延遲等問題。
所以針對這個問題,我們的Redis在2.8版本提出了scan這個指令,那麼這個指令有哪些特色呢?
-
複雜度雖然也是O(n),但它是通過遊標分步進行的,不會阻塞線程 。
-
提供limit參數,可以控制每次返回結果的最大條數,limit只是個hint,返回的結果可多可少。
-
同keys一樣,它也提供模式匹配功能。
-
服務器不需要爲 遊標保存狀態 ,遊標的唯一狀態就是 scan 返回給客戶端的遊標整數。
-
返回的結果可能會有重複,需要客戶端去重,這點非常重要。
-
遍歷的過程中如果有數據修改,改動後的數據能不能遍歷到是不確定的。
-
單次返回的結果是空的並不意昧着遍歷結束,而要看返回的遊標值是否爲零。
scan指令基本用法
..........漫長的添加數據
> scan 0 match name* count 10
1) "30"
2) 1) "name3"
2) "name9"
3) "name12"
4) "name"
5) "name6"
6) "name8"
7) "name11"
8) "name4"
9) "name2"
10) "name7"
> scan 30 match name* count 10
1) "0" #遊標爲0,沒有數據了
2) 1) "name10"
2) "name5"
3) "name1"
scan遍歷原理
首先我們介紹redis整個模型,redis簡單來講其實就是一個大的字典,關於字典不瞭解的同學可以查看我的redis字典基礎。字典是由一維數組+鏈表構成,那麼我們把這個一維數組叫做槽當然也有叫桶的,那麼scan遍歷的時候就是遍歷這個槽,那麼在上面我們發現有時候我們想返回10條記錄,但是redis返回的並沒有10條,這是因爲我們的scan遍歷槽,每個槽下面不一定有鏈表或者鏈表值不一定只有一個,所以我們在有些時候使用scan返回的數據是不規則的就是這個原因。
關於scan的遍歷順序
scan的遍歷順序和我們普通的順序不同,普通方式就是從0->1->2->3....這樣遍歷下去,而scan的遍歷規則是反過來的,這樣的特殊遍歷方式是考慮到字典擴容和內存回收字典收縮時避免槽位重複或者衝突。scan指令擴展
scan指令是一系列指令,除了可以遍歷所有的key之外,還可以對指定的容器
集合進行遍歷。
比如zscan遍歷zset集合元素,hscan遍歷hash字典的元素,sscan遍歷set集合的元素。
它們的原理同scan類似,因爲hash底層就是字典,set也是一個特殊的hash(所有的value指向同一個元素,zset內部也使用了字典來存儲所有的元素內容。
避免大key掃描
比如我們在操作hash或者zset的時候,它的底層實現是一個字典,我們知道當字典的值到達一定程度的時候就會擴容,每次擴容的時候都會開闢一個更大的內存空間,如果在集羣環境中,這個key或被遷移或被刪除會影響系統性能導致系統卡頓現象,我們在運維redis系統的時候如果說redis內存大起大落的很有可能就是大key回收導致的。
那麼我們在開發過程中如何避免呢?這裏我們就需要用到scan指令了,對於掃描出來的每一個key,使用type指令獲得key的類型,然後使用相應數據結構的size或者len方法來得到它的大小,對於每一種類型,將大小排名的前若干名作爲掃描結果展示出來。
上面這樣的過程需要編寫腳本,比較煩瑣,不過Redis官方已經在redis-cli指令
redis-cli -h 127.0.0.1 -p 7001 --bigkeys -i 0.1
一名正在搶救的coder
筆名:mangolove
CSDN地址:https://blog.csdn.net/mango_love
GitHub地址:https://github.com/mangoloveYu