列表(list
)類型是用來存儲多個 有序 的 字符串。在 Redis
中,能夠對列表的 兩端 進行 插入(push
)和 彈出(pop
)操做,還能夠獲取 指定範圍 的 元素列表、獲取 指定索引下標 的 元素 等。mysql
列表 是一種比較 靈活 的 數據結構,它能夠充當 棧 和 隊列 的角色,在實際開發上有不少應用場景。redis
如圖所示,a
、b
、c
、d
、e
五個元素 從左到右 組成了一個 有序的列表,列表中的每一個字符串稱爲 元素(element
),一個列表最多能夠存儲 2 ^ 32 - 1
個元素。sql
下面將按照對 列表 的 5
種 操做類型 對命令進行介紹:
rpush key value [value ...]
下面代碼 從右向左 插入元素 c
、b
、a
:
127.0.0.1:6379> rpush listkey c b a
(integer) 3
複製代碼
lrange 0 -1
命令能夠 從左到右 獲取列表的 全部元素:
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "a"
複製代碼
lpush key value [value ...]
使用方法和 rpush
相同,只不過從 左側插入,這裏再也不贅述。
linsert key before|after pivot value
linsert
命令會從 列表 中找到 第一個 等於 pivot
的元素,在其 前(before
)或者 後(after
)插入一個新的元素 value
,例以下面操做會在列表的 元素 b
前插入 redis
:
127.0.0.1:6379> linsert listkey before b redis
(integer) 4
複製代碼
返回結果爲 4
,表明當前 列表 的 長度,當前列表變爲:
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "redis"
3) "b"
4) "a"
複製代碼
lrange key start stop
lrange
操做會獲取列表 指定索引 範圍全部的元素。
索引下標 有兩個特色:
其一,索引下標 從左到右 分別是 0
到 N-1
,可是 從右到左 分別是 -1
到 -N
。
其二,lrange
中的 end
選項包含了 自身,這個和不少編程語言不包含 end
不太相同。
從左到右 獲取列表的第 2
到第 4
個元素,能夠執行以下操做:
127.0.0.1:6379> lrange listkey 1 3
1) "redis"
2) "b"
3) "a"
複製代碼
從右到左 獲取列表的第 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
是同樣的,只不過從列表 右側 彈出元元素。
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
的不一樣分爲三種狀況:
count > 0:從左到右,刪除最多 count
個元素。
count < 0:從右到左,刪除最多 count
絕對值 個元素。
count = 0,刪除全部。
例如向列表 從左向右 插入 5
個 a
,那麼當前 列表 變爲 「a a a a a redis b a」
,下面操做將從列表 左邊 開始刪除 4
個爲 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
:
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
命令包含兩個參數:
key[key...]:一個列表的 多個鍵。
timeout:阻塞 時間(單位:秒)。
對於 timeout
參數,要氛圍 列表爲空 和 不爲空 兩種狀況:
若是 timeout = 3
,那麼 客戶端 要等到 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
,客戶端 當即返回:
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
時,有如下兩點須要注意:
brpop
會 從左至右 遍歷鍵,一旦有 一個鍵 能 彈出元素,客戶端 當即返回:127.0.0.1:6379> brpop list:1 list:2 list:3 0
..阻塞..
複製代碼
此時另外一個 客戶端 分別向 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
最早有 能夠彈出 的元素。
127.0.0.1:6379> brpop list:1 list:2 list:3 0
1) "list:2"
2) "element2"
複製代碼
brpop
,那麼 最早執行 brpop
命令的 客戶端 能夠 獲取 到彈出的值。按前後順序在 3
個客戶端執行 brpop
命令:
client-1> brpop list:test 0
...阻塞...
複製代碼
client-2> brpop list:test 0
...阻塞...
複製代碼
client-3> brpop list:test 0
...阻塞...
複製代碼
此時另外一個 客戶端 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
來做爲 列表 的 內部實現 來減小內存的使用。
當 列表類型 沒法知足 ziplist
的條件時, Redis
會使用 linkedlist
做爲 列表 的 內部實現。
下面的示例演示了 列表類型 的 內部編碼,以及相應的變化。
ziplist
:127.0.0.1:6379> rpush listkey e1 e2 e3
(integer) 3
127.0.0.1:6379> object encoding listkey
"ziplist"
複製代碼
512
個,內部編碼 變爲 linkedlist
:127.0.0.1:6379> rpush listkey e4 e5 ... e512 e513
(integer) 513
127.0.0.1:6379> object encoding listkey
"linkedlist"
複製代碼
64
字節,內部編碼 也會變爲 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。
經過 Redis
的 lpush + brpop
命令組合,便可實現 阻塞隊列。如圖所示:
生產者客戶端 使用 lrpush
從列表 左側插入元素,多個消費者客戶端 使用 brpop
命令 阻塞式 的 「搶」 列表 尾部 的元素,多個客戶端 保證了消費的 負載均衡 和 高可用性。
每一個 用戶 有屬於本身的 文章列表,現須要 分頁 展現文章列表。此時能夠考慮使用 列表,由於列表不可是 有序的,同時支持 按照索引範圍 獲取元素。
3
個屬性 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
批量獲取。
第二:分頁 獲取 文章列表 時, lrange
命令在列表 兩端性能較好,可是若是 列表較大,獲取列表 中間範圍 的元素 性能會變差。此時能夠考慮將列表作 二級拆分,或者使用 Redis 3.2
的 quicklist
內部編碼實現,它結合 ziplist
和 linkedlist
的特色,獲取列表 中間範圍 的元素時也能夠 高效完成。
實際上列表的使用場景不少,具體能夠參考以下:
命令組合 | 對應數據結構 |
---|---|
lpush + lpop | Stack(棧) |
lpush + rpop | Queue(隊列) |
lpush + ltrim | Capped Collection(有限集合) |
lpush + brpop | Message Queue(消息隊列) |
本文介紹了 Redis
中的 列表 的 一些 基本命令、內部編碼 和 適用場景。經過組合不一樣 命令,能夠把 列表 轉換爲不一樣的 數據結構 使用。
《Redis 開發與運維》
歡迎關注技術公衆號: 零壹技術棧
本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。