Redis精進!List的使用和應用場景

點擊上方 "程序員小樂"關注, 星標或置頂一起成長

每天凌晨00點00分, 第一時間與你相約


每日英文

Life is not always what we want it to be. We fight. We cry. And sometimes, we give up. But in our hearts, we know it's still love.

生活有時不盡如人意。我們掙扎、哭泣,有時甚至放棄。但內心始終充滿愛。


每日掏心話

人生有太多的遇見,擦肩而過是一種遇見,刻骨銘心是一種遇見。有很多時候,看見的,看不見了;記住的,遺忘了。




鏈接:juejin.im/post/5df77d8bf265da33f718b654


Redis精進!List的使用和應用場景

程序員小樂(ID:study_tech)第 777 次推文 圖片來自百度


往日回顧:湖北最新:康復者體內含抗體!懇請捐贈血漿!黃岡放大招:13日24時開始執行!


正文


最近在精進學習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定義了一個區間內的值將被保留下來。這使它非常適合實現一個定長的鏈表。擴展:Redis面試連環問,快看看你能走到哪一步!

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

鏈表常用來做異步隊列使用


  • 將需要延後處理的任務結構體序列化(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使用場景,有想到的小夥伴留言交流下❤️

Redis精進!List的使用和應用場景

歡迎在留言區留下你的觀點,一起討論提高。如果今天的文章讓你有新的啟發,學習能力的提升上有新的認識,歡迎轉發分享給更多人。


猜你還想看


阿里、騰訊、百度、華為、京東最新面試題彙集

阿里面試官:分別說說微信和淘寶掃碼登錄背後的實現原理?

5 個底層程序設計邏輯,決定你有多牛逼

CPU 100%,CPU飆高,頻繁GC,怎麼排查?

關注訂閱號「程序員小樂」,收看更多精彩內容
嘿,你在看嗎?


分享到:


相關文章: