天天看點

【Redis筆記】Redis Lists

Redis Lists

要說清楚清單資料類型,最好先講一點兒理論背景,在資訊技術界List這個詞常常被使用不當。例如”Python Lists”就名不副實(名為Linked Lists),但他們實際上是數組(同樣的資料類型在Ruby中叫數組)。

一般意義上講,清單就是有序元素的序列:10,20,1,2,3就是一個清單。但用數組實作的List和用Linked List實作的List,在屬性方面大不相同。

Redis lists基于Linked Lists實作。這意味着即使在一個list中有數百萬個元素,在頭部或尾部添加一個元素的操作,其時間複雜度也是常數級别的。用LPUSH 指令在十個元素的list頭部添加新元素,和在千萬元素list頭部添加新元素的速度相同。

那麼,壞消息是什麼?在數組實作的list中利用索引通路元素的速度極快,而同樣的操作在linked list實作的list上沒有那麼快。

Redis Lists用linked list實作的原因是:對于資料庫系統來說,至關重要的特性是:能非常快的在很大的清單上添加元素。另一個重要因素是,正如你将要看到的:Redis lists能在常數時間取得常數長度。

如果快速通路集合元素很重要,建議使用可排序集合(sorted sets)。可排序集合我們會随後介紹。

Redis lists 入門

LPUSH 指令可向list的左邊(頭部)添加一個新元素,而RPUSH指令可向list的右邊(尾部)添加一個新元素。最後LRANGE指令可從list中取出一定範圍的元素:

> rpush mylist A
(integer) 1
> rpush mylist B
(integer) 2
> lpush mylist first
(integer) 3
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
           

注意:LRANGE 帶有兩個索引,一定範圍的第一個和最後一個元素。這兩個索引都可以為負來告知Redis從尾部開始計數,是以-1表示最後一個元素,-2表示list中的倒數第二個元素,以此類推。

上面的所有指令的參數都可變,友善你一次向list存入多個值。

> rpush mylist 1 2 3 4 5 "foo bar"
(integer) 9
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
4) "1"
5) "2"
6) "3"
7) "4"
8) "5"
9) "foo bar"
           

還有一個重要的指令是pop,它從list中删除元素并同時傳回删除的值。可以在左邊或右邊操作。

> rpush mylist a b c
(integer) 3
> rpop mylist
"c"
> rpop mylist
"b"
> rpop mylist
"a"
           

我們增加了三個元素,并彈出了三個元素,是以,在這最後 清單中的指令序列是空的,沒有更多的元素可以被彈出。如果我們嘗試彈出另一個元素,這是我們得到的結果:

> rpop mylist
(nil)
           

當list沒有元素時,Redis 傳回了一個NULL。

List的常用案例

正如你可以從上面的例子中猜到的,list可被用來實作聊天系統。還可以作為不同程序間傳遞消息的隊列。關鍵是,你可以每次都以原先添加的順序通路資料。這不需要任何SQL ORDER BY 操作,将會非常快,也會很容易擴充到百萬級别元素的規模。

例如在評級系統中,比如社會化新聞網站 reddit.com,你可以把每個新送出的連結添加到一個list,用LRANGE可簡單的對結果分頁。

在部落格引擎實作中,你可為每篇日志設定一個list,在該list中推入部落格評論,等等。

Capped lists

修剪(trim)一個已存在的 list,這樣 list 就會隻包含指定範圍的指定元素。start 和 stop 都是由0開始計數的, 這裡的 0 是清單裡的第一個元素(表頭),1 是第二個元素,以此類推。

例如: 

LTRIM foobar 0 2

 将會對存儲在 foobar 的清單進行修剪,隻保留清單裡的前3個元素。

start 和 end 也可以用負數來表示與表尾的偏移量,比如 -1 表示清單裡的最後一個元素, -2 表示倒數第二個,等等。

超過範圍的下标并不會産生錯誤:如果 start 超過清單尾部,或者 start > end,結果會是清單變成空表(即該 key 會被移除)。 如果 end 超過清單尾部,Redis 會将其當作清單的最後一個元素。

> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
           

List上的阻塞操作

可以使用Redis來實作生産者和消費者模型,如使用LPUSH和RPOP來實作該功能。但會遇到這種情景:list是空,這時候消費者就需要輪詢來擷取資料,這樣就會增加redis的通路壓力、增加消費端的cpu時間,而很多通路都是無用的。為此redis提供了阻塞式通路 BRPOP 和 BLPOP 指令。 消費者可以在擷取資料時指定如果資料不存在阻塞的時間,如果在時限内獲得資料則立即傳回,如果逾時還沒有資料則傳回null, 0表示一直阻塞。

同時redis還會為所有阻塞的消費者以先後順序排隊。

如需了解詳細資訊請檢視 RPOPLPUSH 和 BRPOPLPUSH。

key 的自動建立和删除

目前為止,在我們的例子中,我們沒有在推入元素之前建立空的 list,或者在 list 沒有元素時删除它。在 list 為空時删除 key,并在使用者試圖添加元素(比如通過 

LPUSH

)而鍵不存在時建立空 list,是 Redis 的職責。

這不光适用于 lists,還适用于所有包括多個元素的 Redis 資料類型 – Sets, Sorted Sets 和 Hashes。

基本上,我們可以用三條規則來概括它的行為:

  1. 當我們向一個聚合資料類型中添加元素時,如果目标鍵不存在,就在添加元素前建立空的聚合資料類型。
  2. 當我們從聚合資料類型中移除元素時,如果值仍然是空的,鍵自動被銷毀。
  3. 對一個空的 key 調用一個隻讀的指令,比如 

    LLEN

     (傳回 list 的長度),或者一個删除元素的指令,将總是産生同樣的結果。該結果和對一個空的聚合類型做同個操作的結果是一樣的。

規則 1 示例:

> del mylist
(integer) 1
> lpush mylist 1 2 3
(integer) 3
           

但是,我們不能對存在但類型錯誤的 key 做操作:   > set foo bar OK > lpush foo 1 2 3 (error) WRONGTYPE Operation against a key holding the wrong kind of value > type foo string

規則 2 示例:

> lpush mylist 1 2 3
(integer) 3
> exists mylist
(integer) 1
> lpop mylist
"3"
> lpop mylist
"2"
> lpop mylist
"1"
> exists mylist
(integer) 0
           

所有的元素被彈出之後, key 不複存在。

規則 3 示例:

> del mylist
(integer) 0
> llen mylist
(integer) 0
> lpop mylist
(nil)
           

繼續閱讀