Redis之scan


【場景】
生產環境沒有對外放開端口,所以在本機Windows、Macos環境下下載的客戶端沒辦法使用,只能在Linux下查看當前的redis使用情況。此時我們可以用redis提供的客戶端redis-cli進行操作:

此時我們可以用dbsize查看redis中有多少個key,用info keyspace查看各庫的使用情況,用info memory查看內存的狀況。同時也可以用keys和scan來查看key的情況。keys只能在我們很清楚查找的內容很少時使用,如果keys *查出來有幾千萬條數據,對於客戶端來說我們很難一次性處理這么多信息;對於服務器來說,這種規模的查找會因為O(n)的復雜度造成服務器卡頓。因為Redis是單線程程序,順序執行所有指令,所以其它客戶端的指令就只能等待。
【scan指令的使用】
提供了limit參數,我們可以控制返回數據的規模,要注意的是,返回的結果可能會有重復,需要客戶端去重。
首先插入100條數據,如圖:

而后我們用scan這個指令去查找:

當這個指令返回的數字標號為0時,說明已經遍歷完成。注意如果返回的是empty list or set時,只要標號不為0,那么遍歷就還沒有結束。
【一個規律】
為了后面描述字典的結構和scan遍歷跳轉的原則便於理解,這里講一個規律。
任何自然數與2^n進行求余(模)運算,最終的結果其實跟這個數字和(2^n - 1)進行位'&'運算的結果是相同的。這是因為,對2^n求余或者說mod運算的本質,是找到這個數字在除開2^n整數倍之后,還能剩下的數字是幾,那么對於2^n - 1來說,剛好就是比2^n小,且二進制全為1,這個全為1的二進制數,和這個自然數進行'&'運算可以真實的復刻出自然數中比2^n小的位還剩多少(對於大於2^n的高位來說,其實都是2^n的整數倍,在和全1的低位進行與的過程中,這一部分數據都會歸0),兩者本質相同。如下圖描述了14005對8進行求余運算,結果其實和14005與7的'&'運算結果一致:

 

【擴容與縮容】
首先,當我們有一個長度為8的數組,我們需要把一大堆元素存儲在這個長度為8的數組上,怎么辦呢?最簡單的辦法就是求余,因為任何一個數字,對8進行求余的結果,必定為0-7的任何一個數,這個時候我們可以根據這個余數安排這些元素放在數組上的位置。如果兩個元素對8求與的結果是相同的,比如11和19,對8求余的結果都為3(二進制為011),那么我們可以把11和19都放在3這個元素的位置上,這種現象我們可以叫做元素的碰撞。這兩個元素在3這個位置將會由一個鏈表進行維護。

此時,如果這個長度為8的數組進行了擴容,長度增加一倍,變為16,再用對16求余的辦法對現有的元素重新進行位置擺放,這個過程我們叫做rehash,那么11和19的位置又在哪里呢?對16求余我們發現,他們的余數為11和3,注意換算為二進制就是1011和0011,那么在新的數組結構中,11和19就會被安放在11和3這兩個位置上。
而縮容的過程,與上述相反,也就是之前在1011和0011這兩個位置上的元素又會合並到0011這個位置上。
【scan的遍歷策略以及原因】
scan遍歷正是利用了上述的規律和hash的規律,找到了一種獨特的遍歷路徑,使得scan在遍歷過程中,無論是發生擴容還是縮容,都盡量避免了歷史鏈表元素的重復遍歷和完全避免了遺漏。
這種策略不同於我們一般對數組進行從0到length-1的遍歷,也不同於從length-1到0的遍歷,而是采用二進制高位反向進位加法,計算下一次遍歷的槽點。如圖:

而理解了上述的兩個規律,我們會發現,這種高位進位法就能夠保證在擴容或者縮容時達到完全避漏盡量不重的效果:

如圖,假如scan完成了原數組10位的所有元素遍歷,此時發生了數組的擴容,形成了下方的數組結構,那么按照scan的遍歷策略,他就會去遍歷原數組01位置,即現數組001位置,也就是說,它遍歷的下一個槽點沒有因為數組的擴容而發生變化。
這里需要說明的是,redis是單線程的,擴容和縮容是發生在本次scan結束之后,所以對於擴容來說,遍歷歷史的所有數據結果不會因為這個擴容而發生改變。
對於縮容來說,如果此時遍歷完了010的所有元素,此刻發生了縮容,那么原本即將遍歷的110的槽點變成了新數組的10位置(去掉高位),就會重復遍歷原010位置的元素,所以說會造成一定的重復。所以對於縮容來說,是會有少量的重復元素,但量占整體元素的比例應該是很低的。
【漸進式rehash】
Java的 HashMap在擴容時會一次性將舊數組下掛接的元素全部轉移到新數組下面。如果HashMap中元素特別多,線程就會出現卡頓現象。Redis為了解決這個問題,采用“漸進式rehash”。
它會同時保留舊數組和新數組,然后在定時任務中以及后續對 hash 的指令操作中漸漸地將舊數組中掛接的元素遷移到新數組上。這意味着要操作處於rehash中的字典,需要同時訪問新舊兩個數組結構。如果在舊數組下面找不到元素,還需要去新數組下面尋找。
scan也需要考慮這個問題,對於rehash中的字典,它需要同時掃描新舊槽位,然后將結果融合后返回給客戶端。
【系列指令】
scan指令是一系列指令,除了可以遍歷所有的key之外,還可以對指定的容器集合進行遍歷。比如zscan遍歷zset集合元素,hscan遍歷 hash字典的元素,sscan遍歷set集合的元素。
【避免大key】
出現大key,會造成內存分配與回收時服務的卡頓,對集群遷移也會造成卡頓,所以在平時的業務開發中,要盡量避免大key的產生。
如果你觀察到Redis 的內存大起大落,這極有可能是因為大key導致的,這時候你就需要定位出具體是哪個key,進一步定位出具體的業務來源,然后再改進相關業務代碼設計。
對於大key的量化定義:
·一個STRING類型的Key,它的值為5MB(數據過大)
·一個LIST類型的Key,它的列表數量為20000個(列表數量過多)
.一個ZSET類型的Key,它的成員數量為10000個(成員數量過多)
·一個HASH格式的Key,它的成員數量雖然只有1000個但這些成員的value總大小為100MB(成員體積過大)
如圖,可以使用指令進行大key的查詢:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM