Redis中pop出隊列多個元素思考

最近,在工作中遇到瞭一個關於Redis中list集合一次性pop所有數據的問題,相信很多小夥伴也會碰到拿到同樣的問題,所以就拿出來聊一聊瞭。

業務場景及問題的提出

業務的情景是這樣的,服務A 是面向客戶的服務,主要是用瞭Redis作為存儲的,而服務B是面向業務人員使用的,服務A 的數據服務B 是需要拿到的,所以我會把每次服務A 的請求參數放到一個Redis的隊列中,而服務B 會起一個線程來定時的獲取我隊列的數據,其實這裡涉及到瞭一個簡易RPC框架的設計,下篇文章會來聊一聊我們這個簡易RPC 框架的設計變遷,大致的架構如下:

所以現在問題就來瞭,內部的定時服務如果一次pop一個那性能可就太差瞭,所以現在我們就需要支持一個可以自定義pop出隊列元素個數的方法,而redis本身是沒有這種方法的,所以得我們自己來設計,一起來聊一聊吧。

解決方案

1.多次請求

這應該是正常人都可以想到的辦法瞭,任何問題都可以使用一個for循環來解決,如果不行,那就再來一個for循環,我們來看下這個簡單的代碼:

      public List<String> multiRPopForCycle(String key, int size) {
        // 獲取當前隊列裡的值
        int curSize = Math.toIntExact(redisTemplate.opsForList().size(key));
        if (curSize == 0) {
            return Collections.emptyList();
        }
        // 最終可以取出的數量
        int finalSize = Math.toIntExact(Math.min(curSize, size));
        List<String> resultList = new ArrayList<>();
        for (int i = 0; i < finalSize; i++) {
            String popElement = redisTemplate.opsForList().rightPop(key);
            resultList.add(popElement);
        }
        return resultList;
    }

這個代碼寫出來很簡單,但是它好危險啊,如果我們需要pop出的數據很多怎麼辦,每次都需要進行通信,這來來回回就會產生很多時耗,上生產我們是沒辦法接受的,所以必須改進。 因為pop出多個元素,我們不可避免的需要進行for循環進行pop然後收集返回,也就是說我們需要執行多次redis的pop命令,為瞭減少通信時耗,我們可以一次性將所有的命令都發過去,一起執行,而實現這種方案我們有以下兩種方法:

2.利用Redis事務

利用redis的事務來實現:拿到連接後,開啟事務,然後進行執行pop命令,代碼如下:

         /**
     * 通過事務機制來pop出多個元素
     *
     * @param key       鍵
     * @param size      需要取出的元素個數
     * @return          返回取出的元素集合
     */
    public List<String> multiRPopTx(String key, int size) {
        // 獲取當前隊列裡的值
        int curSize = Math.toIntExact(redisTemplate.opsForList().size(key));
        if (curSize == 0) {
            return Collections.emptyList();
        }
        // 最終可以取出的數量
        int finalSize = Math.toIntExact(Math.min(curSize, size));
        // 事務支持
        return redisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations redisOperations) throws DataAccessException {
                redisOperations.multi();
                for (int i = 0; i < finalSize; i++) {
                    redisOperations.opsForList().rightPop(key);
                }
                return redisOperations.exec();
            }
        }).stream().map(obj -> (String) obj).collect(Collectors.toList());
    }

3.利用Pipeline

當然瞭,還可以使用我們之前講的pipeline來實現:

     /**
     * 一次性pop出指定數量的數據
     *
     * @param key       鍵
     * @param size      需要取出的元素個數
     * @return          返回取出的元素集合
     */
    public List<String> multiRPopPipeline(String key, int size) {
        // 獲取當前隊列裡的值
        int curSize = Math.toIntExact(redisTemplate.opsForList().size(key));
        if (curSize == 0) {
            return Collections.emptyList();
        }
        // 判斷操作次數
        return redisTemplate.executePipelined(new SessionCallback<String>() {
            @Override
            public String execute(RedisOperations redisOperations) throws DataAccessException {
                final int finalSize = Math.toIntExact(Math.min(curSize, size));
                for (int i = 0; i < finalSize; i++) {
                    redisOperations.opsForList().rightPop(key);
                }
                return null;
            }
        }).stream().map(obj -> (String) obj).collect(Collectors.toList());
    }

其實在我們這種場景中是沒必要使用事務的,使用事務還會帶來一定的性能損耗,所以最終選擇的是方案三,即基於管道來實現pop多個元素。

到此這篇關於Redis中pop出隊列多個元素思考的文章就介紹到這瞭,更多相關Redis pop隊列內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: