Redis對象——列表(List)

  • 2020 年 4 月 10 日
  • 筆記

列表(list)類型是用來存儲多個有序的字元串,列表中的每個字元串稱為元素(element),一個列表最多可以存儲232-1個元素。在Redis中,可以對列表兩端插入(push)和彈出(pop),還可以獲取指定範圍的元素列表、獲取指定索引下標的元素等。列表是一種比較靈活的數據結構,它可以充當棧和隊列的角色,在實際開發上有很多應用場景。

列表類型有兩個特點:

  1. 列表中的元素是有序的,這就意味著可以通過索引下標獲取某個元素或者某個範圍內的元素列表。
  2. 列表中的元素可以是重複的.

一、內部實現

在Redis3.2版本以前列表類型的內部編碼有兩種。

  • ziplist(壓縮列表):當列表的元素個數小於list-max-ziplist-entries配置(默認512個),同時列表中每個元素的值都小於list-max-ziplist-value配置時(默認64位元組),Redis會選用ziplist來作為列表的內部實現來減少記憶體的使用。
  • linkedlist(鏈表):當列表類型無法滿足ziplist的條件時,Redis會使用linkedlist作為列表的內部實現。

而在Redis3.2版本開始對列表數據結構進行了改造,使用 quicklist 代替了 ziplist 和 linkedlist.

關於這三種底層數據結構可以查看我的另外三篇文章

Redis數據結構——鏈表

Redis數據結構——壓縮列表

Redis數據結構——quicklist

二、常用命令

Redis列表對象常用命令如下表(點擊命令可查看命令詳細說明)。

命令 說明 時間複雜度
BLPOP key [key ...] timeout 刪除,並獲得該列表中的第一元素,或阻塞,直到有一個可用 O(1)
BRPOP key [key ...] timeout 刪除,並獲得該列表中的最後一個元素,或阻塞,直到有一個可用 O(1)
BRPOPLPUSH source destination timeout 彈出一個列表的值,將它推到另一個列表,並返回它;或阻塞,直到有一個可用 O(1)
LINDEX key index 獲取一個元素,通過其索引列表 O(N)
LINSERT key BEFORE AFTER pivot value在列表中的另一個元素之前或之後插入一個元素 O(N)
LLEN key 獲得隊列(List)的長度 O(1)
LPOP key 從隊列的左邊出隊一個元素 O(1)
LPUSH key value [value ...] 從隊列的左邊入隊一個或多個元素 O(1)
LPUSHX key value 當隊列存在時,從隊到左邊入隊一個元素 O(1)
LRANGE key start stop 從列表中獲取指定返回的元素 O(S+N)
LREM key count value 從列表中刪除元素 O(N)
LSET key index value 設置隊列裡面一個元素的值 O(N)
LTRIM key start stop 修剪到指定範圍內的清單 O(N)
RPOP key 從隊列的右邊出隊一個元 O(1)
RPOPLPUSH source destination 刪除列表中的最後一個元素,將其追加到另一個列表 O(1)
RPUSH key value [value ...] 從隊列的右邊入隊一個元素 O(1)
RPUSHX key value 從隊列的右邊入隊一個元素,僅隊列存在時有效 O(1)

三、使用場景

3.1 消息隊列

列表類型可以使用 rpush 實現先進先出的功能,同時又可以使用 lpop 輕鬆的彈出(查詢並刪除)第一個元素,所以列表類型可以用來實現消息隊列

3.2 文章(商品等)列表

我們以部落格站點為例,當用戶和文章都越來越多時,為了加快程式的響應速度,我們可以把用戶自己的文章存入到 List 中,因為 List 是有序的結構,所以這樣又可以完美的實現分頁功能,從而加速了程式的響應速度。

  1. 每篇文章我們使用哈希結構存儲,例如每篇文章有3個屬性title、timestamp、content

    hmset acticle:1 title xx timestamp 1476536196 content xxxx  ...  hmset acticle:k title yy timestamp 1476512536 content yyyy  ...  
  2. 向用戶文章列表添加文章,user:{id}:articles作為用戶文章列表的鍵:

    lpush user:1:acticles article:1 article3  ...  lpush  ...  
  3. 分頁獲取用戶文章列表,例如下面偽程式碼獲取用戶id=1的前10篇文章

    articles = lrange user:1:articles 0 9  for article in {articles}  {  	hgetall {article}  }  

注意:使用列表類型保存和獲取文章列表會存在兩個問題。

  • 如果每次分頁獲取的文章個數較多,需要執行多次hgetall操作,此時可以考慮使用Pipeline批量獲取,或者考慮將文章數據序列化為字元串類型,使用mget批量獲取。
  • 分頁獲取文章列表時,lrange命令在列表兩端性能較好,但是如果列表較大,獲取列表中間範圍的元素性能會變差,此時可以考慮將列表做二級拆分,或者使用Redis3.2的quicklist內部編碼實現,它結合ziplist和linkedlist的特點,獲取列表中間範圍的元素時也可以高效完成。

關於列表的使用場景可參考以下幾個命令組合:

  • lpush+lpop=Stack(棧)
  • lpush+rpop=Queue(隊列)
  • lpush+ltrim=Capped Collection(有限集合)
  • lpush+brpop=Message Queue(消息隊列)

小結

本篇文章我們總結了Redis 列表對象的內部實現、常用命令以及常用的一些場景,那麼大家在項目中對Redis列表對象的使用都有哪些場景呢,歡迎在評論區給我留言和分享,我會第一時間回饋!我們共同學習與進步!

參考

《Redis設計與實現》

《Redis開發與運維》

《Redis官方文檔》

-----END-----

Tags: