亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 數據庫 > Redis > 正文

Redis中scan命令的深入講解

2020-03-17 12:32:23
字體:
來源:轉載
供稿:網友

前言

熟悉Redis的人都知道,它是單線程的。因此在使用一些時間復雜度為O(N)的命令時要非常謹慎??赡芤徊恍⌒木蜁枞M程,導致Redis出現卡頓。

有時,我們需要針對符合條件的一部分命令進行操作,比如刪除以test_開頭的key。那么怎么獲取到這些key呢?在Redis2.8版本之前,我們可以使用keys命令按照正則匹配得到我們需要的key。但是這個命令有兩個缺點:

  • 沒有limit,我們只能一次性獲取所有符合條件的key,如果結果有上百萬條,那么等待你的就是“無窮無盡”的字符串輸出。
  • keys命令是遍歷算法,時間復雜度是O(N)。如我們剛才所說,這個命令非常容易導致Redis服務卡頓。因此,我們要盡量避免在生產環境使用該命令。

在滿足需求和存在造成Redis卡頓之間究竟要如何選擇呢?面對這個兩難的抉擇,Redis在2.8版本給我們提供了解決辦法——scan命令。

相比于keys命令,scan命令有兩個比較明顯的優勢:

  • scan命令的時間復雜度雖然也是O(N),但它是分次進行的,不會阻塞線程。
  • scan命令提供了limit參數,可以控制每次返回結果的最大條數。

這兩個優勢就幫助我們解決了上面的難題,不過scan命令也并不是完美的,它返回的結果有可能重復,因此需要客戶端去重。至于為什么會重復,相信你看完本文之后就會有答案了。

SCAN 命令

SCAN命令的有SCAN,SSCAN,HSCAN,ZSCAN。 

SCAN的話就是遍歷所有的keys 

其他的SCAN命令的話是SCAN選中的集合。 

SCAN命令是增量的循環,每次調用只會返回一小部分的元素。所以不會有KEYS命令的坑。 

SCAN命令返回的是一個游標,從0開始遍歷,到0結束遍歷。

今天我們主要從底層的結構和源碼的角度來討論scan是如何工作的。

Redis的結構

Redis使用了Hash表作為底層實現,原因不外乎高效且實現簡單。說到Hash表,很多Java程序員第一反應就是HashMap。沒錯,Redis底層key的存儲結構就是類似于HashMap那樣數組+鏈表的結構。其中第一維的數組大小為2n(n>=0)。每次擴容數組長度擴大一倍。

scan命令就是對這個一維數組進行遍歷。每次返回的游標值也都是這個數組的索引。limit參數表示遍歷多少個數組的元素,將這些元素下掛接的符合條件的結果都返回。因為每個元素下掛接的鏈表大小不同,所以每次返回的結果數量也就不同。

SCAN的遍歷順序

關于scan命令的遍歷順序,我們可以用一個小栗子來具體看一下。

127.0.0.1:6379> keys *1) "db_number"2) "key1"3) "myKey"127.0.0.1:6379> scan 0 MATCH * COUNT 11) "2"2) 1) "db_number"127.0.0.1:6379> scan 2 MATCH * COUNT 11) "1"2) 1) "myKey"127.0.0.1:6379> scan 1 MATCH * COUNT 11) "3"2) 1) "key1"127.0.0.1:6379> scan 3 MATCH * COUNT 11) "0"2) (empty list or set)

我們的Redis中有3個key,我們每次只遍歷一個一維數組中的元素。如上所示,SCAN命令的遍歷順序是

0->2->1->3

這個順序看起來有些奇怪。我們把它轉換成二進制就好理解一些了。

00->10->01->11

我們發現每次這個序列是高位加1的。普通二進制的加法,是從右往左相加、進位。而這個序列是從左往右相加、進位的。這一點我們在redis的源碼中也得到印證。

在dict.c文件的dictScan函數中對游標進行了如下處理

v = rev(v);v++;v = rev(v);

意思是,將游標倒置,加一后,再倒置,也就是我們所說的“高位加1”的操作。

這里大家可能會有疑問了,為什么要使用這樣的順序進行遍歷,而不是用正常的0、1、2……這樣的順序呢,這是因為需要考慮遍歷時發生字典擴容與縮容的情況(不得不佩服開發者考慮問題的全面性)。

我們來看一下在SCAN遍歷過程中,發生擴容時,遍歷會如何進行。加入我們原始的數組有4個元素,也就是索引有兩位,這時需要把它擴充成3位,并進行rehash。

Redis,scan,命令

原來掛接在xx下的所有元素被分配到0xx和1xx下。在上圖中,當我們即將遍歷10時,dict進行了rehash,這時,scan命令會從010開始遍歷,而000和100(原00下掛接的元素)不會再被重復遍歷。

