首頁 > 資料庫 > Redis > 主體

Redis指令使用實例分析

WBOY
發布: 2023-05-30 15:46:52
轉載
685 人瀏覽過

問題原因

小編負責的應用是一個管理後台應用,權限管理使用 Shiro 框架,由於存在多個節點,需要使用分散式 Session,於是這裡使用 Redis 儲存 Session 資訊。

由於 Shiro 並沒有直接提供 Redis 儲存 Session 元件,阿粉不得不使用 Github 一個開源元件 shiro-redis。

由於 Shiro 框架需要定期驗證 Session 是否有效,因此 Shiro 底層將會呼叫  SessionDAO#getActiveSessions 取得所有的 Session 資訊。

shiro-redis 剛好繼承SessionDAO 這個接口,底層使用用keys 指令來尋找Redis 所有儲存的Session key。

public Set<byte[]> keys(byte[] pattern){
    checkAndInit();
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();
    try{
        keys = jedis.keys(pattern);
    }finally{
        jedis.close();
    }
    return keys;
}
登入後複製

找到問題原因,解決方法就比較簡單了,github 上查找到解決方案,升級一下 shiro-redis 到最新版本。

在這個版本,shiro-redis 採用 scan指令取代 keys,從而修正這個問題。

public Set<byte[]> keys(byte[] pattern) {
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();


    try{
        keys = new HashSet<byte[]>();
        ScanParams params = new ScanParams();
        params.count(count);
        params.match(pattern);
        byte[] cursor = ScanParams.SCAN_POINTER_START_BINARY;
        ScanResult<byte[]> scanResult;
        do{
            scanResult = jedis.scan(cursor,params);
            keys.addAll(scanResult.getResult());
            cursor = scanResult.getCursorAsBytes();
        }while(scanResult.getStringCursor().compareTo(ScanParams.SCAN_POINTER_START) > 0);
    }finally{
        jedis.close();
    }
    return keys;


}
登入後複製

雖然問題成功解決了,但阿粉心裡還是有點不解。

為什麼keys 指令會導致其他指令執行變慢?

為什麼Keys 指令查詢會這麼慢?

為什麼Scan 指令就沒有問題?

Redis 執行指令的原理

首先我們來看第一個問題,為什麼keys 指令會導致其他指令執行變慢?

站在客戶端的視角,執行一條指令分成三步驟:

  1. 發送指令

  2. 執行指令

  3. 返回結果

但是這僅僅是客戶端自己以為的過程,但是實際上同一時刻,可能存在很多客戶端發送命令給Redis ,而Redis 我們都知道它採用的是單線程模型。

為了處理同一時刻所有的客戶端的請求命令,Redis 內部採用了佇列的方式,排隊執行。

於是客戶端執行一條指令實際上需要四個步驟:

  1. 發送指令

  2. 指令排隊

  3. 執行指令
  4. 傳回結果

#由於Redis 單執行緒執行指令,只能依序從佇列取出任務開始執行。

只要 3 這個流程執行指令速度過慢,佇列其他任務不得不進行等待,這對外部客戶端看來,Redis 好像就被阻塞一樣,一直無法回應。

所以使用 Redis 程序切勿執行需要長時間運行的指令,這可能導致 Redis 阻塞,影響執行其他指令。

KEYS 原理接下來開始回答第二個問題,為什麼

Keys

指令查詢會這麼慢?

回答這個問題之前,請先回想 Redis 底層儲存結構。

不太清楚朋友的也沒關係,大家可以回看一下之前的文章「阿里面試官:HashMap 熟悉吧?好的,那就來聊聊 Redis 字典吧!」。 keys指令需要回傳所有的符合給定模式pattern 的 Redis 中鍵,為了實現這個目的,Redis 不得不遍歷字典中ht[ 0]哈希表底層數組,這個時間複雜度為

“O(N)”

(N 為Redis 中key 所有的數量)。

即使 Redis 中的鍵數量很少,它仍然會有很快的執行速度。當Redis鍵的數量逐漸增多,達到百萬、千萬,甚至上億級時,它的執行速度會變得非常緩慢。 下面是阿粉本地做的一次實驗,使用lua 腳本往Redis 中增加10W 個key,然後使用

keys
查詢所有鍵,這個查詢大概會阻塞十幾秒的時間。

eval "for i=1,100000  do redis.call('set',i,i+1) end" 0
登入後複製
這裡阿粉使用 Docker 部署 Redis,效能可能會稍差。

SCAN 原理最後我們來看下第三個問題,為什麼

scan

指令就沒有問題? 這是因為 scan指令採用一種黑科技-

「基於遊標的迭代器」

每次呼叫

scan

指令,Redis 都會向使用者傳回一個新的遊標以及一定數量的 key。下次再想繼續取得剩餘的 key,需要將這個遊標傳入 scan 指令, 以此來延續先前的迭代過程。 簡單來講,

scan

指令使用分頁查詢 redis 。

下面是一個scan 指令的迭代過程範例:

scan

指令使用遊標這個方式,巧妙將一次全量查詢拆分成多次,降低查詢複雜度。 雖然 scan 指令時間複雜度與keys一樣,都是「O(N)」,但由於

scan

指令只需要回傳少量的key,所以執行速度會很快。 最後,雖然scan 指令解決

keys
    不足,但同時也引入其他一些缺陷:
  • ###同一個元素可能會被回傳多次,這需要我們應用程式增加處理重複元素功能。 ###
  • 在迭代過程中,有可能會傳回正在增加到 Redis 的元素,或是正在被刪除的元素,也有可能不會。

以上這些缺陷,在我們開發中需要考慮這種情況。

除了scan以外,redis 還有其他幾個用於增量迭代命令:

  • sscan:用於迭代目前資料庫中的資料庫鍵,用於解決smembers可能產生阻塞問題

  • hscan指令用於迭代雜湊鍵中的鍵值對,用於解決hgetall 可能產生阻塞問題。

  • zscan:指令用於迭代有序集合中的元素(包括元素成員和元素分數),用於產生zrange#可能產生阻塞問題。

#

以上是Redis指令使用實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:yisu.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!