Redis命令之scan、sscan、hscan、zcan
官方文檔:https://redis.io/commands/scan#the-count-option
與Keys、smembers的比較
- keys命令以遍歷的方式迭代整個庫,實現(xiàn)的復(fù)雜度是 O(n),庫中的key越多,速度越慢,由于Redis的單線程處理,阻塞的時間也就越長。smembers也一樣,只不過它并不是阻塞整個庫,影響只針對單個set,當(dāng)set過大時會存在同樣的問題。
- scan命令的時間復(fù)雜度雖然也是O(N),但它是分次進行的,不會阻塞線程。
- scan命令提供了limit參數(shù),可以控制每次返回結(jié)果的最大條數(shù)。
- scan命令返回的結(jié)果有可能重復(fù),因此需要客戶端去重
SCAN語法
?
scan cursor [MATCH pattern] [COUNT count] #基于整個redis庫進行掃描 sscan key cursor [MATCH pattern] [COUNT count] #掃描指定的set類型的key hscan key cursor [MATCH pattern] [COUNT count] #掃描指定的hash類型的key zscan key cursor [MATCH pattern] [COUNT count] #掃描指定的zset類型的key? 參數(shù)說明:
- cursor表示本次掃描的開始游標(biāo),可以理解成從開始索引。
- pattern表示正則表達式
- count表示希望返回的個數(shù),實際返回個數(shù)會圍繞該數(shù)波動,返回的個數(shù)可能等于該值也可能有一定出入,默認(rèn)為10。
SCAN返回值
? ? ? ? scan返回值是兩個值的數(shù)組:第一個值表示在下一個調(diào)用中使用的新游標(biāo),第二個值是本次迭代結(jié)果集。如果已經(jīng)完成了一次完整的迭代,那么服務(wù)器會返回一個為0的游標(biāo),告訴客戶端,Redis已經(jīng)對庫<或set/zset、hash>完成了一次完整的迭代。
以下內(nèi)容摘自:https://www.jianshu.com/p/be15dc89a3e8
SCAN的遍歷順序
? ??關(guān)于scan命令的遍歷順序,我們可以用一個小栗子來具體看一下。
我們的Redis中有3個key,我們每次只遍歷一個一維數(shù)組中的元素。如上所示,SCAN命令的遍歷順序是
0->2->1->3,
這個順序看起來有些奇怪。我們把它轉(zhuǎn)換成二進制就好理解一些了。
00->10->01->11
我們發(fā)現(xiàn)每次這個序列是高位加1的。普通二進制的加法,是從右往左相加、進位。而這個序列是從左往右相加、進位的。這一點我們在redis的源碼中也得到印證。
在dict.c文件的dictScan函數(shù)中對游標(biāo)進行了如下處理
v = rev(v); v++; v = rev(v);意思是,將游標(biāo)倒置,加一后,再倒置,也就是我們所說的“高位加1”的操作。
這里大家可能會有疑問了,為什么要使用這樣的順序進行遍歷,而不是用正常的0、1、2……這樣的順序呢,這是因為需要考慮遍歷時發(fā)生字典擴容與縮容的情況(不得不佩服開發(fā)者考慮問題的全面性)。
我們來看一下在SCAN遍歷過程中,發(fā)生擴容時,遍歷會如何進行。加入我們原始的數(shù)組有4個元素,也就是索引有兩位,這時需要把它擴充成3位,并進行rehash。
rehash
原來掛接在xx下的所有元素被分配到0xx和1xx下。在上圖中,當(dāng)我們即將遍歷10時,dict進行了rehash,這時,scan命令會從010開始遍歷,而000和100(原00下掛接的元素)不會再被重復(fù)遍歷。
再來看看縮容的情況。假設(shè)dict從3位縮容到2位,當(dāng)即將遍歷110時,dict發(fā)生了縮容,這時scan會遍歷10。這時010下掛接的元素會被重復(fù)遍歷,但010之前的元素都不會被重復(fù)遍歷了。所以,縮容時還是可能會有些重復(fù)元素出現(xiàn)的。
Redis的rehash
rehash是一個比較復(fù)雜的過程,為了不阻塞Redis的進程,它采用了一種漸進式的rehash的機制。
/* 字典 */ typedef struct dict {// 類型特定函數(shù)dictType *type;// 私有數(shù)據(jù)void *privdata;// 哈希表dictht ht[2];// rehash 索引// 當(dāng) rehash 不在進行時,值為 -1int rehashidx; /* rehashing not in progress if rehashidx == -1 */// 目前正在運行的安全迭代器的數(shù)量int iterators; /* number of iterators currently running */ } dict;在Redis的字典結(jié)構(gòu)中,有兩個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其實就是我們前面所提到的一維數(shù)組的元素。每次遷移一個列表。下面來解釋一下這段代碼。
- 首先判斷一下是否在進行rehash,如果是,則繼續(xù)進行;否則直接返回。
- 接著就是分n步開始進行漸進式rehash。同時還判斷是否還有剩余元素,以保證安全性。
- 在進行rehash之前,首先判斷要遷移的bucket是否越界。
- 然后跳過空的bucket,這里有一個empty_visits變量,表示最大可訪問的空bucket的數(shù)量,這一變量主要是為了保證不過多的阻塞Redis。
- 接下來就是元素的遷移,將當(dāng)前bucket的全部元素進行rehash,并且更新兩張表中元素的數(shù)量。
- 每次遷移完一個bucket,需要將舊表中的bucket指向NULL。
- 最后判斷一下是否全部遷移完成,如果是,則收回空間,重置rehash索引,否則告訴調(diào)用方,仍有數(shù)據(jù)未遷移。
由于Redis使用的是漸進式rehash機制,因此,scan命令在需要同時掃描新表和舊表,將結(jié)果返回客戶端。
?
?
總結(jié)
以上是生活随笔為你收集整理的Redis命令之scan、sscan、hscan、zcan的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分开旅行—没有什么失恋是一场旅行解决不了
- 下一篇: python是不是现在主流的人工智能编程