再來看看縮容的情況。假設dict從3位縮容到2位,當即將遍歷110時,dict發生了縮容,這時scan會遍歷10。這時010下掛接的元素會被重復遍歷,但010之前的元素都不會被重復遍歷了。所以,縮容時還是可能會有些重復元素出現的。

Redis的rehash

rehash是一個比較復雜的過程,為了不阻塞Redis的進程,它采用了一種漸進式的rehash的機制。

/* 字典 */typedef struct dict { // 類型特定函數 dictType *type; // 私有數據 void *privdata; // 哈希表 dictht ht[2]; // rehash 索引 // 當 rehash 不在進行時,值為 -1 int rehashidx; /* rehashing not in progress if rehashidx == -1 */ // 目前正在運行的安全迭代器的數量 int iterators; /* number of iterators currently running */} dict;

在Redis的字典結構中,有兩個hash表,一個新表,一個舊表。在rehash的過程中,redis將舊表中的元素逐步遷移到新表中,接下來我們看一下dict的rehash操作的源碼。

/* Performs N steps of incremental rehashing. Returns 1 if there are still * keys to move from the old to the new hash table, otherwise 0 is returned. * * Note that a rehashing step consists in moving a bucket (that may have more * than one key as we use chaining) from the old to the new hash table, however * since part of the hash table may be composed of empty spaces, it is not * guaranteed that this function will rehash even a single bucket, since it * will visit at max N*10 empty buckets in total, otherwise the amount of * work it does would be unbound and the function may block for a long time. */int dictRehash(dict *d, int n) { int empty_visits = n*10; /* Max number of empty buckets to visit. */ if (!dictIsRehashing(d)) return 0; while(n-- && d->ht[0].used != 0) { dictEntry *de, *nextde; /* Note that rehashidx can't overflow as we are sure there are more  * elements because ht[0].used != 0 */ assert(d->ht[0].size > (unsigned long)d->rehashidx); while(d->ht[0].table[d->rehashidx] == NULL) {  d->rehashidx++;  if (--empty_visits == 0) return 1; } de = d->ht[0].table[d->rehashidx]; /* Move all the keys in this bucket from the old to the new hash HT */ while(de) {  uint64_t h;  nextde = de->next;  /* Get the index in the new hash table */  h = dictHashKey(d, de->key) & d->ht[1].sizemask;  de->next = d->ht[1].table[h];  d->ht[1].table[h] = de;  d->ht[0].used--;  d->ht[1].used++;  de = nextde; } d->ht[0].table[d->rehashidx] = NULL; d->rehashidx++; } /* Check if we already rehashed the whole table... */ if (d->ht[0].used == 0) { zfree(d->ht[0].table); d->ht[0] = d->ht[1]; _dictReset(&d->ht[1]); d->rehashidx = -1; return 0; } /* More to rehash... */ return 1;}

通過注釋我們就能了解到,rehash的過程是以bucket為基本單位進行遷移的。所謂的bucket其實就是我們前面所提到的一維數組的元素。每次遷移一個列表。下面來解釋一下這段代碼。

  • 首先判斷一下是否在進行rehash,如果是,則繼續進行;否則直接返回。
  • 接著就是分n步開始進行漸進式rehash。同時還判斷是否還有剩余元素,以保證安全性。
  • 在進行rehash之前,首先判斷要遷移的bucket是否越界。
  • 然后跳過空的bucket,這里有一個empty_visits變量,表示最大可訪問的空bucket的數量,這一變量主要是為了保證不過多的阻塞Redis。
  • 接下來就是元素的遷移,將當前bucket的全部元素進行rehash,并且更新兩張表中元素的數量。
  • 每次遷移完一個bucket,需要將舊表中的bucket指向NULL。
  • 最后判斷一下是否全部遷移完成,如果是,則收回空間,重置rehash索引,否則告訴調用方,仍有數據未遷移。

由于Redis使用的是漸進式rehash機制,因此,scan命令在需要同時掃描新表和舊表,將結果返回客戶端。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


注:相關教程知識閱讀請移步到Redis頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久综合电影一区| 国产精品伦子伦免费视频| 91精品久久久久久久久久另类| 97婷婷涩涩精品一区| 亚洲精品久久久一区二区三区| 久久综合电影一区| 日本高清不卡在线| 不卡伊人av在线播放| 91精品免费久久久久久久久| 久久免费福利视频| 亚洲欧美日韩天堂| 欧美裸体xxxx极品少妇软件| 欧美性xxxx18| 亚洲白虎美女被爆操| 久久影视电视剧免费网站清宫辞电视| 亚洲第一页中文字幕| 久色乳综合思思在线视频| 91精品在线观| 精品一区二区电影| 成人福利网站在线观看11| 国产精品久久久av久久久| 国产精品久久77777| 国产在线视频欧美| 国产欧美精品一区二区三区-老狼| 亚洲男人的天堂在线播放| 欧美多人爱爱视频网站| 国产成人精品久久二区二区| 91欧美视频网站| 国语自产偷拍精品视频偷| 自拍偷拍亚洲一区| 国产婷婷成人久久av免费高清| 色777狠狠综合秋免鲁丝| 国产亚洲一区精品| 琪琪第一精品导航| 欧美亚洲成人xxx| 亚洲第一国产精品| 久久久视频免费观看| 美女啪啪无遮挡免费久久网站| 成人福利网站在线观看11| 日韩激情视频在线| 国产又爽又黄的激情精品视频| 夜夜嗨av色一区二区不卡| 日韩精品免费综合视频在线播放| 久久久久成人网| 国产精品亚洲激情| 一区二区欧美亚洲| 中文字幕亚洲一区在线观看| 国产日韩在线亚洲字幕中文| 在线观看国产精品日韩av| 精品香蕉一区二区三区| 国产精品亚洲第一区| 国产精品一区二区电影| 国产精品电影在线观看| 2021久久精品国产99国产精品| 成人精品在线观看| 精品久久久久久中文字幕一区奶水| 色777狠狠综合秋免鲁丝| 91久久精品国产| 欧美一区二粉嫩精品国产一线天| 欧美成人精品xxx| 欧美小视频在线观看| 少妇高潮久久久久久潘金莲| 国产精品美女在线观看| 色综合久综合久久综合久鬼88| 国产精品无av码在线观看| 98精品在线视频| 日韩国产高清污视频在线观看| 韩国一区二区电影| 国产精品亚洲综合天堂夜夜| 久久99国产精品久久久久久久久| 在线视频欧美日韩精品| 欧美性生交大片免网| 欧美日本高清一区| 亚洲视频国产视频| 欧美日韩美女视频| 欧美人与性动交a欧美精品| 日韩在线观看成人| 毛片精品免费在线观看| 亚洲爱爱爱爱爱| 中文字幕日韩免费视频| 亚洲综合精品一区二区| 久久精视频免费在线久久完整在线看| 亚洲精品中文字| 91欧美精品成人综合在线观看| 国产精品极品美女在线观看免费| 亚洲欧美999| 另类色图亚洲色图| 久久久久久久久久国产精品| 国产精品r级在线| 中文字幕成人精品久久不卡| 欧美最猛黑人xxxx黑人猛叫黄| 久久99精品国产99久久6尤物| 欧美国产精品人人做人人爱| 亚洲性线免费观看视频成熟| 国产成人aa精品一区在线播放| 欧美高清理论片| 日韩av第一页| 亚洲开心激情网| 国产精品情侣自拍| 欧美日韩中文字幕日韩欧美| 日本欧美在线视频| 日韩欧美国产黄色| 亚洲丝袜一区在线| 国产主播欧美精品| 韩国三级电影久久久久久| 精品久久久久久国产91| 中文字幕在线成人| 91福利视频在线观看| 国产主播喷水一区二区| 欧美福利视频在线| 亚洲精品av在线| 欧美性猛交xxxx免费看| 在线播放精品一区二区三区| 国产精品国内视频| 在线视频免费一区二区| 夜色77av精品影院| 久久伊人色综合| 日韩精品日韩在线观看| 久久亚洲精品毛片| 久热在线中文字幕色999舞| 欧美丰满少妇xxxxx做受| 国产亚洲精品久久久久久777| 色先锋资源久久综合5566| 久久久久在线观看| 亚洲精选在线观看| 正在播放欧美一区| 在线视频日韩精品| 欧美大片欧美激情性色a∨久久| 亚洲电影av在线| 午夜免费日韩视频| 亚洲有声小说3d| 亚洲在线一区二区| 亚洲色无码播放| 亚洲色图欧美制服丝袜另类第一页| 国产一区二区三区三区在线观看| 亚洲美女av在线| 国产综合色香蕉精品| 狠狠色噜噜狠狠狠狠97| 午夜免费日韩视频| 国产欧美精品日韩| 亚洲图片欧美日产| 亚洲欧洲在线观看| 美女福利视频一区| 亚洲高清免费观看高清完整版| 久久全球大尺度高清视频| 亚洲精品日韩久久久| 欧美影院在线播放| 亚洲成人在线视频播放| 日韩美女激情视频| 欧美与黑人午夜性猛交久久久| 久久精品国产清自在天天线| 在线观看欧美日韩国产| 色综合色综合网色综合| 国产网站欧美日韩免费精品在线观看| 日本成人黄色片| 日韩成人在线观看| 国产91色在线免费| 国产日产久久高清欧美一区| 国产91热爆ts人妖在线| 伊人男人综合视频网| 亚洲毛片在线看| 欧美孕妇性xx| 久久国产精品亚洲| 亚洲视频欧洲视频|