SCAN cursor [MATCH pattern] [COUNT count] SSCAN KEY cursor [MATCH pattern] [COUNT count] HSCAN KEY cursor [MATCH pattern] [COUNT count] ZSCAN KEY cursor [MATCH pattern] [COUNT count]
scan:迭代當前庫redis
sscan:迭代一個 set 類型數組
hscan:迭代一個hash類型,並返回相應的值服務器
zscan:迭代一個sorted set,而且返回相應的分數函數
redis是單進程單線程模型,keys和smembers這種命令可能會阻塞服務器,因此出現了scan系列的命令,經過返回一個遊標,能夠增量式迭代.性能
scan,sscan,hscan,zsan分別有本身的命令入口,入口中會進行參數檢測和遊標賦值,而後進入統一的入口函數:scanGenericCommand,以hscan命令爲例:spa
scanGenericCommand主要分四步:線程
遊標在保存爲hash的時候發揮做用,具體入口函數爲dictScan,下文詳細描述。設計
當迭代一個哈希表時,存在三種狀況:code
redis中進行rehash時會存在兩個哈希表,ht[0]與ht[1],而且是漸進式rehash(即不會一次性所有rehash);新的鍵值對會存放到ht[1]中而且會逐步將ht[0]的數據轉移到ht[1].所有rehash完畢後,ht[1]賦值給ht[0]而後清空ht[1].blog
所以遊標的實現須要兼顧以上三種狀況,以上三種狀況的遊標實現要求以下:
假設bucket0讀完以後返回了遊標1,當客戶端再次帶着遊標1返回時哈希表已經進行完rehash,而且size擴大了一倍變成了8.redis按以下方法計算一個鍵的bucket:
hash(key)&(size-1)
即若是size是4時,hash(key)&11,若是size是8時,hash(key)&111.所以當從4擴容到8時,原先在0bucket的數據會分散到0(000)與4(100)兩個bucket,bucket對應關係表以下:
從二進制來看,當size爲4時,hash(key)以後取低兩位即 hash(key)&11即key的bucket位置,若是size爲8時,bucket位置爲 hash(key)&111,即取低三位,當低兩位爲00時,若是第三位爲0,則爲000,若是第三位爲1,則爲100,正好是4.其餘槽位的相似.因此若是此時繼續按第一種方法遍歷,第四個bucket取到的值所有爲重複值
因此爲了兼顧以上三種狀況,作到不漏數據而且儘可能不重複,redis使用了一種叫作reverse binary iteration的方法.具體的遊標計算代碼以下:
代碼邏輯很簡單,下面示例從4變爲8和從4變爲16以及從8變爲4和從16變爲4時,這種方法爲什麼可以作到不重不漏
遍歷size爲4時的遊標狀態轉移爲0-2-1-3.
同理,size爲8時的遊標狀態轉移爲0-4-2-6-1-5-3-7.
size爲16時的遊標狀態轉義爲0-8-4-12-2-10-6-14-1-9-5-13-3-11-7-15
能夠看出,當size由小變大時,全部原來的遊標都能在大的hashTable中找到相應的位置,而且順序一致,不會重複讀取而且不會遺漏
例如size原來是4變爲了8,且第二次遍歷時rehash已經完成.此時遊標爲2,根據圖2,咱們知道size爲4時的bucket2會rehash到size爲8時的2和6.而size爲4時的bucket0rehash到size爲8時的0和4
因爲bucket 0 已經遍歷完,也即size爲8時的0,4已經遍歷,正好開始從2開始繼續遍歷,不重複也不會遺漏
繼續考慮size由大變小的狀況.假設size由16變爲了4,分兩種狀況,一種是遊標爲0,2,1,3中的一種,此時繼續讀取,也不會遺漏和重複
但若是遊標返回的不是這四種,例如返回了10,10&11以後變爲了2,因此會從2開始繼續遍歷.但因爲size爲16時的bucket2已經讀取過,而且2,10,6,14都會rehash到size爲4的bucket2,因此會形成重複讀取
size爲16時的bucket2。即有重複但不會遺漏
總結一下:redis裏邊rehash從小到大時,scan系列命令不會重複也不會遺漏.而從大到小時,有可能會形成重複但不會遺漏.
截止目前,狀況1和狀況2已經比較完美的處理了。狀況3看看如何處理
狀況3須要從ht[0]和ht[1]中都取出數據,主要的難點在於如何在size大的哈希表中找到應該取哪些bucket.redis代碼以下:
判斷條件爲:
v&(m0^m1)
size 4的m0爲00000011,size8的m1爲00000111,兩者異或以後取值爲00000100,即取兩者mask高位的值,而後&v,看遊標是否在高位還有值
下一個遊標的取值方法爲
v = ( ((v | m0) +1)& ~m0) | ( v & m0)
右半部分 取v的低位,左半部分取v的高位。 (v&m0)取出v的低位 例如size = 4時爲 v&00000011
左半部分 (v|m0) + 1即將v的低位都置爲1,而後+1以後會進位到v的高位,再次 & ~m0以後即取出了v的高位
總體來看每次將遊標v的高位加1.下邊舉例來看:
假設遊標返回了2,而且正在進行rehash,此時size由4變成了8 .則m0 = 00000011 v = 00000010
根據公式計算出的下一個遊標爲 ( (( 00000010|00000011) +1 ) & (11111100) )| (00000010 & 00000011) = (00000100)&(11111100)|(00000010) = (00000110) 正好是6
判斷條件爲 (00000010) & (00000011 ^ 00000111) = (00000010) & (00000100) = (00000000) 爲0,結束循環