1. 程式人生 > >Redis 的 KEYS 命令不能亂用啊

Redis 的 KEYS 命令不能亂用啊

KESY 命令

時間複雜度: O(N) , 假設Redis中的鍵名和給定的模式的長度有限的情況下,N為資料庫中key的個數。

Redis Keys 命令用於查詢所有符合給定模式 pattern 的 key

儘管這個操作的時間複雜度是 O(N), 但是常量時間相當低。例如,在一個普通筆記本上跑Redis,掃描100萬個key只要40毫秒。

命令格式 KEYS pattern

Warning: 生產環境使用 KEYS 命令需要非常小心。在大的資料庫上執行命令會影響效能。這個命令適合用來除錯和特殊操作,像改變鍵空間keyspace佈局。不要在你的程式碼中使用 KEYS 。如果你需要一個尋找鍵空間中的key子集,考慮使用SCAN 或 集合結構sets。

支援的匹配模式 patterns:

  • h?llo 匹配 hello, hallo 和 hxllo

  • h*llo 匹配 hllo 和 heeeello

  • h[ae]llo 匹配 hello 和 hallo, 不匹配 hillo

  • h[^e]llo 匹配 hallo, hbllo, … 不匹配 hello

  • h[a-b]llo 匹配 hallo 和 hbllo

使用 \ 轉義你想匹配的特殊字元。

背景

1、Redis是單執行緒的,其所有操作都是原子的,不會因併發產生資料異常

2、使用高耗時的Redis命令是很危險的,會佔用唯一的一個執行緒的大量處理時間,導致所有的請求都被拖慢

場景

    在生產環境中執行 KEYS 命令的時,因為Redis是單執行緒的,KEYS 命令的效能隨著資料庫資料的增多而越來越慢,使用 KEYS 命令時會佔用唯一的一個執行緒的大量處理時間,引發Redis阻塞並且增加Redis的CPU佔用,導致所有的請求都被拖慢,可能造成Redis所在的伺服器宕機,情況是很惡劣的,在實際生產運用的過程中請忽略掉。試想如果Redis阻塞超過10秒,如果有叢集的場景,可能導致叢集判斷Redis已經故障,從而進行故障切換。

    如果所有的執行緒在Redis那取不到資料,情況嚴重時可能會導致應用程式出現雪崩的情況,一瞬間全去資料庫取資料,資料庫就宕機了。

其他危險命令

但凡發現時間複雜度為O(N)的命令,都要慎重,不要在生產上隨便使用。例如hgetall、lrange、smembers、zrange、sinter等命令,它們並非不能使用,但這些命令的時間複雜度都為O(N),使用這些命令需要明確N的值,否則也會出現快取宕機。

1、flushdb 命令用於清空當前資料庫中的所有 key

2、flushall 命令用於清空整個 Redis 伺服器的資料(刪除所有資料庫的所有 key )

3、config 客戶端連線後可配置伺服器

如何禁用危險命令

在redis.conf中,在SECURITY這一項中,新增以下配置禁用指定命令:

rename-command FLUSHALL ""
rename-command FLUSHDB  ""
rename-command CONFIG   ""
rename-command KEYS ""

另外,對於flushall命令,需要設定配置檔案中appendonly no,否則伺服器是無法啟動。

如果想要保留命令,但是不輕易使用,可以重新命名命令來設定:

rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52

TIP:更改記錄到AOF檔案或傳輸到從屬伺服器的命令的名稱可能會導致問題。

改良建議

一、如果有這種需求的話可以自己對鍵值做索引,比如把各種鍵值存到不同的set裡面,分類建立索引,這樣就可以很快的得到資料,但是這樣也存在一個明顯的缺點,就是浪費寶貴的空間,所以還是要合理考慮,當然也可以想辦法,比如對於有規律的鍵值,可以儲存他們的始末值等等。

二、針對改良keys和smembers命令也可以使用scan命令

SCAN 命令

Redis從2.8版本開始支援scan命令,可以用來分批次掃描Redis記錄,這樣肯定會導致整個查詢消耗的總時間變大,影響服務使用,但不會影響redis服務卡頓,SCAN命令的基本用法如下:

