最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis中scan命令的深入講解

 更新時間:2018年10月12日 11:21:43   作者:面向Google編程  
這篇文章主要給大家介紹了關(guān)于Redis中scan命令的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用redis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

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

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

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

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

相比于keys命令,scan命令有兩個比較明顯的優(yōu)勢:

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

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

關(guān)于scan命令的基本用法,可以參看Redis命令詳解:Keys一文中關(guān)于SCAN命令的介紹。

SCAN 命令

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

SCAN的話就是遍歷所有的keys

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

SCAN命令是增量的循環(huán),每次調(diào)用只會返回一小部分的元素。所以不會有KEYS命令的坑。

SCAN命令返回的是一個游標(biāo),從0開始遍歷,到0結(jié)束遍歷。

今天我們主要從底層的結(jié)構(gòu)和源碼的角度來討論scan是如何工作的。

Redis的結(jié)構(gòu)

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

scan命令就是對這個一維數(shù)組進(jìn)行遍歷。每次返回的游標(biāo)值也都是這個數(shù)組的索引。limit參數(shù)表示遍歷多少個數(shù)組的元素,將這些元素下掛接的符合條件的結(jié)果都返回。因?yàn)槊總€元素下掛接的鏈表大小不同,所以每次返回的結(jié)果數(shù)量也就不同。

SCAN的遍歷順序

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

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

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

0->2->1->3

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

00->10->01->11

我們發(fā)現(xiàn)每次這個序列是高位加1的。普通二進(jìn)制的加法,是從右往左相加、進(jìn)位。而這個序列是從左往右相加、進(jìn)位的。這一點(diǎn)我們在redis的源碼中也得到印證。

在dict.c文件的dictScan函數(shù)中對游標(biāo)進(jìn)行了如下處理

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

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

這里大家可能會有疑問了,為什么要使用這樣的順序進(jìn)行遍歷,而不是用正常的0、1、2……這樣的順序呢,這是因?yàn)樾枰紤]遍歷時發(fā)生字典擴(kuò)容與縮容的情況(不得不佩服開發(fā)者考慮問題的全面性)。

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

原來掛接在xx下的所有元素被分配到0xx和1xx下。在上圖中,當(dāng)我們即將遍歷10時,dict進(jìn)行了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的進(jìn)程,它采用了一種漸進(jìn)式的rehash的機(jī)制。

