深入剖析Redis系列(七) - Redis數(shù)據(jù)結(jié)構(gòu)之列表

前言

列表list)類型是用來存儲多個 有序字符串燎悍。在 Redis 中,可以對列表的 兩端 進行 插入push)和 彈出pop)操作盼理,還可以獲取 指定范圍元素列表谈山、獲取 指定索引下標元素 等。

image

列表 是一種比較 靈活數(shù)據(jù)結(jié)構(gòu)宏怔,它可以充當 隊列 的角色勾哩,在實際開發(fā)上有很多應(yīng)用場景。

如圖所示举哟,a思劳、bc妨猩、d潜叛、e 五個元素 從左到右 組成了一個 有序的列表,列表中的每個字符串稱為 元素element),一個列表最多可以存儲 2 ^ 32 - 1 個元素威兜。

  • 列表的 插入彈出 操作
image
  • 列表的 獲取销斟、截取刪除 操作
image

正文

1. 相關(guān)命令

下面將按照對 列表5操作類型 對命令進行介紹:

image

1.1. 添加命令

1.1.1. 從右邊插入元素

rpush key value [value ...]

下面代碼 從右向左 插入元素 cb椒舵、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 操作會獲取列表 指定索引 范圍所有的元素。

索引下標 有兩個特點:

  • 其一,索引下標 從左到右 分別是 0N-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讼积,刪除所有

例如向列表 從左向右 插入 5a脚仔,那么當前 列表 變?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

blpopbrpoplpoprpop阻塞版本,它們除了 彈出方向 不同鲤脏,使用方法 基本相同们颜,所以下面以 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:2list: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ù)雜度

image

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é)合了 ziplistlinkedlist 兩者的優(yōu)勢翔横,為 列表類型 提供了一種更為優(yōu)秀的 內(nèi)部編碼 實現(xiàn),它的設(shè)計原理可以參考 Redis 的另一個作者 Matt Stancliff 的博客 redis-quicklist梗搅。

3. 應(yīng)用場景

3.1. 消息隊列

通過 Redislpush + brpop 命令組合禾唁,即可實現(xiàn) 阻塞隊列舔亭。如圖所示:

image

生產(chǎn)者客戶端 使用 lrpush 從列表 左側(cè)插入元素多個消費者客戶端 使用 brpop 命令 阻塞式“搶” 列表 尾部 的元素蟀俊,多個客戶端 保證了消費的 負載均衡高可用性钦铺。

3.2. 文章列表

每個 用戶 有屬于自己的 文章列表,現(xiàn)需要 分頁 展示文章列表肢预。此時可以考慮使用 列表矛洞,因為列表不但是 有序的,同時支持 按照索引范圍 獲取元素烫映。

  • 每篇文章使用 哈希結(jié)構(gòu) 存儲沼本,例如每篇文章有 3 個屬性 titletimestamp锭沟、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.2quicklist 內(nèi)部編碼實現(xiàn)娩鹉,它結(jié)合 ziplistlinkedlist 的特點,獲取列表 中間范圍 的元素時也可以 高效完成稚伍。

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ù)棧

零壹技術(shù)棧

本帳號將持續(xù)分享后端技術(shù)干貨困檩,包括虛擬機基礎(chǔ)祠挫,多線程編程,高性能框架悼沿,異步等舔、緩存和消息中間件,分布式和微服務(wù)糟趾,架構(gòu)學(xué)習(xí)和進階等學(xué)習(xí)資料和文章慌植。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末甚牲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蝶柿,更是在濱河造成了極大的恐慌丈钙,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件交汤,死亡現(xiàn)場離奇詭異雏赦,居然都是意外死亡,警方通過查閱死者的電腦和手機芙扎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門星岗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人戒洼,你說我怎么就攤上這事俏橘。” “怎么了圈浇?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵寥掐,是天一觀的道長。 經(jīng)常有香客問我汉额,道長曹仗,這世上最難降的妖魔是什么榨汤? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任蠕搜,我火速辦了婚禮,結(jié)果婚禮上收壕,老公的妹妹穿的比我還像新娘妓灌。我一直安慰自己,他們只是感情好蜜宪,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布虫埂。 她就那樣靜靜地躺著,像睡著了一般圃验。 火紅的嫁衣襯著肌膚如雪掉伏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天澳窑,我揣著相機與錄音斧散,去河邊找鬼。 笑死摊聋,一個胖子當著我的面吹牛鸡捐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播麻裁,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼箍镜,長吁一口氣:“原來是場噩夢啊……” “哼源祈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起色迂,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤香缺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后歇僧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赫悄,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年馏慨,在試婚紗的時候發(fā)現(xiàn)自己被綠了埂淮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡写隶,死狀恐怖倔撞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慕趴,我是刑警寧澤痪蝇,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站冕房,受9級特大地震影響躏啰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耙册,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一给僵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧详拙,春花似錦帝际、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弃揽,卻和暖如春脯爪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背矿微。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工痕慢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冷冗。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓守屉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蒿辙。 傳聞我的和親對象是個殘疾皇子拇泛,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容