Redis高級操做:scan實現模糊查詢

簡介

在巨大的數據量的狀況下,作查找符合某種規則的Key的信息,這裏就有兩種方式:java

  1. keys命令:簡單粗暴,可是因爲Redis是單線程,keys命令是以阻塞的方式執行的,keys是以遍歷的方式實現的複雜度是 O(n),Redis庫中的key越多,查找實現代價越大,產生的阻塞時間越長。
  2. can命令: 以非阻塞的方式實現key值的查找,絕大多數狀況下是能夠替代keys命令的,可選性更強

1. scan相關命令

都是用於增量迭代集合元素。正則表達式

  1. SCAN 命令用於迭代當前數據庫中的數據庫鍵。
  2. SSCAN 命令用於迭代集合鍵中的元素。
  3. HSCAN 命令用於迭代哈希鍵中的鍵值對。
  4. ZSCAN 命令用於迭代有序集合中的元素(包括元素成員和元素分值)。

以後的例子會以sscan爲例redis

2. 命令參數

redis 127.0.0.1:6379> SSCAN key cursor [MATCH pattern] [COUNT count]

複製代碼

Key:查詢的相關集合名稱數據庫

cursor: 遊標值,第一次迭代使用 0 做爲遊標,表示開始一次新的迭代數組

[MATCH pattern] : 模糊匹配服務器

[COUNT count] :每次的查詢條數,默認值爲 10spa

2.1 建立數組

新建數組

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

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

以 0 做爲遊標開始一次新的迭代, 一直調用 SCAN 命令, 直到命令返回遊標 0 , 咱們稱這個過程爲一次完整遍歷(full iteration)。cdn

在同一時間, 能夠有任意多個客戶端對同一數據集進行迭代, 客戶端每次執行迭代都須要傳入一個遊標, 並在迭代執行以後得到一個新的遊標, 而這個遊標就包含了迭代的全部狀態, 所以, 服務器無須爲迭代記錄任何狀態,一樣也不會阻塞線程

2.2 解讀match

match: 經過提供一個 glob 風格的模式參數,讓命令只返回和給定模式相匹配的元素。

前方高能預警:match的底層操做是在從數據集中取出元素以後,向客戶端返回元素以前的這段時間內進行的, 若是返回的結果集中沒有匹配,那麼可能會在屢次執行中都不返回任何元素。

因此單次返回的結果是空的並不意味着遍歷結束,而要看返回的遊標值是否爲零;

匹配包含o的對象和以o開頭的

2.3 解讀count

count:能夠控制每次返回結果的最大條數,count只是一個 hint,返回的結果可多可少.

注意

  1. 返回的結果可能會有重複,須要客戶端去重複,這點很是重要;
  2. 遍歷的過程當中若是有數據修改,改動後的數據能不能遍歷到是不肯定的;

3. 實操

在線上有時候須要對大量key進行刪除操做,有幾個風險點:

  1. 一次性查詢所指定的key, 數量較大可能形成redis服務卡頓,Redis是單線程程序,順序執行全部指令,其它指令必須等到當前的 keys 指令執行完了才能夠繼續。
  2. 如何從海量的 key 中找出知足特定前綴的 key?

可是經過用scan,咱們就能夠指定有共性的key,並指定一次性查詢條件。

for (String cacheName : cacheNames) {
        String keyPrefix = new String(cachePrefix.prefix(cacheName)); //拼接咱們的
        ScanParams scanParams = new ScanParams().match(keyPrefix.concat("*")).count(200);  //指定規則
        String cur = ScanParams.SCAN_POINTER_START; //遊標初始值爲0
        boolean hasNext = true;
        int count = 0;
        while (hasNext) {
            count++;
            ScanResult<String> scanResult = jedisCluster.scan(cur, scanParams); //key的正則表達式
            List<String> keys = scanResult.getResult();
            for (String key : keys) {
                jedisCluster.del(key);
            }
            cur = scanResult.getStringCursor();  //返回用於下次遍歷的遊標
            if (StringUtils.equals("0", cur)) { //說明遍歷已結束
                hasNext = false;
            }
        }
        log.info("redis cache evict {} {}", cacheName, count);
    }
}
複製代碼
相關文章
相關標籤/搜索