日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis scan命令原理

發布時間:2025/3/18 数据库 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis scan命令原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

scan類型命令

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:迭代當前庫

sscan:迭代一個 set 類型

hscan:迭代一個hash類型,并返回相應的值

zscan:迭代一個sorted set,并且返回相應的分數

redis是單進程單線程模型,keys和smembers這種命令可能會阻塞服務器,所以出現了scan系列的命令,通過返回一個游標,可以增量式迭代.

scan類型命令的實現

scan,sscan,hscan,zsan分別有自己的命令入口,入口中會進行參數檢測和游標賦值,然后進入統一的入口函數:scanGenericCommand,以hscan命令為例:


scanGenericCommand主要分四步:

  • 解析count和match參數.如果沒有指定count,默認返回10條數據
  • 開始迭代集合,如果是key保存為ziplist或者intset,則一次性返回所有數據,沒有游標(游標值直接返回0).由于redis設計只有數據量比較小的時候才會保存為ziplist或者intset,所以此處不會影響性能.

游標在保存為hash的時候發揮作用,具體入口函數為dictScan,下文詳細描述。

  • 根據match參數過濾返回值,并且如果這個鍵已經過期也會直接過濾掉(redis中鍵過期之后并不會立即刪除)
  • 返回結果到客戶端,是一個數組,第一個值是游標,第二個值是具體的鍵值對

dictScan中游標的實現

當迭代一個哈希表時,存在三種情況:

  • 從迭代開始到結束,哈希表沒有進行rehash
  • 從迭代開始到結束,哈希表進行了rehash,但是每次迭代時,哈希表要么沒開始rehash,要么已經結束了rehash
  • 從迭代開始到結束,某次或某幾次迭代時哈希表正在進行rehash

redis中進行rehash時會存在兩個哈希表,ht[0]與ht[1],并且是漸進式rehash(即不會一次性全部rehash);新的鍵值對會存放到ht[1]中并且會逐步將ht[0]的數據轉移到ht[1].全部rehash完畢后,ht[1]賦值給ht[0]然后清空ht[1].

因此游標的實現需要兼顧以上三種情況,以上三種情況的游標實現要求如下:

  • 第一種情況比較簡單,假設redis的哈希表大小為4,則第一次游標為0,讀取第一個bucket的數據,然后游標返回1,下次讀取第二個bucket的位置,依次遍歷
  • 第二種情況比較復雜,假設redis的哈希表大小為4,如果rehash完后size變成了8.如果仍然按照上邊的思路返回游標,則如下圖:

假設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取到的值全部為重復值

  • 第三種情況,如果返回游標1時正在進行rehash,ht[0]中的bucket 1中的部分數據可能已經rehash到 ht[1]中的bucket[1]或者bucket[5],此時必須將ht[0]和ht[1]中的相應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,結束循環

總結

以上是生活随笔為你收集整理的Redis scan命令原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。