Redis 笔记(04)— list类型(作为消息队列使用、在列表头部添加元素、尾部删除元素、查看列表长度、遍历指定列表区间元素、获取指定区间列表元素、阻塞式获取列表元素)
Redis 的列表是鏈表而不是數(shù)組。這意味著 list 的插入和刪除操作非常快,時間復(fù)雜度為 O(1),但是索引定位很慢,時間復(fù)雜度為 O(n)。
當(dāng)列表彈出了最后一個元素之后,該數(shù)據(jù)結(jié)構(gòu)自動被刪除,內(nèi)存被回收。
Redis 的列表結(jié)構(gòu)常用來做異步隊(duì)列使用。將需要延后處理的任務(wù)結(jié)構(gòu)體序列化成字符串塞進(jìn) Redis 的列表,另一個線程從這個列表中輪詢數(shù)據(jù)進(jìn)行處理。
Redis 在列表元素較少的情況下會使用一塊連續(xù)的內(nèi)存來存儲列表,這個結(jié)構(gòu)是 ziplist ,也即是壓縮列表。它將所有的元素緊挨著一起存儲,分配的是一塊連續(xù)的內(nèi)存。當(dāng)數(shù)據(jù)量比較多的時候才會改成 quicklist 。
因?yàn)槠胀ǖ逆湵硇枰母郊又羔樋臻g太大,會比較浪費(fèi)空間,而且會加重內(nèi)存的碎片化。比如這個列表里存的只是 int 類型的數(shù)據(jù),結(jié)構(gòu)上還需要兩個額外的指針 prev 和 next 。所以 Redis 將鏈表和 ziplist 結(jié)合起來組成了 quicklist。也就是將多個 ziplist 使用雙向指針串起來使用。這樣既滿足了快速的插入刪除性能,又不會出現(xiàn)太大的空間冗余。
1. list 類型相關(guān)命令
| 命令 | 說明 |
|---|---|
| lpush key string | 在key對應(yīng)list的頭部添加字符串元素 |
| rpop key | 在list的尾部刪除元素,并返回刪除元素 |
| rpush key string | 在key對應(yīng)list的尾部添加字符串元素 |
| lpop key | 在list的頭部刪除元素,并返回刪除元素 |
| lset key index value | 將列表key下標(biāo)為index的元素值設(shè)置為value |
| llen key | 返回對應(yīng)list的長度 |
| lrange key start end | 返回指定區(qū)間內(nèi)的元素,從下標(biāo) 0 開始 |
| ltrim key start end | 截取list, 保留指定區(qū)間內(nèi)元素 |
| lindex key 下標(biāo) | 獲取列表下標(biāo)對應(yīng)的指定元素 |
| blpop key[key…] time out | 刪除,并獲得該列的第一元素, 或阻塞,直到有一個可用 |
| brpop key[key…] time out | 刪除, 并獲得該列的最后一個元素, 或阻塞,直到有一個可用 |
| rpoplpush source destination | 刪除列表中的最后一個元素,將其追加到另一個列表 |
| brpoplpush source destination timeout | 彈出一個列表的值,將他推到另一個列表,并返回他,直到有一個可用 |
2. 使用示例
127.0.0.1:6379> lpush list 1
(integer) 1
127.0.0.1:6379> lpush list 2
(integer) 2
127.0.0.1:6379> lpush list 3
(integer) 3
127.0.0.1:6379> lpush list 4
(integer) 4
127.0.0.1:6379> lpush list 5
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> lpop list
"5"
127.0.0.1:6379> rpop list
"1"
127.0.0.1:6379> lrange list 0 -1
1) "4"
2) "3"
3) "2"
127.0.0.1:6379> rpush list 1
(integer) 4
127.0.0.1:6379> rpush list -1
(integer) 5
127.0.0.1:6379> rpush list -2
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "-1"
6) "-2"
127.0.0.1:6379> lset list 0 00
OK
127.0.0.1:6379> lrange list 0 -1
1) "00"
2) "3"
3) "2"
4) "1"
5) "-1"
6) "-2"
127.0.0.1:6379> llen list
(integer) 6
lindex 需要對鏈表進(jìn)行遍歷,性能隨著參數(shù) index 增大而變差。
127.0.0.1:6379> lindex -2
(error) ERR wrong number of arguments for 'lindex' command
127.0.0.1:6379> lindex list -2
"-1"
127.0.0.1:6379> lindex list 0
"00"
ltrim 和字面上的含義不太一樣,個人覺得它叫 lretain (保留) 更合適一些,因?yàn)?ltrim 跟的兩個參數(shù)start_index 和 end_index 定義了一個區(qū)間,在這個區(qū)間內(nèi)的值,ltrim 要保留,區(qū)間之外統(tǒng)統(tǒng)砍掉。我們可以通過 ltrim 來實(shí)現(xiàn)一個定長的鏈表,這一點(diǎn)非常有用。
index 可以為負(fù)數(shù),index=-1 表示倒數(shù)第一個元素,同樣 index=-2 表示倒數(shù)第二個元素。
127.0.0.1:6379> ltrim list 1 4
OK
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
4) "-1"
127.0.0.1:6379> blpop list 3
1) "list"
2) "3"
127.0.0.1:6379> brpop list 2
1) "list"
2) "-1"
127.0.0.1:6379>
3. 消息隊(duì)列
Redis 的 list(列表) 數(shù)據(jù)結(jié)構(gòu)常用來作為異步消息隊(duì)列使用,使用 rpush/lpush 操作入隊(duì)列,使用 lpop 和 rpop 來出隊(duì)列。
lpush 和 rpop 命令可以實(shí)現(xiàn)隊(duì)列功能,只需要生產(chǎn)者將任務(wù)使用 lpush 命令加入到某個鍵中,而消費(fèi)者使用 rpop 命令將任務(wù)從對應(yīng)的鍵中取出來即可。
brpop 命令和 rpop 命令相似,區(qū)別在于 brpop 是阻塞式的,當(dāng)隊(duì)列中沒有任務(wù)時,brpop 命令會一直阻塞住連接,直到隊(duì)列中有任務(wù)。
brpop 命令格式
brpop key seconds
seconds 為 0 表示不限制等待時間,即永遠(yuǎn)阻塞下去。
3.1 隊(duì)列空情況
客戶端是通過隊(duì)列的 pop 操作來獲取消息,然后進(jìn)行處理。處理完了再接著獲取消息,再進(jìn)行處理。如此循環(huán)往復(fù),這便是作為隊(duì)列消費(fèi)者的客戶端的生命周期。
如果隊(duì)列空了,客戶端就會陷入 pop 的死循環(huán),不停地 pop,沒有數(shù)據(jù),接著再 pop,又沒有數(shù)據(jù)。這就是浪費(fèi)生命的空輪詢。空輪詢不但拉高了客戶端的 CPU,Redis 的 QPS 也會被拉高。
通常我們使用 sleep 來解決這個問題,讓線程睡一會,睡個 1s 鐘就可以了。不但客戶端的 CPU 能降下來,Redis 的 QPS 也降下來了。
time.sleep(1) # python 睡 1s
3.2 阻塞隊(duì)列
用上面睡眠的辦法可以解決問題。但是有個小問題,那就是睡眠會導(dǎo)致消息的延遲增大。更好的解決方法是使用 blpop/brpop,這兩個指令的前綴字符 b 代表的是 blocking ,也就是阻塞讀。
阻塞讀在隊(duì)列沒有數(shù)據(jù)的時候,會立即進(jìn)入休眠狀態(tài),一旦數(shù)據(jù)到來,則立刻醒過來。消息的延遲幾乎為零。用 blpop/brpop 替代前面的 lpop/rpop 可以完美解決隊(duì)列延遲問題。
3.3 空閑連接自動斷開
如果線程一直阻塞在哪里,Redis 的客戶端連接就成了閑置連接,閑置過久,服務(wù)器一般會主動斷開連接,減少閑置資源占用。這個時候 blpop/brpop 會拋出異常來。
所以編寫客戶端消費(fèi)者的時候要小心,注意捕獲異常,還要重試。
代碼參考:
玩轉(zhuǎn) Redis:簡單消息隊(duì)列
玩轉(zhuǎn) Redis — 延時消息隊(duì)列
總結(jié)
以上是生活随笔為你收集整理的Redis 笔记(04)— list类型(作为消息队列使用、在列表头部添加元素、尾部删除元素、查看列表长度、遍历指定列表区间元素、获取指定区间列表元素、阻塞式获取列表元素)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis 笔记(03)— string
- 下一篇: Redis 笔记(05)— hash 类