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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

⼤海捞针 —— Scan

發(fā)布時間:2024/1/17 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ⼤海捞针 —— Scan 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
在平時線上 Redis 維護(hù)?作中,有時候需要從 Redis 實(shí)例成千上萬的 key 中找出特定前綴的 key 列表來?動處理數(shù)據(jù),可能是修改它的值,也可能是刪除 key。這?就有?個問題,如何從海量的 key中找出滿?特定前綴的 key 列表來?Redis 提供了?個簡單暴?的指令 keys ?來列出所有滿?特定正則字符串規(guī)則的 key。 127.0.0.1:6379> set codehole1 a OK 127.0.0.1:6379> set codehole2 b OK 127.0.0.1:6379> set codehole3 c OK 127.0.0.1:6379> set code1hole a OK 127.0.0.1:6379> set code2hole b OK 127.0.0.1:6379> set code3hole b OK 127.0.0.1:6379> keys * 1) "codehole1" 2) "code3hole" 3) "codehole3" 4) "code2hole" 5) "codehole2" 6) "code1hole" 127.0.0.1:6379> keys codehole* 1) "codehole1" 2) "codehole3" 3) "codehole2" 127.0.0.1:6379> keys code*hole 1) "code3hole" 2) "code2hole" 3) "code1hole"

?

這個指令使??常簡單,提供?個簡單的正則字符串即可,但是有很明顯的兩個缺點(diǎn)。 1. 沒有 offset、limit 參數(shù),?次性吐出所有滿?條件的 key,萬?實(shí)例中有?百 w 個 key 滿?條件,當(dāng)你看到滿屏的字符串刷的沒有盡頭時,你就知道難受了。 2. keys 算法是遍歷算法,復(fù)雜度是 O(n),如果實(shí)例中有千萬級以上的 key,這個指令就會導(dǎo)致 Redis 服務(wù)卡頓,所有讀寫Redis 的其它的指令都會被延后甚?會超時報錯,因為 Redis是單線程程序,順序執(zhí)?所有指令,其它指令必須等到當(dāng)前的keys 指令執(zhí)?完了才可以繼續(xù)。 ?對這兩個顯著的缺點(diǎn)該怎么辦呢? Redis 為了解決這個問題,它在 2.8 版本中加?了?海撈針的指令——scan。scan 相? keys 具備有以下特點(diǎn): 1. 復(fù)雜度雖然也是 O(n),但是它是通過游標(biāo)分步進(jìn)?的,不會阻塞線程; 2. 提供 limit 參數(shù),可以控制每次返回結(jié)果的最?條數(shù),limit 只是?個 hint,返回的結(jié)果可多可少; 3. 同 keys ?樣,它也提供模式匹配功能; 4. 服務(wù)器不需要為游標(biāo)保存狀態(tài),游標(biāo)的唯?狀態(tài)就是 scan 返回給客戶端的游標(biāo)整數(shù); 5. 返回的結(jié)果可能會有重復(fù),需要客戶端去重復(fù),這點(diǎn)?常重要; 6. 遍歷的過程中如果有數(shù)據(jù)修改,改動后的數(shù)據(jù)能不能遍歷到是不確定的; 7. 單次返回的結(jié)果是空的并不意味著遍歷結(jié)束,?要看返回的游標(biāo)值是否為零;

scan 基礎(chǔ)使?

在使?之前,讓我們往 Redis ?插? 10000 條數(shù)據(jù)來進(jìn)?測試import redis client = redis.StrictRedis() for i in range(10000): client.set("key%d" % i, i) 好,Redis 中現(xiàn)在有了 10000 條數(shù)據(jù),接下來我們找出以 key99 開頭 key 列表。 scan 參數(shù)提供了三個參數(shù),第?個是 cursor 整數(shù)值,第?個是 key 的正則模式,第三個是遍歷的 limit hint。第?次遍歷時, cursor 值為 0,然后將返回結(jié)果中第?個整數(shù)值作為下?次遍歷的 cursor。?直遍歷到返回的 cursor 值為 0 時結(jié)束。 127.0.0.1:6379> scan 0 match key99* count 1000 1) "13976" 2) 1) "key9911" 2) "key9974" 3) "key9994" 4) "key9910" 5) "key9907" 6) "key9989" 7) "key9971" 8) "key99" 9) "key9966" 10) "key992" 11) "key9903" 12) "key9905" 127.0.0.1:6379> scan 13976 match key99* count 1000 1) "1996" 2) 1) "key9982" 2) "key9997"3) "key9963" 4) "key996" 5) "key9912" 6) "key9999" 7) "key9921" 8) "key994" 9) "key9956" 10) "key9919" 127.0.0.1:6379> scan 1996 match key99* count 1000 1) "12594" 2) 1) "key9939" 2) "key9941" 3) "key9967" 4) "key9938" 5) "key9906" 6) "key999" 7) "key9909" 8) "key9933" 9) "key9992" ...... 127.0.0.1:6379> scan 11687 match key99* count 1000 1) "0" 2) 1) "key9969" 2) "key998" 3) "key9986" 4) "key9968" 5) "key9965" 6) "key9990" 7) "key9915" 8) "key9928" 9) "key9908" 10) "key9929"11) "key9944" 從上?的過程可以看到雖然提供的 limit 是 1000,但是返回的結(jié)果只有 10 個左右。因為這個 limit 不是限定返回結(jié)果的數(shù)量,?是限定服務(wù)器單次遍歷的字典槽位數(shù)量(約等于)。如果將 limit 設(shè)置為10,你會發(fā)現(xiàn)返回結(jié)果是空的,但是游標(biāo)值不為零,意味著遍歷還沒結(jié)束。 127.0.0.1:6379> scan 0 match key99* count 10 1) "3072" 2) (empty list or set) 字典的結(jié)構(gòu) 在 Redis 中所有的 key 都存儲在?個很?的字典中,這個字典的結(jié)構(gòu)和 Java 中的 HashMap ?樣,是?維數(shù)組 + ?維鏈表結(jié)構(gòu),第?維數(shù)組的??總是 2^n(n>=0),擴(kuò)容?次數(shù)組??空間加倍,也就是 n++。scan 指令返回的游標(biāo)就是第?維數(shù)組的位置索引,我們將這個位置索引稱為槽 (slot)。如果不考慮字典的擴(kuò)容縮容,直接按數(shù)組下標(biāo)挨個遍歷就?了。limit 參數(shù)就表示需要遍歷的槽位數(shù),之所以返回的結(jié)果可能多可能少,是因為不是所有的槽位上都會掛接鏈表,有些槽位可能是空的,還有些槽位上掛接的鏈表上的元素可能會有多個。每?次遍歷都會將 limit 數(shù)量的槽位上掛接的所有鏈表元素進(jìn)?模式匹配過濾后,?次性返回給客戶端。 scan 遍歷順序 scan 的遍歷順序?常特別。它不是從第?維數(shù)組的第 0 位?直遍歷到末尾,?是采?了?位進(jìn)位加法來遍歷。之所以使?這樣特殊的?式進(jìn)?遍歷,是考慮到字典的擴(kuò)容和縮容時避免槽位的遍歷重復(fù)和遺漏。 字典擴(kuò)容 Java 中的 HashMap 有擴(kuò)容的概念,當(dāng) loadFactor 達(dá)到閾值時,需要重新分配?個新的 2 倍??的數(shù)組,然后將所有的元素全部rehash 掛到新的數(shù)組下?。rehash 就是將元素的 hash 值對數(shù)組?度進(jìn)?取模運(yùn)算,因為?度變了,所以每個元素掛接的槽位可能也發(fā)?了變化。?因為數(shù)組的?度是 2^n 次?,所以取模運(yùn)算等價于位與操作。 a mod 8 = a & (8-1) = a & 7 a mod 16 = a & (16-1) = a & 15 a mod 32 = a & (32-1) = a & 31 這?的 7, 15, 31 稱之為字典的 mask 值,mask 的作?就是保留 hash 值的低位,?位都被設(shè)置為 0。 接下來我們看看 rehash 前后元素槽位的變化。 假設(shè)當(dāng)前的字典的數(shù)組?度由 8 位擴(kuò)容到 16 位,那么 3 號槽位011 將會被 rehash 到 3 號槽位和 11 號槽位,也就是說該槽位鏈表中?約有?半的元素還是 3 號槽位其它的元素會放到 11 號槽位,11 這個數(shù)字的?進(jìn)制是 1011,就是對 3 的?進(jìn)制 011 增加了?個?位 1。抽象?點(diǎn)說,假設(shè)開始槽位的?進(jìn)制數(shù)是 xxx,那么該槽位中的元素將被 rehash 到 0xxx 和 1xxx(xxx+8) 中。如果字典?度由 16 位擴(kuò)容到 32 位,那么對于?進(jìn)制槽位 xxxx 中的元素將被 rehash 到 0xxxx 和 1xxxx(xxxx+16) 中。對?擴(kuò)容縮容前后的遍歷順序觀察這張圖,我們發(fā)現(xiàn)采??位進(jìn)位加法的遍歷順序,rehash 后的槽位在遍歷順序上是相鄰的。假設(shè)當(dāng)前要即將遍歷 110 這個位置 (橙?),那么擴(kuò)容后,當(dāng)前槽位上所有的元素對應(yīng)的新槽位是 0110 和 1110(深綠?),也就是在槽位的?進(jìn)制數(shù)增加?個?位 0 或 1。這時我們可以直接從 0110 這個槽位開始往后繼續(xù)遍歷,0110 槽位之前的所有槽位都是已經(jīng)遍歷過的,這樣就可以避免擴(kuò)容后對已經(jīng)遍歷過的槽位進(jìn)?重復(fù)遍歷。再考慮縮容,假設(shè)當(dāng)前即將遍歷 110 這個位置 (橙?),那么縮容后,當(dāng)前槽位所有的元素對應(yīng)的新槽位是 10(深綠?),也就是去掉槽位?進(jìn)制最?位。這時我們可以直接從 10 這個槽位繼續(xù)往后遍歷,10 槽位之前的所有槽位都是已經(jīng)遍歷過的,這樣就可以避免縮容的重復(fù)遍歷。不過縮容還是不太?樣,它會對圖中 010 這個槽位上的元素進(jìn)?重復(fù)遍歷,因為縮融后 10 槽位的元素是 010 和 110上掛接的元素的融合。

