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!
推薦閱讀:
- redis用list做消息隊列的實現示例
- Java redisTemplate阻塞式處理消息隊列
- Java Collections的emptyList、EMPTY_LIST詳解與使用說明
- Java Collections.EMPTY_LIST與Collections.emptyList()的區別
- Java中Collections.emptyList()的註意事項