命令格式 SCAN cursor [MATCH pattern] [COUNT count]

SCAN 命令提供三個引數,第一個是cursor(遊標),第二個是要匹配的正則,第三個是單次遍歷的槽位

SCAN 命令及其相關的 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都用於增量地迭代(incrementally iterate)一集元素(a collection of elements):

  • SCAN 命令用於迭代當前資料庫中的資料庫鍵。

  • SSCAN 命令用於迭代集合鍵中的元素。

  • HSCAN 命令用於迭代雜湊鍵中的鍵值對。

  • ZSCAN 命令用於迭代有序集合中的元素(包括元素成員和元素分值)。

以上列出的四個命令都支援增量式迭代,它們每次執行都只會返回少量元素,所以這些命令可以用於生產環境,而不會出現像 KEYS 命令、SMEMBERS 命令帶來的問題 —— 當 KEYS 命令被用於處理一個大的資料庫時,又或者 SMEMBERS 命令被用於處理一個大的集合鍵時,它們可能會阻塞伺服器達數秒之久。

不過,增量式迭代命令也不是沒有缺點的:舉個例子,使用 SMEMBERS 命令可以返回集合鍵當前包含的所有元素,但是對於 SCAN 這類增量式迭代命令來說,因為在對鍵進行增量式迭代的過程中,鍵可能會被修改,所以增量式迭代命令只能對被返回的元素提供有限的保證(offer limited guarantees about the returned elements)。

因為 SCAN 、 SSCAN 、 HSCAN 和 ZSCAN 四個命令的工作方式都非常相似, 所以接下來會一併介紹這四個命令,但是要記住:

  • SSCAN 命令、 HSCAN 命令和 ZSCAN 命令的第一個引數總是一個數據庫鍵。

  • 而 SCAN 命令則不需要在第一個引數提供任何資料庫鍵 —— 因為它迭代的是當前資料庫中的所有資料庫鍵。

SCAN 命令的基本用法

SCAN 命令是一個基於遊標的迭代器(cursor based iterator):SCAN 命令每次被呼叫之後, 都會向用戶返回一個新的遊標, 使用者在下次迭代時需要使用這個新遊標作為 SCAN 命令的遊標引數, 以此來延續之前的迭代過程。

當 SCAN 命令的遊標引數被設定為 0 時, 伺服器將開始一次新的迭代, 而當伺服器向用戶返回值為 0 的遊標時, 表示迭代已結束。

以下是一個 SCAN 命令的迭代過程示例:

redis 127.0.0.1:6379> scan 0
1) "17"
2)  1) "key:12"
    2) "key:8"
    3) "key:4"
    4) "key:14"
    5) "key:16"
    6) "key:17"
    7) "key:15"
    8) "key:10"
    9) "key:3"
    10) "key:7"
    11) "key:1"

redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"
   2) "key:18"
   3) "key:0"
   4) "key:2"
   5) "key:19"
   6) "key:13"
   7) "key:6"
   8) "key:9"
   9) "key:11"

在上面這個例子中, 第一次迭代使用 0 作為遊標, 表示開始一次新的迭代。

第二次迭代使用的是第一次迭代時返回的遊標, 也即是命令回覆第一個元素的值 —— 17 。

從上面的示例可以看到, SCAN 命令的回覆是一個包含兩個元素的陣列, 第一個陣列元素是用於進行下一次迭代的新遊標, 而第二個陣列元素則是一個數組, 這個陣列中包含了所有被迭代的元素。

在第二次呼叫 SCAN 命令時, 命令返回了遊標 0 , 這表示迭代已經結束, 整個資料集(collection)已經被完整遍歷過了。

以 0 作為遊標開始一次新的迭代, 一直呼叫 SCAN 命令, 直到命令返回遊標 0 , 我們稱這個過程為一次完整遍歷(full iteration)。

SCAN 命令的保證(guarantees)

