列表(list)類型是用來存儲多個 有序 的 字元串。在 Redis 中,可以對列表的 兩端 進行 插入(push)和 彈出(pop)操作,還可以獲取 指定範圍 的 元素列表、獲取 指定索引下標 的 元素 等。
list
Redis
push
pop
列表 是一種比較 靈活 的 數據結構,它可以充當 棧 和 隊列 的角色,在實際開發上有很多應用場景。
如圖所示,a、b、c、d、e 五個元素 從左到右 組成了一個 有序的列表,列表中的每個字元串稱為 元素(element),一個列表最多可以存儲 2 ^ 32 - 1 個元素。
a
b
c
d
e
element
2 ^ 32 - 1
下面將按照對 列表 的 5 種 操作類型 對命令進行介紹:
5
rpush key value [value ...]
下面代碼 從右向左 插入元素 c、b、a:
127.0.0.1:6379> rpush listkey c b a (integer) 3
lrange 0 -1 命令可以 從左到右 獲取列表的 所有元素:
lrange 0 -1
127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "b" 3) "a"
lpush key value [value ...]
使用方法和 rpush 相同,只不過從 左側插入,這裡不再贅述。
rpush
linsert key before|after pivot value
linsert 命令會從 列表 中找到 第一個 等於 pivot 的元素,在其 前(before)或者 後(after)插入一個新的元素 value,例如下面操作會在列表的 元素 b 前插入 redis:
linsert
pivot
before
after
value
redis
127.0.0.1:6379> linsert listkey before b redis (integer) 4
返回結果為 4,代表當前 列表 的 長度,當前列表變為:
4
127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "redis" 3) "b" 4) "a"
lrange key start stop
lrange 操作會獲取列表 指定索引 範圍所有的元素。
lrange
索引下標 有兩個特點:
0
N-1
-1
-N
end
從左到右 獲取列表的第 2 到第 4 個元素,可以執行如下操作:
2
127.0.0.1:6379> lrange listkey 1 3 1) "redis" 2) "b" 3) "a"
從右到左 獲取列表的第 1 到第 3 個元素,可以執行如下操作:
1
3
127.0.0.1:6379> lrange listkey -3 -1 1) "redis" 2) "b" 3) "a"
lindex key index
例如當前列表 最後一個 元素為 a:
127.0.0.1:6379> lindex listkey -1 "a"
llen key
例如,下面示例 當前列表長度 為 4:
127.0.0.1:6379> llen listkey (integer) 4
lpop key
如下操作將 列表 最左側的元素 c 彈出,彈出後 列表 變為 redis、b、a。
127.0.0.1:6379> lpop listkey "c" 127.0.0.1:6379> lrange listkey 0 -1 1) "redis" 2) "b" 3) "a"
rpop key
它的使用方法和 lpop 是一樣的,只不過從列表 右側 彈出元元素。
lpop
127.0.0.1:6379> lpop listkey "a" 127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "redis" 3) "b"
lrem key count value
lrem 命令會從 列表 中找到 等於 value 的元素進行 刪除,根據 count 的不同分為三種情況:
lrem
count
例如向列表 從左向右 插入 5 個 a,那麼當前 列表 變為 「a a a a a redis b a」,下面操作將從列表 左邊 開始刪除 4 個為 a 的元素:
「a a a a a redis b a」
127.0.0.1:6379> lrem listkey 4 a (integer) 4 127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "redis" 3) "b" 4) "a"
127.0.0.1:6379> ltrim listkey 1 3 OK 127.0.0.1:6379> lrange listkey 0 -1 1) "redis" 2) "b" 3) "a"
修改 指定索引下標 的元素:
lset key index newValue
下面操作會將列表 listkey 中的第 3 個元素設置為 mysql:
listkey
mysql
127.0.0.1:6379> lset listkey 2 mysql OK 127.0.0.1:6379> lrange listkey 0 -1 1) "redis" 2) "b" 3) "mysql"
阻塞式彈出 操作的命令如下:
blpop key [key ...] timeout brpop key [key ...] timeout
blpop 和 brpop 是 lpop 和 rpop 的 阻塞版本,它們除了 彈出方向 不同,使用方法 基本相同,所以下面以 brpop 命令進行說明, brpop 命令包含兩個參數:
blpop
brpop
rpop
對於 timeout 參數,要氛圍 列表為空 和 不為空 兩種情況:
timeout
如果 timeout = 3,那麼 客戶端 要等到 3 秒後返回,如果 timeout = 0,那麼 客戶端 一直 阻塞 等下去:
timeout = 3
timeout = 0
127.0.0.1:6379> brpop list:test 3 (nil) (3.10s) 127.0.0.1:6379> brpop list:test 0 ...阻塞...
如果此期間添加了數據 element1,客戶端 立即返回:
element1
127.0.0.1:6379> brpop list:test 3 1) "list:test" 2) "element1" (2.06s)
127.0.0.1:6379> brpop list:test 0 1) "list:test" 2) "element1"
在使用 brpop 時,有以下兩點需要注意:
127.0.0.1:6379> brpop list:1 list:2 list:3 0 ..阻塞..
此時另一個 客戶端 分別向 list:2 和 list:3 插入元素:
list:2
list:3
client-lpush> lpush list:2 element2 (integer) 1 client-lpush> lpush list:3 element3 (integer) 1
客戶端 會立即返回 list:2 中的 element2,因為 list:2 最先有 可以彈出 的元素。
element2
127.0.0.1:6379> brpop list:1 list:2 list:3 0 1) "list:2" 2) "element2"
按先後順序在 3 個客戶端執行 brpop 命令:
client-1> brpop list:test 0 ...阻塞...
client-2> brpop list:test 0 ...阻塞...
client-3> brpop list:test 0 ...阻塞...
此時另一個 客戶端 lpush 一個元素到 list:test 列表中:
lpush
list:test
client-lpush> lpush list:test element (integer) 1
那麼 客戶端 1 會獲取到元素,因為 客戶端 1 最先執行 brpop 命令,而 客戶端 2 和 客戶端 3 會繼續 阻塞。
client> brpop list:test 0 1) "list:test" 2) "element"
有關 列表 的 基礎命令 已經介紹完了,下表是相關命令的 時間複雜度:
列表類型的 內部編碼 有兩種:
當列表的元素個數 小於 list-max-ziplist-entries 配置(默認 512 個),同時列表中 每個元素 的值都 小於 list-max-ziplist-value 配置時(默認 64 位元組),Redis 會選用 ziplist 來作為 列表 的 內部實現 來減少內存的使用。
list-max-ziplist-entries
512
list-max-ziplist-value
64
ziplist
當 列表類型 無法滿足 ziplist 的條件時, Redis 會使用 linkedlist 作為 列表 的 內部實現。
linkedlist
下面的示例演示了 列表類型 的 內部編碼,以及相應的變化。
127.0.0.1:6379> rpush listkey e1 e2 e3 (integer) 3 127.0.0.1:6379> object encoding listkey "ziplist"
127.0.0.1:6379> rpush listkey e4 e5 ... e512 e513 (integer) 513 127.0.0.1:6379> object encoding listkey "linkedlist"
127.0.0.1:6379> rpush listkey "one string is bigger than 64 byte..." (integer) 4 127.0.0.1:6379> object encoding listkey "linkedlist"
Redis3.2 版本提供了 quicklist 內部編碼,簡單地說它是以一個 ziplist 為 節點 的 linkedlist,它結合了 ziplist 和 linkedlist 兩者的優勢,為 列表類型 提供了一種更為優秀的 內部編碼 實現,它的設計原理可以參考 Redis 的另一個作者 Matt Stancliff 的博客 redis-quicklist。
Redis3.2
quicklist
Matt Stancliff
通過 Redis 的 lpush + brpop 命令組合,即可實現 阻塞隊列。如圖所示:
lpush + brpop
生產者客戶端 使用 lrpush 從列表 左側插入元素,多個消費者客戶端 使用 brpop 命令 阻塞式 的 「搶」 列表 尾部 的元素,多個客戶端 保證了消費的 負載均衡 和 高可用性。
lrpush
每個 用戶 有屬於自己的 文章列表,現需要 分頁 展示文章列表。此時可以考慮使用 列表,因為列表不但是 有序的,同時支持 按照索引範圍 獲取元素。
title
timestamp
content
hmset acticle:1 title xx timestamp 1476536196 content xxxx hmset acticle:2 title yy timestamp 1476536196 content yyyy ... hmset acticle:k title kk timestamp 1476512536 content kkkk
user:{id}:articles
lpush user:1:acticles article:1 article:3 article:5 lpush user:2:acticles article:2 article:4 article:6 ... lpush user:k:acticles article:7 article:8
id=1
10
articles = lrange user:1:articles 0 9 for article in {articles} hgetall {article}
使用 列表 類型 保存 和 獲取 文章列表會存在兩個問題:
hgetall
Pipeline
mget
Redis 3.2
實際上列表的使用場景很多,具體可以參考如下:
本文介紹了 Redis 中的 列表 的 一些 基本命令、內部編碼 和 適用場景。通過組合不同 命令,可以把 列表 轉換為不同的 數據結構 使用。
《Redis 開發與運維》
歡迎關注技術公眾號: 零壹技術棧
http://weixin.qq.com/r/VDgkPNHE1YyqrZVf921G (二維碼自動識別)
本帳號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,非同步、緩存和消息中間件,分散式和微服務,架構學習和進階等學習資料和文章。
推薦閱讀: