Redis 還能這樣用,真騷!

點擊上方「碼農沉思錄」,選擇「設為星標」

優質文章,及時送達

一、List類型使用說明

  • list類型是用來存儲多個有序的字元串的,支援存儲2^32次方-1個元素。
  • redis可以從鏈表的兩端進行插入(pubsh)和彈出(pop)元素,充當隊列或者棧
  • 支援讀取指定範圍的元素集
  • 讀取指定下標的元素等

注意它是鏈表而不是數組。這意味著 list 的插入和刪除操作非常快,時間複雜度為 O(1),但是索引定位很慢,時間複雜度為 O(n)

另外當列表彈出了最後一個元素之後,該數據結構自動被刪除,記憶體被回收。

二、String類型常用命令:

右邊進左邊出:隊列

# 進入隊列  > rpush books python java golang  (integer) 3    # 隊列長度  > llen books  (integer) 3    # 取出隊列  > lpop books  "python"  > lpop books  "java"  > lpop books  "golang"  > lpop books  (nil)

右邊進右邊出:棧

# 入棧  > rpush books python java golang  (integer) 3    # 出棧  > rpop books  "golang"  > rpop books  "java"  > rpop books  "python"  > rpop books  (nil)

慢操作

lindex 相當於 Java 鏈表的get(int index)方法,它需要對鏈表進行遍歷,性能隨著參數index增大而變差。

> rpush books python java golang  (integer) 3    > lindex books 1  # O(n) 慎用  "java"    > lrange books 0 -1  # 獲取所有元素,O(n) 慎用  1) "python"  2) "java"  3) "golang"      > ltrim books 1 0 # O(n) 慎用 這其實是清空了整個列表,因為區間範圍長度為負  OK    > llen books  (integer) 0

ltrim 和字面意思不太一樣,與其說去除不如說保留。

因為 ltrim 兩個參數start_index和end_index定義了一個區間內的值將被保留下來。這使它非常適合實現一個定長的鏈表。

三、使用場景:鏈表用來做非同步隊列

鏈表常用來做非同步隊列使用

  • 將需要延後處理的任務結構體序列化(JSON)成字元串塞進 Redis 的列表
  • 另一個執行緒從這個列表中輪詢數據進行處理。
  • lpush + lpop = stack 先進後出的棧
  • lpush + rpop = queue 先進先出的隊列
  • lpush + ltrim = capped collection 有限集合
  • lpush + brpop = message queue 消息隊列

Redis 隊列繞不開的消息丟失問題

一般藉助List來實現消息隊列:

  • 通過命令LPUSH(BLPUSH)把消息入隊
  • 通過命令RPOP(BRPOP)獲取消息。

但這種方式實現的隊列是不安全的。

因為RPOP(BRPOP)命令的特性:

  • 移除list的隊尾元素(消息)並返回給客戶端。這時該元素只存在於客戶端的上下文中,redis伺服器中沒有這個元素.
  • 如果客戶端在處理元素的過程崩潰了,那麼這個元素就永遠丟失了。這種情況導致:客戶端雖然成功收到了消息,但是卻沒有處理它。

試圖搶救一下

那怎麼來實現一個更安全的隊列呢?

可以試試redis的RPOPLPUSH (或者其阻塞版本的 BRPOPLPUSH)命令。

具體是操作是:

  • 在A隊列推出元素(並刪除)時,保存元素到 B隊列。
  • 如果處理 元素 的客戶端奔潰了,還可以在B隊列找到
redis> RPUSH mylist "one"  (integer) 1  redis> RPUSH mylist "two"  (integer) 2  redis> RPUSH mylist "three"  (integer) 3  redis> RPOPLPUSH mylist myotherlist  "three"  redis> LRANGE mylist 0 -1  1) "one"  2) "two"  redis> LRANGE myotherlist 0 -1  1) "three"  redis> 

這種方法存在兩個問題,

  • 多個消費者同時將消息轉存入第二個隊列,第二隊列會出現( 已執行、未執行 )消息堆積
  • 假設你的消息很特別,內容不會重複,你可以通過lrem a 0 "元素"函數找到並刪除消息,另外啟動的那個專門處理第二個隊列的client面對的隊列中的資訊數量必須很小,如果很大client處理不過來又不能使用並發,因為使用並發必須將消息pop出隊列2,如果pop出隊列2,那就又回到了我們本來要繞開的問題。

最後

所以折騰試試,發現redislist

  • 做消費者確認ACK麻煩
  • 不能重複消費,一旦消費就會被刪除
  • 隊列不去重

因此對於一致性要求高的場景,隊列建議使用Redis 5的 Stream 或者 RocketMQ。

目前還沒發現特別適合redis list使用場景,有想到的小夥伴留言交流下❤️