漸進(jìn)式 rehash

Java 的 HashMap 在擴(kuò)容時會?次性將舊數(shù)組下掛接的元素全部轉(zhuǎn)移到新數(shù)組下?。如果 HashMap 中元素特別多,線程就會出現(xiàn)卡頓現(xiàn)象。Redis 為了解決這個問題,它采?漸進(jìn)式 rehash。它會同時保留舊數(shù)組和新數(shù)組,然后在定時任務(wù)中以及后續(xù)對 hash的指令操作中漸漸地將舊數(shù)組中掛接的元素遷移到新數(shù)組上。這意味著要操作處于 rehash 中的字典,需要同時訪問新舊兩個數(shù)組結(jié)構(gòu)。如果在舊數(shù)組下?找不到元素,還需要去新數(shù)組下?去尋找。scan 也需要考慮這個問題,對與rehash 中的字典,它需要同時掃描新舊槽位,然后將結(jié)果融合后返回給客戶端。更多的 scan 指令scan 指令是?系列指令,除了可以遍歷所有的 key 之外,還可以對指定的容器集合進(jìn)?遍歷。?如 zscan 遍歷 zset 集合元素,hscan遍歷 hash 字典的元素、sscan 遍歷 set 集合的元素。它們的原理同 scan 都會類似的,因為 hash 底層就是字典,set 也是?個特殊的 hash(所有的 value 指向同?個元素),zset 內(nèi)部也使?了字典來存儲所有的元素內(nèi)容,所以這?不再贅述。

? key 掃描

有時候會因為業(yè)務(wù)?員使?不當(dāng),在 Redis 實(shí)例中會形成很?的對象,?如?個很?的 hash,?個很?的 zset 這都是經(jīng)常出現(xiàn)的。這樣的對象對 Redis 的集群數(shù)據(jù)移帶來了很?的問題,因為在集群環(huán)境下,如果某個 key 太?,會數(shù)據(jù)導(dǎo)致遷移卡頓。另外在內(nèi)存分配上,如果?個 key 太?,那么當(dāng)它需要擴(kuò)容時,會?次性申請更?的?塊內(nèi)存,這也會導(dǎo)致卡頓。如果這個? key 被刪除,內(nèi)存會?次性回收,卡頓現(xiàn)象會再?次產(chǎn)?。在平時的業(yè)務(wù)開發(fā)中,要盡量避免? key 的產(chǎn)?。如果你觀察到 Redis 的內(nèi)存?起?落,這極有可能是因為? key 導(dǎo)致的,這時候你就需要定位出具體是那個 key,進(jìn)?步定位出具體的業(yè)務(wù)來源,然后再改進(jìn)相關(guān)業(yè)務(wù)代碼設(shè)計。

那如何定位? key 呢?

為了避免對線上 Redis 帶來卡頓,這就要?到 scan 指令,對于掃描出來的每?個 key,使? type 指令獲得 key 的類型,然后使?相應(yīng)數(shù)據(jù)結(jié)構(gòu)的 size 或者 len ?法來得到它的??,對于每?種類型,保留??的前 N 名作為掃描結(jié)果展示出來。上?這樣的過程需要編寫腳本,?較繁瑣,不過 Redis 官?已經(jīng)在redis-cli 指令中提了這樣的掃描功能,我們可以直接拿來即?。redis-cli -h 127.0.0.1 -p 7001 –-bigkeys如果你擔(dān)?這個指令會?幅抬升 Redis 的 ops 導(dǎo)致線上報警,還可以增加?個休眠參數(shù)。redis-cli -h 127.0.0.1 -p 7001 –-bigkeys -i 0.1上?這個指令每隔 100 條 scan 指令就會休眠 0.1s,ops 就不會劇烈抬升,但是掃描的時間會變?。 擴(kuò)展閱讀 感興趣可以繼續(xù)深?閱讀 美團(tuán)近期修復(fù)的Scan的?個bug(https://mp.weixin.qq.com/s/ufoLJiXE0wU4Bc7ZbE9cDQ)

轉(zhuǎn)載于:https://www.cnblogs.com/hanmengya/p/10967540.html

總結(jié)

以上是生活随笔為你收集整理的⼤海捞针 —— Scan的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。