/* 字典 */
typedef struct dict {
 // 類型特定函數(shù)
 dictType *type;
 // 私有數(shù)據(jù)
 void *privdata;
 // 哈希表
 dictht ht[2];
 // rehash 索引
 // 當(dāng) rehash 不在進(jìn)行時,值為 -1
 int rehashidx; /* rehashing not in progress if rehashidx == -1 */
 // 目前正在運(yùn)行的安全迭代器的數(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為基本單位進(jìn)行遷移的。所謂的bucket其實(shí)就是我們前面所提到的一維數(shù)組的元素。每次遷移一個列表。下面來解釋一下這段代碼。

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

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

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

  • 提高redis緩存命中率的方法

    提高redis緩存命中率的方法

    在本篇文章里小編給大家整理了關(guān)于怎么提高redis緩存命中率的相關(guān)知識點(diǎn)內(nèi)容,有興趣的朋友們跟著學(xué)習(xí)下。
    2019-06-06
  • Redis緩存降級的四種策略

    Redis緩存降級的四種策略

    在高并發(fā)系統(tǒng)架構(gòu)中,Redis作為核心緩存組件扮演著至關(guān)重要的角色,它不僅能夠顯著提升系統(tǒng)響應(yīng)速度,還能有效減輕數(shù)據(jù)庫壓力,然而,當(dāng)Redis服務(wù)出現(xiàn)故障、性能下降或連接超時時,如果沒有適當(dāng)?shù)慕导墮C(jī)制,可能導(dǎo)致系統(tǒng)雪崩,所以本文給大家介紹了Redis緩存降級的四種策略
    2025-04-04
  • Redis內(nèi)部數(shù)據(jù)結(jié)構(gòu)Dict的實(shí)現(xiàn)方法

    Redis內(nèi)部數(shù)據(jù)結(jié)構(gòu)Dict的實(shí)現(xiàn)方法

    這篇文章主要介紹了Redis內(nèi)部數(shù)據(jù)結(jié)構(gòu)Dict的實(shí)現(xiàn)方法,本篇文章所述的dict在Redis中最主要的作用就是用于維護(hù)Redis數(shù)據(jù)庫中所有Key、value映射的數(shù)據(jù)結(jié)構(gòu),需要的朋友可以參考下
    2022-05-05
  • 詳解如何使用Redis實(shí)現(xiàn)分布式鎖

    詳解如何使用Redis實(shí)現(xiàn)分布式鎖

    Redis 作為一個獨(dú)立的三方系統(tǒng),其天生的優(yōu)勢就是可以作為一個分布式系統(tǒng)來使用,因此使用 Redis 實(shí)現(xiàn)的鎖都是分布式鎖,所以本文就給大家講講如何使用Redis實(shí)現(xiàn)分布式鎖,感興趣的小伙伴跟著小編來看看吧
    2023-08-08
  • Centos7 Redis主從搭建配置的實(shí)現(xiàn)

    Centos7 Redis主從搭建配置的實(shí)現(xiàn)

    這篇文章主要介紹了Centos7 Redis主從搭建配置的實(shí)現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-06-06
  • Redis 緩存問題及解決

    Redis 緩存問題及解決

    網(wǎng)上收集的一些經(jīng)典特效,這里因?yàn)槠^長,不加整理了,想運(yùn)行的代碼的朋友可以點(diǎn)擊textarea中,全選復(fù)制即可。
    2010-07-07
  • Redis接口訪問優(yōu)化的方法步驟

    Redis接口訪問優(yōu)化的方法步驟

    本文基于之前的Redis接口訪問進(jìn)行優(yōu)化,引入了接口防抖功能,通過時間段參數(shù)限制接口調(diào)用頻率,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-10-10
  • Windows下Redis的安裝使用教程

    Windows下Redis的安裝使用教程

    這篇文章主要以圖文結(jié)合的方式為大家詳細(xì)介紹了Windows下Redis的安裝使用,Redis的出現(xiàn),很大程度補(bǔ)償了memcached這類key/value存儲的不足,在部分場合可以對關(guān)系數(shù)據(jù)庫起到很好的補(bǔ)充作用,對Redis感興趣的小伙伴們可以參考一下
    2016-05-05
  • Redis Stream類型的使用詳解

    Redis Stream類型的使用詳解

    本文主要介紹了Redis Stream類型的使用詳解,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • Redis哨兵模式與主從架構(gòu)對比分析

    Redis哨兵模式與主從架構(gòu)對比分析

    Redis哨兵模式在主從架構(gòu)基礎(chǔ)上增強(qiáng)高可用性,通過自動故障切換和監(jiān)控實(shí)現(xiàn)無人值守恢復(fù),但部署復(fù)雜且無法突破單機(jī)內(nèi)存限制,適用于讀多寫少、高可用需求的場景
    2025-08-08

最新評論

海盐县| 苍溪县| 永宁县| 拉萨市| 石河子市| 鸡泽县| 水城县| 崇明县| 云安县| 瑞金市| 布拖县| 怀集县| 六枝特区| 曲麻莱县| 板桥市| 黑水县| 蕉岭县| 嘉鱼县| 祁门县| 锦州市| 阜康市| 师宗县| 泽州县| 武乡县| 名山县| 孟连| 景德镇市| 广饶县| 合阳县| 鹤峰县| 阿坝县| 邳州市| 项城市| 滕州市| 永州市| 保德县| 深水埗区| 洪江市| 绿春县| 陵水| 绿春县|