前言
列表(list
)類型是用來存儲多個 有序 的 字符串燎悍。在 Redis
中,可以對列表的 兩端 進行 插入(push
)和 彈出(pop
)操作盼理,還可以獲取 指定范圍 的 元素列表谈山、獲取 指定索引下標 的 元素 等。
列表 是一種比較 靈活 的 數(shù)據(jù)結(jié)構(gòu)宏怔,它可以充當 棧 和 隊列 的角色勾哩,在實際開發(fā)上有很多應(yīng)用場景。
如圖所示举哟,a
思劳、b
、c
妨猩、d
潜叛、e
五個元素 從左到右 組成了一個 有序的列表,列表中的每個字符串稱為 元素(element
),一個列表最多可以存儲 2 ^ 32 - 1
個元素威兜。
- 列表的 插入 和 彈出 操作
- 列表的 獲取销斟、截取 和 刪除 操作
正文
1. 相關(guān)命令
下面將按照對 列表 的 5
種 操作類型 對命令進行介紹:
1.1. 添加命令
1.1.1. 從右邊插入元素
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"
1.1.2. 從左邊插入元素
lpush key value [value ...]
使用方法和 rpush
相同蚂踊,只不過從 左側(cè)插入,這里不再贅述笔宿。
1.1.3. 向某個元素前或者后插入元素
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
返回結(jié)果為 4
泼橘,代表當前 列表 的 長度涝动,當前列表變?yōu)椋?/p>
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "redis"
3) "b"
4) "a"
1.2. 查詢命令
1.2.1. 獲取指定范圍內(nèi)的元素列表
lrange key start stop
lrange
操作會獲取列表 指定索引 范圍所有的元素。
索引下標 有兩個特點:
其一,索引下標 從左到右 分別是
0
到N-1
,但是 從右到左 分別是-1
到-N
筝闹。其二灸拍,
lrange
中的end
選項包含了 自身,這個和很多編程語言不包含end
不太相同。
從左到右 獲取列表的第 2
到第 4
個元素,可以執(zhí)行如下操作:
127.0.0.1:6379> lrange listkey 1 3
1) "redis"
2) "b"
3) "a"
從右到左 獲取列表的第 1
到第 3
個元素,可以執(zhí)行如下操作:
127.0.0.1:6379> lrange listkey -3 -1
1) "redis"
2) "b"
3) "a"
1.2.2. 獲取列表指定索引下標的元素
lindex key index
例如當前列表 最后一個 元素為 a
:
127.0.0.1:6379> lindex listkey -1
"a"
1.2.3. 獲取列表長度
llen key
例如育苟,下面示例 當前列表長度 為 4
:
127.0.0.1:6379> llen listkey
(integer) 4
1.3. 刪除命令
1.3.1. 從列表左側(cè)彈出元素
lpop key
如下操作將 列表 最左側(cè)的元素 c
彈出,彈出后 列表 變?yōu)?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"
1.3.2. 從列表右側(cè)彈出元素
rpop key
它的使用方法和 lpop
是一樣的拓哺,只不過從列表 右側(cè) 彈出元元素勇垛。
127.0.0.1:6379> lpop listkey
"a"
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "redis"
3) "b"
1.3.3. 刪除指定元素
lrem key count value
lrem
命令會從 列表 中找到 等于 value
的元素進行 刪除,根據(jù) count
的不同分為三種情況:
count > 0:從左到右士鸥,刪除最多
count
個元素闲孤。count < 0:從右到左,刪除最多
count
絕對值 個元素烤礁。count = 0讼积,刪除所有。
例如向列表 從左向右 插入 5
個 a
脚仔,那么當前 列表 變?yōu)?“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"
1.3.4. 按照索引范圍修剪列表
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"
1.4. 修改命令
1.4.1. 修改指定索引下標的元素
修改 指定索引下標 的元素:
lset key index newValue
下面操作會將列表 listkey
中的第 3
個元素設(shè)置為 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"
1.5. 阻塞操作命令
阻塞式彈出 操作的命令如下:
blpop key [key ...] timeout
brpop key [key ...] timeout
blpop
和 brpop
是 lpop
和 rpop
的 阻塞版本,它們除了 彈出方向 不同鲤脏,使用方法 基本相同们颜,所以下面以 brpop
命令進行說明吕朵, brpop
命令包含兩個參數(shù):
key[key...]:一個列表的 多個鍵。
timeout:阻塞 時間(單位:秒)窥突。
對于 timeout
參數(shù)努溃,要氛圍 列表為空 和 不為空 兩種情況:
- 列表為空
如果 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
...阻塞...
如果此期間添加了數(shù)據(jù) 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"
- 其二邻眷,如果 多個客戶端 對 同一個鍵 執(zhí)行
brpop
眠屎,那么 最先執(zhí)行brpop
命令的 客戶端 可以 獲取 到彈出的值。
按先后順序在 3
個客戶端執(zhí)行 brpop
命令:
- 客戶端1:
client-1> brpop list:test 0
...阻塞...
- 客戶端2:
client-2> brpop list:test 0
...阻塞...
- 客戶端3:
client-3> brpop list:test 0
...阻塞...
此時另一個 客戶端 lpush
一個元素到 list:test
列表中:
client-lpush> lpush list:test element
(integer) 1
那么 客戶端 1
會獲取到元素肆饶,因為 客戶端 1
最先執(zhí)行 brpop
命令改衩,而 客戶端 2
和 客戶端 3
會繼續(xù) 阻塞。
client> brpop list:test 0
1) "list:test"
2) "element"
有關(guān) 列表 的 基礎(chǔ)命令 已經(jīng)介紹完了驯镊,下表是相關(guān)命令的 時間復(fù)雜度:
2. 內(nèi)部編碼
列表類型的 內(nèi)部編碼 有兩種:
2.1. ziplist(壓縮列表)
當列表的元素個數(shù) 小于 list-max-ziplist-entries
配置(默認 512
個)葫督,同時列表中 每個元素 的值都 小于 list-max-ziplist-value
配置時(默認 64
字節(jié)),Redis
會選用 ziplist
來作為 列表 的 內(nèi)部實現(xiàn) 來減少內(nèi)存的使用板惑。
2.2. linkedlist(鏈表)
當 列表類型 無法滿足 ziplist
的條件時橄镜, Redis
會使用 linkedlist
作為 列表 的 內(nèi)部實現(xiàn)。
2.3. 編碼轉(zhuǎn)換
下面的示例演示了 列表類型 的 內(nèi)部編碼冯乘,以及相應(yīng)的變化洽胶。
- 當元素 個數(shù)較少 且 沒有大元素 時,內(nèi)部編碼 為
ziplist
:
127.0.0.1:6379> rpush listkey e1 e2 e3
(integer) 3
127.0.0.1:6379> object encoding listkey
"ziplist"
- 當元素個數(shù)超過
512
個裆馒,內(nèi)部編碼 變?yōu)?linkedlist
:
127.0.0.1:6379> rpush listkey e4 e5 ... e512 e513
(integer) 513
127.0.0.1:6379> object encoding listkey
"linkedlist"
- 當某個元素超過
64
字節(jié)姊氓,內(nèi)部編碼 也會變?yōu)?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
內(nèi)部編碼,簡單地說它是以一個 ziplist
為 節(jié)點 的 linkedlist
喷好,它結(jié)合了 ziplist
和 linkedlist
兩者的優(yōu)勢翔横,為 列表類型 提供了一種更為優(yōu)秀的 內(nèi)部編碼 實現(xiàn),它的設(shè)計原理可以參考 Redis
的另一個作者 Matt Stancliff
的博客 redis-quicklist梗搅。
3. 應(yīng)用場景
3.1. 消息隊列
通過 Redis
的 lpush + brpop
命令組合禾唁,即可實現(xiàn) 阻塞隊列舔亭。如圖所示:
生產(chǎn)者客戶端 使用 lrpush
從列表 左側(cè)插入元素,多個消費者客戶端 使用 brpop
命令 阻塞式 的 “搶” 列表 尾部 的元素蟀俊,多個客戶端 保證了消費的 負載均衡 和 高可用性钦铺。
3.2. 文章列表
每個 用戶 有屬于自己的 文章列表,現(xiàn)需要 分頁 展示文章列表肢预。此時可以考慮使用 列表矛洞,因為列表不但是 有序的,同時支持 按照索引范圍 獲取元素烫映。
- 每篇文章使用 哈希結(jié)構(gòu) 存儲沼本,例如每篇文章有
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}
使用 列表 類型 保存 和 獲取 文章列表會存在兩個問題:
第一:如果每次 分頁 獲取的 文章個數(shù)較多族淮,需要執(zhí)行多次
hgetall
操作辫红,此時可以考慮使用Pipeline
進行 批量獲取,或者考慮將文章數(shù)據(jù) 序列化為字符串 類型祝辣,使用mget
批量獲取贴妻。第二:分頁 獲取 文章列表 時,
lrange
命令在列表 兩端性能較好蝙斜,但是如果 列表較大名惩,獲取列表 中間范圍 的元素 性能會變差。此時可以考慮將列表做 二級拆分孕荠,或者使用Redis 3.2
的quicklist
內(nèi)部編碼實現(xiàn)娩鹉,它結(jié)合ziplist
和linkedlist
的特點,獲取列表 中間范圍 的元素時也可以 高效完成稚伍。
3.3. 其他場景
實際上列表的使用場景很多弯予,具體可以參考如下:
命令組合 | 對應(yīng)數(shù)據(jù)結(jié)構(gòu) |
---|---|
lpush + lpop | Stack(棧) |
lpush + rpop | Queue(隊列) |
lpush + ltrim | Capped Collection(有限集合) |
lpush + brpop | Message Queue(消息隊列) |
小結(jié)
本文介紹了 Redis
中的 列表 的 一些 基本命令、內(nèi)部編碼 和 適用場景槐瑞。通過組合不同 命令熙涤,可以把 列表 轉(zhuǎn)換為不同的 數(shù)據(jù)結(jié)構(gòu) 使用。
參考
《Redis 開發(fā)與運維》
歡迎關(guān)注技術(shù)公眾號: 零壹技術(shù)棧
本帳號將持續(xù)分享后端技術(shù)干貨困檩,包括虛擬機基礎(chǔ)祠挫,多線程編程,高性能框架悼沿,異步等舔、緩存和消息中間件,分布式和微服務(wù)糟趾,架構(gòu)學(xué)習(xí)和進階等學(xué)習(xí)資料和文章慌植。