SCAN 命令, 以及其他增量式迭代命令, 在進行完整遍歷的情況下可以為使用者帶來以下保證:從完整遍歷開始直到完整遍歷結束期間, 一直存在於資料集內的所有元素都會被完整遍歷返回;這意味著, 如果有一個元素, 它從遍歷開始直到遍歷結束期間都存在於被遍歷的資料集當中, 那麼 SCAN 命令總會在某次迭代中將這個元素返回給使用者。

然而因為增量式命令僅僅使用遊標來記錄迭代狀態, 所以這些命令帶有以下缺點:

  • 同一個元素可能會被返回多次。處理重複元素的工作交由應用程式負責, 比如說, 可以考慮將迭代返回的元素僅僅用於可以安全地重複執行多次的操作上。

  • 如果一個元素是在迭代過程中被新增到資料集的, 又或者是在迭代過程中從資料集中被刪除的, 那麼這個元素可能會被返回, 也可能不會, 這是未定義的(undefined)。

SCAN 命令每次執行返回的元素數量

增量式迭代命令並不保證每次執行都返回某個給定數量的元素。

增量式命令甚至可能會返回零個元素, 但只要命令返回的遊標不是 0 , 應用程式就不應該將迭代視作結束。

不過命令返回的元素數量總是符合一定規則的, 在實際中:

  • 對於一個大資料集來說, 增量式迭代命令每次最多可能會返回數十個元素

  • 而對於一個足夠小的資料集來說, 如果這個資料集的底層表示為編碼資料結構(encoded data structure,適用於是小集合鍵、小雜湊鍵和小有序集合鍵), 那麼增量迭代命令將在一次呼叫中返回資料集中的所有元素。

最後, 使用者可以通過增量式迭代命令提供的 COUNT 選項來指定每次迭代返回元素的最大值。

COUNT 選項

雖然增量式迭代命令不保證每次迭代所返回的元素數量, 但我們可以使用 COUNT 選項, 對命令的行為進行一定程度上的調整。

基本上, COUNT 選項的作用就是讓使用者告知迭代命令, 在每次迭代中應該從資料集裡返回多少元素。

雖然 COUNT 選項只是對增量式迭代命令的一種提示(hint), 但是在大多數情況下, 這種提示都是有效的。

  • COUNT 引數的預設值為 10 。

  • 在迭代一個足夠大的、由雜湊表實現的資料庫、集合鍵、雜湊鍵或者有序集合鍵時, 如果使用者沒有使用 MATCH 選項, 那麼命令返回的元素數量通常和 COUNT 選項指定的一樣, 或者比 COUNT 選項指定的數量稍多一些。

  • 在迭代一個編碼為整數集合(intset,一個只由整數值構成的小集合)、 或者編碼為壓縮列表(ziplist,由不同值構成的一個小雜湊或者一個小有序集合)時, 增量式迭代命令通常會無視 COUNT 選項指定的值, 在第一次迭代就將資料集包含的所有元素都返回給使用者。

並非每次迭代都要使用相同的 COUNT 值

使用者可以在每次迭代中按自己的需要隨意改變 COUNT 值, 只要記得將上次迭代返回的遊標用到下次迭代裡面就可以了

MATCH 選項

和 KEYS 命令一樣, 增量式迭代命令也可以通過提供一個 glob 風格的模式引數, 讓命令只返回和給定模式相匹配的元素, 這一點可以通過在執行增量式迭代命令時, 通過給定 MATCH引數來實現。

以下是一個使用 MATCH 選項進行迭代的示例:

redis 127.0.0.1:6379> sadd myset 1 2 3 foo foobar feelsgood
(integer) 6

redis 127.0.0.1:6379> sscan myset 0 match f*
1) "0"
2) 1) "foo"
   2) "feelsgood"
   3) "foobar"

需要注意的是, 對元素的模式匹配工作是在命令從資料集中取出元素之後, 向客戶端返回元素之前的這段時間內進行的, 所以如果被迭代的資料集中只有少量元素和模式相匹配, 那麼迭代命令或許會在多次執行中都不返回任何元素。

以下是這種情況的一個例子:

redis 127.0.0.1:6379> scan 0 MATCH *11*
1) "288"
2) 1) "key:911"

redis 127.0.0.1:6379> scan 288 MATCH *11*
1) "224"
2) (empty list or set)

redis 127.0.0.1:6379> scan 224 MATCH *11*
1) "80"
2) (empty list or set)

redis 127.0.0.1:6379> scan 80 MATCH *11*
1) "176"
2) (empty list or set)

redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000
1) "0"
2)  1) "key:611"
    2) "key:711"
    3) "key:118"
    4) "key:117"
    5) "key:311"
    6) "key:112"
    7) "key:111"
    8) "key:110"
    9) "key:113"
   10) "key:211"
   11) "key:411"
   12) "key:115"
   13) "key:116"
   14) "key:114"
   15) "key:119"
   16) "key:811"
   17) "key:511"
   18) "key:11"

如你所見, 以上的大部分迭代都不返回任何元素。

在最後一次迭代, 我們通過將 COUNT 選項的引數設定為 1000 , 強制命令為本次迭代掃描更多元素, 從而使得命令返回的元素也變多了。

併發執行多個迭代

在同一時間, 可以有任意多個客戶端對同一資料集進行迭代, 客戶端每次執行迭代都需要傳入一個遊標, 並在迭代執行之後獲得一個新的遊標, 而這個遊標就包含了迭代的所有狀態, 因此, 伺服器無須為迭代記錄任何狀態。

中途停止迭代

因為迭代的所有狀態都儲存在遊標裡面, 而伺服器無須為迭代儲存任何狀態, 所以客戶端可以在中途停止一個迭代, 而無須對伺服器進行任何通知。

即使有任意數量的迭代在中途停止, 也不會產生任何問題。

使用錯誤的遊標進行增量式迭代

使用間斷的(broken)、負數、超出範圍或者其他非正常的遊標來執行增量式迭代並不會造成伺服器崩潰, 但可能會讓命令產生未定義的行為。

未定義行為指的是, 增量式命令對返回值所做的保證可能會不再為真。

只有兩種遊標是合法的:

1、在開始一個新的迭代時, 遊標必須為 0 。

2、增量式迭代命令在執行之後返回的, 用於延續(continue)迭代過程的遊標。

迭代終結的保證

增量式迭代命令所使用的演算法只保證在資料集的大小有界(bounded)的情況下, 迭代才會停止, 換句話說, 如果被迭代資料集的大小不斷地增長的話, 增量式迭代命令可能永遠也無法完成一次完整迭代。

從直覺上可以看出, 當一個數據集不斷地變大時, 想要訪問這個資料集中的所有元素就需要做越來越多的工作, 能否結束一個迭代取決於使用者執行迭代的速度是否比資料集增長的速度更快。

時間複雜度:

    增量式迭代命令每次執行的複雜度為 O(1) , 對資料集進行一次完整迭代的複雜度為 O(N) , 其中 N 為資料集中的元素數量。

返回值:

    SCAN 命令、 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都返回一個包含兩個元素的 multi-bulk 回覆:回覆的第一個元素是字串表示的無符號 64 位整數(遊標), 回覆的第二個元素是另一個 multi-bulk 回覆, 這個 multi-bulk 回覆包含了本次被迭代的元素。

    SCAN 命令返回的每個元素都是一個數據庫鍵。

    SSCAN 命令返回的每個元素都是一個集合成員。

    HSCAN 命令返回的每個元素都是一個鍵值對,一個鍵值對由一個鍵和一個值組成。

    ZSCAN 命令返回的每個元素都是一個有序集合元素,一個有序集合元素由一個成員(member)和一個分值(score)組成。

更多用法可檢視Redis SCAN命令:

http://doc.redisfans.com/key/scan.html

 

參考:

https://redis.io

https://www.dazhuanlan.com/2019/12/17/5df832fd189f6

● 概念解釋:對稱加密、非對稱加密、公鑰、私鑰、簽名、證書

● Mybatis Generator逆向工程的使用

● XStream學習手冊

● 別在 Java 程式碼裡亂打日誌了,這才是正確的打日誌姿勢!

● 高可用Redis服務架構分析與搭建