Redis資料結構

Tybyq發表於2018-12-13

redis不只是一個簡單的鍵(key)-值(value)資料庫,實際上它是一個資料結構伺服器,支援各種型別的值。也就是說,在傳統的鍵-值資料庫中,你把字串鍵與字串值聯絡起來,而在redis,值不僅限於一個簡單的字串,還可以是更復雜的資料結構。下面列出了所有redis支援的資料結構,下文會分別對這些結構進行介紹:

  • 二進位制安全字串

  • 佇列(lists):基於插入順序有序儲存的字串元素集合。主要是鏈式的list。

  • 集(sets):元素唯一的、無序的字串元素集合。

  • 有序集(sorted sets):與sets相似,但是每個字串元素都與一個被稱為分數(score)的浮點數相關聯。和sets不同的是,元素能夠基於分數排序,因此可以檢索某個範圍內的元素(比如你可以查詢前10個或後10個)。

  • 雜湊(hashes):由域(fields)和值之間關係組成的對映。域和值都是字串。這和Ruby或Python的雜湊非常相似。

  • 位陣列(點陣圖bitmaps):可以透過特殊命令,像處理點陣圖一樣地處理字串:設定和清除某一位,統計被置1的位數,找到第一個被設定或沒有被設定的位等。

  • HyperLogLogs:這是一種機率資料結構,用於估算集的勢。不要被嚇到了,沒那麼難。本文將在下文中HyperLogLog章節介紹。

遇到問題的時候,理解資料結構是怎麼工作的以及怎麼被使用的並不是那麼微不足道的事情。因此,這篇文件是一個關於Redis資料型別和它們常用模式的速成教材。 
這裡所有的例子,我們都使用redis客戶端(redis-cli)。相對於redis伺服器來說,這是一個簡單方便的命令列控制檯。

redis的鍵

redis的鍵是二進位制安全【1】的,也說是說,你可以使用任意的二進位制序列作為鍵,比如字串”foo”或一個JPEG檔案的內容。 
空串也是一個有效的鍵。 
一些關於鍵的其它規則:

  • 太長的鍵不推薦。例如長度為1024位元組的鍵並不好,不管是從記憶體角度,還是從查詢鍵的角度。因為從資料集中查詢鍵需要多次的鍵匹配步驟。即使手邊的任務就是要判斷一個很大的值是否存在,採用某種手段對它做hash是個好主意,尤其是從記憶體和頻寬的角度去考慮。

  • 太短的鍵通常也不推薦。如果你把鍵“user:1000:followers”寫成“u1000flw”可能會有點問題。因為前者可讀性更好,而只需要多花費一點點的空間。短的鍵顯然佔的花費的空間會小一點,因此你需要找到平衡點。

  • 儘量堅持模式。例如”object-type:id”是推薦的,就像”user:1000”。點和短線常用於多個單詞的場景,比如”comment:1234:reply.to”或”comment:1234:reply-to”。

  • 鍵的大小不能超過512MB。

Redis中的字串

Redis中的字串型別是可以與鍵關聯的最簡單的型別。它中Memcached中唯一的資料型別,也是Redis新手最常用的型別。 
由於Redis的鍵都是字串,那麼把使用字串為值,也就是字串到字串的對映。字串資料型別可以用於許多場景,比如快取HTML片段或頁面。 
讓我們用redis客戶端嘗試一些字串型別的使用吧(本文所有的例子都在redis客戶端執行)。

> set mykey somevalueOK> get mykey"somevalue"
  • 1

  • 2

  • 3

  • 4

正如你所看到的, GET SET 命令用於設定或獲取一個字串值。需要注意的是,如果鍵已經存在, SET 會覆蓋它的值,即使與這個鍵相關聯的不是字串型別的值。 SET 相當於賦值。 
值可以是任意型別的字串(包含二進位制資料),你也可以使用一個jpeg影像。值在大小不能大於512MB。 
SET命令配上一些額外的引數,可以實現一些有趣的功能。例如,我可以要求如果鍵已經存在,SET就會失敗,或者相反,鍵已經存在時SET才會成功。

> set mykey newval nx(nil)> set mykey newval xxOK
  • 1

  • 2

  • 3

  • 4

雖然字串是最基礎的資料型別,你仍可以對它執行一些有趣的操作,比如原子性的自增:

> set counter 100OK> incr counter(integer) 101> incr counter(integer) 102> incrby counter 50(integer) 152
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

INCR 命令把字串解析成一個整數,然後+1,把得到的結果作為一個新值存進去。還有其它相似的命令: INCRBY DECR DECRBY 。從命令的實現原理上講,這幾個命令是相同的,只是有一點細微的差別。 
為什麼說 INCR 是原子性的呢?因為即使是多個客戶端對同一個鍵使用 INCR 命令,也不會形成競爭條件。舉個例子,像這樣的情況是不會發生的:客戶端1讀取到鍵是10,客戶端2也讀到鍵值是10,它們同時對它執行自增命令,最終得到的值是11。實際上,最終的得到的值是12,因為當一個客戶端對鍵值做讀-自增-寫的過程中,其它的客戶是不能同時執行這個過程的。 
有許多用於操作字串的命令,例如 GETSET 命令,它給鍵設定一個新值,並返回舊值。比如你有一個系統,每當有一個新的訪問者登陸你的網站時,使用 INCR 對一個鍵值自增。你可能想要統計每個小時的資訊,卻又不希望丟失每次自增操作。你可以使用 GETSET 命令,設定一個新值“0”,同時讀取舊值。 
redis支援透過一條命令同時設定或讀取多個鍵,這對於減少延時很有用。這就是 MSET 命令和 MGET 命令:

> mset a 10 b 20 c 30OK> mget a b c1) "10"
2) "20"
3) "30"
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

使用 MGET 時,redis返回包含多個值的陣列。

更改或查詢鍵空間

【2】 
有些命令並沒有指定特定的型別,但在與鍵空間的互動有非常有用,因此可以用於任意型別的鍵。 
舉個例子, EXISTS 命令返回1或者0,用於表示某個給定的鍵在資料庫中是否存在。 DEL 命令刪除鍵以及它對應的值而不管是什麼值。

> set mykey helloOK> exists mykey(integer) 1> del mykey(integer) 1> exists mykey(integer) 0
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

DEL 返回1還是0取決於鍵是(鍵存在)否(鍵不存在)被刪除掉了。 
有許多鍵空間相關的命令,但以上這兩個命令和TYPE命令是最基本的。 TYPE 命令的作用是返回這個鍵的值的型別。

> set mykey xOK> type mykeystring> del mykey(integer) 1> type mykeynone
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

鍵的生命週期

在介紹更多更復雜的資料結構之間,我們先討論另一個與值型別無關的特性,那就是redis的期限(redis expires)。最基本的,你可以給鍵設定一個超時時間,就是這個鍵的生存週期。當生存週期過去了,鍵會被自動銷燬,就好像被使用者執行過 DEL 一樣。 
一些關於redis期限的快速資訊:

  • 生存週期可以設定的時間單位從秒級到毫秒級。

  • 生存週期的時間精度都是1毫秒。

  • 關於生存週期的資料有多份且存在硬碟上,基於Redis伺服器停止了,時間仍在流逝,這意味著redis儲存的是key到期的時間。

設定生存週期是件瑣碎的事情:

> set key some-valueOK> expire key 5(integer) 1> get key (immediately)"some-value"> get key (after some time)(nil)
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

鍵在兩次呼叫之間消失了,這是因為第二次呼叫的延遲了超過5秒的時間。在上面的例子中,我們使用 EXPIRE 命令設定生命週期(它也可以用於為一個已經設定過生命週期的鍵重新設定生命週期, PERSIST 命令可以用於移除鍵的命令週期,使它能夠長期存在)。我們還可以使用redis命令在建立鍵的同時設定生命週期。比如使用帶引數的 SET 命令:

> set key 100 ex 10OK> ttl key(integer) 9
  • 1

  • 2

  • 3

  • 4

上面這個例子中建立了一個鍵,它的值是字串100,生命週期是10秒。後面的TTL命令用於檢視鍵的剩餘時間。 
如果要以毫秒為單位設定或查詢鍵的生命週期,請查詢 PEXPIRE 命令和 PTTL 命令,以及 SET 命令的引數列表。

redis中的列表(lists)

要解釋列表資料型別,最好先從一點理論開始。因為列表這個術語常被資訊科技人員錯誤地使用。例如“python 列表”,並不像它的命令所提示的(連結串列),而是陣列(實際上與Ruby中的陣列是同一個資料型別)。 
從廣義上講,列表只是元素的有序序列:10,20,1,2,3是一個列表。但是用陣列實現的列表和用連結串列實現的列表,它們的屬性有很大的不同。 
redis的列表都是用連結串列的方式實現的。也就是說,即使列表中有數百萬個元素,增加一個新元素到列表頭部或尾部操作的執行時間是常數時間。使用 LPUSH 命令把一個新元素增加到一個擁有10個元素的列表的頭部,或是增加到一個擁有一千萬個元素的列表的頭部,其速度是一樣的。 
缺點是什麼呢?透過索引訪問一個元素的操作,在陣列實現的列表中非常快(常數時間),但在連結串列實現的列表中不是那麼快(與找到元素對應下標的速度成比例)。 
redis選擇用連結串列實現列表,因為對於一個資料庫來說,快速地向一個很大的列表新增元素是非常重要的。另一個使用連結串列的強大優勢,你稍後將會看到,能夠在常數時間內得到一個固定長度的redis列表。 
快速地讀取很大一堆元素的中間元素也是重要的,這時可以使用另一種資料結構,稱為有序集(sorted sets)。本文後面會講到有序集。

regis列表第一步

LPUSH 命令把一個新的元素加到列表的左邊(頭部),而 RPUSH 命令把一個新的元素加到列表的右邊(尾部)。 LRANGE 命令從列表中提取某個範圍內的元素

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

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

注意, LRANGE 命令需要輸入兩個下標,即範圍的第一個元素下標和最後一個元素下標。兩個下標都可以是負的,意思是從尾部開始數:因此-1是最後一個元素,-2是倒數第二個元素,等。 
正如你所見, RPUSH 把元素加到列表右邊, LPUSH 把元素加到列表左邊。所有命令的引數都是可變的,即你可以隨意地把多個增加元素入列表的命令放到一次呼叫中:

> 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"
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

Redis中定義了一個重要的操作就是刪除元素。刪除命令可以同時從列表中檢索和刪除元素。你可以從左邊或者右邊刪除元素,和從兩邊增加元素的方法類似:

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

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

我們增加和刪除的三個元素,因此最後列表是空的,沒有元素可以刪除。如果我們嘗試繼續刪除元素,會得到這樣的結果:

> rpop mylist(nil)
  • 1

  • 2

redis返回空值說明列表中沒有元素了。

列表的常見用例

列表可以用於完成多種任務,以下是兩個非常有代表性的用例:

  • 記住使用者釋出到社交網路的最新更新。

  • 使用消費者-生產者模型進行程式間通訊,生產生把表項(items)放進列表中,消費者(通常是工作者)消費這些items並執行一些行為。redis針對這種用例有一些特殊的列表命令,既可靠又高效。

例如非常有名的Ruby庫 resque sidekip ,在底層都使用了Redis列表來實現後臺作業。 
著名的社交網路Twitter使用Redis列表來獲取使用者釋出的最新的訊息。 
為了一步一步地描述一個常見用例,假設要在你的主頁上展示社交網路上最新分享的照片並且加速訪問。

  • 每當一個使用者釋出了一張新的照片,我們使用 LPUSH 命令把它的ID加入到列表中。

  • 當使用者訪問這個主頁,我們使用LRANGE 0 9獲取最新加入的10個表項。

限制列表

很多情況下我們只想要使用列表來儲存最新的幾條表項,例如社交網路更新、日誌或者其它。 
Redis允許我們使用列表作為一個固定集合,使用 LTRIM 命令,只記錄最新的N條記錄,而丟棄所有更早的記錄。 
LTRIM 命令和 LRANGE 命令相似,但不像 LRANGE 一樣顯示特定範圍的元素,而是用這個範圍內的值重新設定列表。所有範圍外的元素都被刪除了。 
用個例子來說明這一點:

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

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

上面的 LTRIM 命令告訴Redis只取得列表中下標為0到2的元素,其它的都要丟棄。這就是一種簡單有用的模式成為了可能:列表增加(push)元素操作+列表提取(trim)元素操作=增加一個元素同時刪除一個元素使得列表元素總數有限:

LPUSH mylist <some element>LTRIM mylist 0 999
  • 1

  • 2

上面的操作結合增加一個元素但只是存在1000個最新的元素在列表中。透過 LRANGE 你可以獲取最新的表項而不需要記住舊的資料。 
注意:由於理論上 LRANGE 是O(N)命令,讀取從頭開始或從尾開始的小範圍資料是常數時間的操作。

列表中是阻塞型操作

列表的一些特性使它適合現實佇列(queues),也通常作為程式間通訊系統的一個基礎元件:阻塞式操作。 
假設你透過一個程式把元素增加到列表中,使用另一個程式對這些元素做些實際的操作。這是通常的生產者/消費者基礎,你可以用下面這種簡單的方法實現:

  • 生產者呼叫LPUSH,把元素加入列表

  • 消費者呼叫RPOP,把元素從列表中取出或處理

有沒有可能出現這種情況,列表是空的,沒有什麼東西可以處理,因此RPOP返回NULL。這種情況下,消費者不得不等一會再嘗試RPOP。這就叫輪詢。這並不是一個好方法,因為它有以下缺點:

  1. 要求redis和客戶端執行沒有意義的命令(當列表為空是所有的請求都不會執行實際工作,只是返回NULL)

  2. 工作者在收到NULL之後加入一個延時,讓它等待一些時間。如果讓延時小一點,在兩次呼叫 RPOP 之間的等待時間會比較短,這成為第一個問題的放大-呼叫Redis更加沒有意義

因此Redis實現了命令 BRPOP BLPOP ,它是 RPOP LPOP 的帶阻塞功能的版本:當列表為空時,它們會等到一個新的元素加入到列表時,或者使用者定義的等待時間到了時,才會返回。 
這是 BRPOP 呼叫的一個例子,我們可以在工作者程式使用它:

> brpop tasks 5
1) "tasks"2) "do_something"
  • 1

  • 2

  • 3

它的意思是:等待列表中的元素,如果5秒還沒有可用的元素。 
注意,如果使用0作為超時時間,將會永遠等待,你也可以定義多個列表而不只是一個,這樣就會同時等待多個列表,當任意一個列表收到一個元素時就會收到通知。 
一些關於 BRPOP 需要注意的事情:

  1. 客戶端是按順序被服務的:第一個等待某個列表的客戶端,當列表被另一個客戶端增加一個元素時,它會第一個處理。

  2. 返回值與RPOP的不同:只得到兩個元素的包含鍵名的陣列,因為BRPOP和BLPOP因為等待多個列表而阻塞。

  3. 如果時間超時了,就會返回NULL

還有更多你應該知道的關於列表和阻塞操作的東西。我們建議你閱讀以下材料:

  • 可以使用 RPOPLPUSH 建立更安全的佇列或旋轉佇列。

  • 這個命令有一個阻塞引數,即 BRPOPLPUSH

自動建立和移除鍵

到目前為止我們的例子還沒有涉及到這些情景,在增加一個元素之間建立一個空的列表,或者當一個列表沒有元素時把它移除。redis有責任刪除變為空的列表,或當我們試圖增加元素時建立空列表。例如 LPUSH  
這不僅適用於列表,它可以應用於所有包含多個元素的Redis資料結構-集、有序集和雜湊。 
基本上講,我們把它的行為總結為三個規則:

  1. 當我們把一個元素增加到一個集合類資料型別時,如果這個鍵不存在,在增加前會建立一個空的集合類資料型別。

  2. 我們從一個集合類資料型別中移除一個元素時,如果值保持為空,鍵就會被自動刪除

  3. 呼叫一個只讀命令例如 LLEN (返回列表的長度),或者一個移除元素的寫命令但鍵為空,結果不會改變。[3]

規則1舉例:

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

  • 2

  • 3

  • 4

然而,我們不能對一個已經存在的鍵執行與它型別不同的操作:

> set foo barOK> lpush foo 1 2 3(error) WRONGTYPE Operation against a key holding the wrong kind of value> type foostring
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

規則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
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

當所有元素被取出,這個鍵就不存在了。 
規則3舉例:

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

  • 2

  • 3

  • 4

  • 5

  • 6

redis中的雜湊(hashed)

redis的雜湊和我們所認識的“雜湊”非常相似,是域-值對。

> hmset user:1000 username antirez birthyear 1977 verified 1
OK
> hget user:1000 username"antirez"> hget user:1000 birthyear"1977"> hgetall user:1000
1) "username"2) "antirez"3) "birthyear"4) "1977"5) "verified"6) "1"
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

hash表示物件(object)非常方便。實際上,可以放入一個hash的域的數量沒有限制(不考慮可用記憶體),因此你可以在你的應用中用許多不同的方式使用雜湊。 
HMSET命令為hash設定多個域,而 HGET 獲取某一個域。 HMGET HGET 相似,但它返回由值組成的陣列。

> hmget user:1000 username birthyear no-such-field1) "antirez"2) "1977"3) (nil)
  • 1

  • 2

  • 3

  • 4

還有一些命令可以對單個的域執行操作,例如 HINCRBY

> hincrby user:1000 birthyear 10(integer) 1987> hincrby user:1000 birthyear 10(integer) 1997
  • 1

  • 2

  • 3

  • 4

你可以檢視這篇文件《hash命令全列》 
把小的雜湊(少量的元素,較小的值)用特殊的編碼方式存放在記憶體中並不是什麼難事,因此它們的空間效率非常高。

redis的集(sets)

Redis的集是字串無序的集合。 SADD 向集中增加一些元素。對於集合還有很多其它的操作,例如測試某個給定的元素是否存在,多個集合之間求交集、合集或者差集,等。

> sadd myset 1 2 3(integer) 3> smembers myset1. 32. 13. 2
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

在這個例子中,我向myset中增加了三個元素,並讓redis返回所有的元素。正如你所看到的,它們是無序的。每次呼叫,redis都可能以任何順序返回元素,因此在這裡,使用者不能對元素的順序有要求。 
redis提供測試成員的命令。這個給定的元素是否存在?

> sismember myset 3(integer) 1> sismember myset 30(integer) 0
  • 1

  • 2

  • 3

  • 4

“3”是這個集中的一員,而“30”不是。 
集善於表現物件之間的關係。例如我們可以很容易使用集實現標籤(tags)。處理這個問題的一個簡單的模型就是把所有要打標籤的物件設定一個集。集包含相關物件的標籤的ID。 
假設我們想要為新聞加標籤。ID為1000的新聞被打上1,2,5和77這幾個標籤,我們可以用一個集將這些標籤ID與新聞關聯起來:

> sadd news:1000:tags 1 2 5 77(integer) 4
  • 1

  • 2

然而有時我會想要相反的關係:列表中的所有新聞都被打上一個給定的標籤:

> sadd tag:1:news 1000(integer) 1> sadd tag:2:news 1000(integer) 1> sadd tag:5:news 1000(integer) 1> sadd tag:77:news 1000(integer) 1
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

要獲取一個物件的所有標籤是很麻煩的。

> smembers news:1000:tags1. 52. 13. 774. 2
  • 1

  • 2

  • 3

  • 4

  • 5

注意:在這個例子中我們假設你還有另一個資料結構,例如redis的雜湊,用於標籤ID到標籤名的對映。 
如果使用正確的redis命令,可以透過並不繁瑣卻簡單的方式去實現。例如我們可能想到同時擁有1,2,10和27標籤的所有物件。我們可以使用 SINTER 命令執行不同集之間的求交運算。我們可以這麼用:

> sinter tag:1:news tag:2:news tag:10:news tag:27:news... results here ...
  • 1

  • 2

求交集運算不是唯一可以執行的操作,你還可以執行求並集運算、求差集運算、提取任意一個元素等。 
提取一個元素的命令是 SOP ,它對於模擬某些問題很方便。例如要實現一個基於網頁的撲克牌遊戲,你可能會把你的牌(deck)做成一個集。假設我們使用一個字元字首來表示C(梅花)、D(方塊)、H(紅心)、S(黑桃):

>  sadd deck C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 CJ CQ CK
   D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 DJ DQ DK H1 H2 H3
   H4 H5 H6 H7 H8 H9 H10 HJ HQ HK S1 S2 S3 S4 S5 S6
   S7 S8 S9 S10 SJ SQ SK
   (integer) 52
  • 1

  • 2

  • 3

  • 4

  • 5

現在我們給每個玩家提供5張牌。 SPOP 命令移除一個隨機的元素,並把它返回給客戶端,因此在這個用例中是最好的操作。 
然後如果我們直接對deck呼叫它,下一輪遊戲我們需要把再次填寫所有的牌,這還不夠理想。因此在開始之前,先把集中儲存的deck鍵做一個備份到game中。使用 SUNIONSTORE 來實現,把結果存到另一個集中。這個命令通常是對多個集做求並集執行的。對一個集求並集運算就是它自己,因此可以用於複製:

> sunionstore game:1:deck deck(integer) 52
  • 1

  • 2

現在我已經準備好為第一個玩家發五張牌了。

> spop game:1:deck"C6"> spop game:1:deck"CQ"> spop game:1:deck"D1"> spop game:1:deck"CJ"> spop game:1:deck"SJ"
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

一對J,不太好。。。 
現在是時候介紹集中元素個數的命令了。在集理論中,元素個數常被為集的勢,因此這個命令是 SCARD

> scard game:1:deck(integer) 47
  • 1

  • 2

計算公式:52-5=47 
如果你只是想得到一個隨機的元素但不把它從集中刪除, SRANDMEMBER 命令適合這個任務。它還可以提供返回重複元素或非重要元素的功能。

Redis的有序集

有序集像一種將集和雜湊混合的資料型別。像集一樣,有序集由唯一的不重複的字串元素組成。因此某種意義上說,有序集也是一個集。 
集中的元素是無序的,而有序集中的元素都基於一個相關聯的浮點值排序。這個浮點值稱為分數(score)(每個元素都對映到一個值,因此和雜湊相似)。 
此外,有序集中的元素是按順序取的(它們不是按照要求排序的,排序是這個資料結構用於表現有序集的一個特性【4】)。它們按照下面的規則排序:

  • 假設A和B是分值不同的兩個元素,如果A的分數>B的分數,則A>B

  • 假設A和B的分值相同,如果字串A的字典序大於字串B的字典序,則A>B。A和B兩個字串不可能相同,因為有序集的元素是唯一的。

我們從一個簡單的例子開始,向有序集增加一些駭客的名字,將它們的出生年份作為“分數”。

> zadd hackers 1940 "Alan Kay"(integer) 1> zadd hackers 1957 "Sophie Wilson"(integer 1)> zadd hackers 1953 "Richard Stallman"(integer) 1> zadd hackers 1949 "Anita Borg"(integer) 1> zadd hackers 1965 "Yukihiro Matsumoto"(integer) 1> zadd hackers 1914 "Hedy Lamarr"(integer) 1> zadd hackers 1916 "Claude Shannon"(integer) 1> zadd hackers 1969 "Linus Torvalds"(integer) 1> zadd hackers 1912 "Alan Turing"(integer) 1
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

正如你所見, ZADD SADD 相似,但是需要一個額外的引數(位置在要加的元素之前),這就是分數。 ZADD 也是引數可變的,你可以隨意地定義多個“分數-值”對,雖然上面的例子沒有這麼寫。 
要求有序集中的駭客名單按他們的出生年份排序是沒有意義的,因為它們已經是這樣的了。 
實現細節:有序集是基於一個雙埠資料結構實現的,包含一個跳躍表和一個雜湊表。因此增加一個元素的執行時間是O(log(N))。這很好,當我們請求有序的元素時不需要其它的工作,它們已經是排序的了:

> zrange hackers 0 -1
1) "Alan Turing"2) "Hedy Lamarr"3) "Claude Shannon"4) "Alan Kay"5) "Anita Borg"6) "Richard Stallman"7) "Sophie Wilson"8) "Yukihiro Matsumoto"9) "Linus Torvalds"
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

注意:0和-1的意思是從下標為0的元素開始到最後一個元素(這裡的-1和LRANGE命令中的-1一樣)。 
如果想要反向排序,從最年輕到最老呢?使用 ZREVERANGE 代替 ZRANGE

> zrevrange hackers 0 -1
1) "Linus Torvalds"2) "Yukihiro Matsumoto"3) "Sophie Wilson"4) "Richard Stallman"5) "Anita Borg"6) "Alan Kay"7) "Claude Shannon"8) "Hedy Lamarr"9) "Alan Turing"
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

也可以同時返回分數,使用 WITHSCORES 引數:

> zrange hackers 0 -1 withscores
1) "Alan Turing"2) "1912"3) "Hedy Lamarr"4) "1914"5) "Claude Shannon"6) "1916"7) "Alan Kay"8) "1940"9) "Anita Borg"10) "1949"11) "Richard Stallman"12) "1953"13) "Sophie Wilson"14) "1957"15) "Yukihiro Matsumoto"16) "1965"17) "Linus Torvalds"18) "1969"
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

基於範圍的操作

有序集的功能強大遠不止這些。它還可以基於範圍操作。我們要取得所有出生年份早於(包括)1950年的人,就使用ZRANGEBYSCORE命令來實現:

> zrangebyscore hackers -inf 1950
1) "Alan Turing"2) "Hedy Lamarr"3) "Claude Shannon"4) "Alan Kay"5) "Anita Borg"
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

我們請求Redis返回所有分數在無限到1950(兩邊都是閉區間)之間的元素。 
也可以移除某個範圍內的元素。我們要從有序集中移除所有出生年份在1940和1960之間的駭客:

> zremrangebyscore hackers 1940 1960(integer) 4
  • 1

  • 2

ZREMRANGEBYSCORE 命令的名字也許不是很好,但是真的很有用,它返回被移除的元素的個數。 
另一個為有序集元素定義的非常有用的操作是獲取排名(get-rank)操作。可以詢問一個元素在它的有序集中的位置。

> zrank hackers "Anita Borg"(integer) 4
  • 1

  • 2

ZREVRANK 命令也可以獲取排名,不過元素是逆序排序的。

字典序的分數

最近的Redis 2.8版本引入了一個新特性,假設有序集中所有元素的分數相同(使用C語言的memcmp函式來比較元素,這樣保證每個redis例項都會返回相同的結果)的情況下,允許按照字典序獲得範圍。【5】 
針對字典序範圍操作的主要命令是 ZRANGEBYLEX ZREVRANGEBYLEX ZREMRANGEBYLEX ZLEXCOUNT 。 
舉個例子,我們再次把所有著名駭客加入到列表中,但這一次所有元素的分數都是0:

> zadd hackers 0 "Alan Kay" 0 "Sophie Wilson" 0 "Richard Stallman" 0  "Anita Borg" 0 "Yukihiro Matsumoto" 0 "Hedy Lamarr" 0 "Claude Shannon"
  0 "Linus Torvalds" 0 "Alan Turing"
  • 1

  • 2

  • 3

基於有序集的排序規則,它們是字典序排序的:

> zrange hackers 0 -1
1) "Alan Kay"2) "Alan Turing"3) "Anita Borg"4) "Claude Shannon"5) "Hedy Lamarr"6) "Linus Torvalds"7) "Richard Stallman"8) "Sophie Wilson"9) "Yukihiro Matsumoto"
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

我們可以使用ZRANGEBYLEX命令請求字典序的範圍:

> zrangebylex hackers [B [P
1) "Claude Shannon"2) "Hedy Lamarr"3) "Linus Torvalds"
  • 1

  • 2

  • 3

  • 4

範圍是開區間還是閉區間都可以(由第一個字元決定),字串正無窮和負無窮分別透過+和-來定義。更多資訊請檢視文件。 
這個特性很重要,它使得我們使用有序集作為一個通常索引。舉個例子,如果你想要使用一個128位的無符號整數來為元素索引,你所要做的只是把元素加入到一個有序集並設定一個相同的分數(比如0)以及一個由128位數值組成的8位元組字首。由於數值是大端的,字典序的順序(原始位元組序)實際上是數字序的,你可以在128位空間請求範圍,以字首降序返回元素的值。 
如果你想檢視這個特性更嚴謹的演示,請檢視 Redis autocomplete demo.

更新分數:排行榜

這是在切換到下一個話題之前最後一個關於有序集的點。有序集的分數可以隨時被更新。只需要對一個在有序集中已經存在的元素執行 ZADD 就可以在O(log(N))更新它的分數(和位置)。同樣的,當會經常更新時使用有序集非常合適。 
這個特性的一個通常用例是排行榜。典型的應用是Facebook的一個遊戲,你可以使使用者基於它們的高分排序,增加獲取排名的操作,在排行榜上顯示前N個使用者及使用者排名(例:你是第4932好的分數)

點陣圖

點陣圖其實不是一個真正的資料型別,只是在這種字串型別上有一系列基於位的操作。由於字串是二進位制安全的,最大長度是512MB,因此可以設定多達2^32種不同的位串。 
位操作分為兩類,一種是針對某一個位的常數時間操作,如果設定某個位為1或0,或獲取某個位的值。另一種是對所有位的操作,例如計算一個給定範圍內被設定為1的位的個數(如人數統計)。 
點陣圖一個最大的優勢就是儲存資訊時非常少空間。例如一個系統裡面每個使用者用一個不同的遞增的使用者ID表示,可以記錄40億使用者的某個資訊(這個使用者是否想要接收郵件)只需要512M的記憶體。 
透過 SETBIT 命令和 GETBIT 命令設定或獲取位:

> setbit key 10 1(integer) 1> getbit key 10(integer) 1> getbit key 11(integer) 0
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

SETBIT 命令把第一個引數作為位的序號,第二個引數作為這個位要設定的值,只能是1或0。如果地址位大於當前字串的長度,這個命令會自動擴充字串。 
GETBIT 只返回位於某個位置的值。超過長度的位(地址位大於字串的長度)將必然得到0。 
這三個命令是對所有位的操作:

  1. BITOP :執行不同字串之間的位操作。包括AND、OR、XOR和NOT。

  2. BITCOUNT :執行統計操作,返回位集中1的個數

  3. BITPOS :找到第一個被設定為0或1的位置

BITPOS BITCOUNT 都可以接受位的地址範圍作為引數,這樣就不對對整個字串做操作。下面是一個關於 BITCOUNT 的簡單例子:

> setbit key 0 1(integer) 0> setbit key 100 1(integer) 0> bitcount key(integer) 2
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

點陣圖的通常用法:

  • 各種實時分析

  • 儲存要求空間時間高效的與物件ID關聯的二進位制資訊。

假如你想知道你的網頁使用者中每天訪問最長的時間【6】,你從開始記錄天數,你把你的網頁公開化的第一天開始,計數為0,然後每當有使用者使用者網頁,使用 SETBIT 設定一位。位的下標可以簡單的認為是當前系統時間,減去第一天得到偏移,然後除以3600*24。 
使用這種方式,對於每一個使用者,你有一個包含每天訪問資訊的字串來表示。使用 BITCOUNT 命令可以得到某個使用者訪問網頁的天數。而使用多個 BITOPs 呼叫,可能簡單地獲取和分析點陣圖,就很容易地計算出longest streak。 
為了共享資料或避免對一個很大的鍵操作,bitmap可以分裂成多個鍵。要把一個點陣圖分裂成多個鍵而不是全部設定到一個鍵裡面去,一個比較麻煩的方法就是讓每個鍵儲存M位,鍵名為鍵的數量/M,第N個位在這個鍵裡的地址是數的位置%M。

HyperLogLogs

HyperLogLogs是一個機率性的資料結構,用於計算特別的東西(技術上常用於估算一個集的勢)。透過計算一個特別的表項需要使用相對於要計算的表項本身來說很大的記憶體,因為需要記住你已經見過的元素,以免重複計算。然後有一系列演算法可以按精度使用記憶體:在redis的實現中,你最終得到一個標準錯誤的估計測量結果的可能性少於1%【7】。這個演算法的神奇之處在於你再大需要相對於要計算的表項本身來說很大的記憶體空間,可能是隻使用一個常數數量的空間。最壞情況下12K位元組,如果元素少的話,這個空間會更小。 
Redis中的HLL,從技術上講,它是一個完全不同的資料結構,但像字串一樣編碼,因此你可以使用 GET 來序列化一個HLL,使用 SET 並行到伺服器。 
從概念上講,HLL的介面使用集來完成相同的工作。你會使用SADD把每一個觀測值寫入集中,使用 SCARD 來查詢集中的元素個數。因為 SADD 不會重複新增一個已存在的元素,因此集中的元素是唯一的。 
但是你不能把一個表項真正地新增入一個HLL,因為資料結構只包含並不真正存在的元素的狀態,API是一樣的:

  • 每當你看見一個新的元素,使用 PFADD 計數增加

  • 每當你想要恢復當前的近似值,使用 PFCOUNT 。【8】

> pfadd hll a b c d(integer) 1> pfcount hll(integer) 4
  • 1

  • 2

  • 3

  • 4

使用這種資料結構的一個例子統計在一個調整中使用者每天執行的請求數。 
Redis還可以對HLL執行求並集操作,請查詢完整檔案獲取更多資訊。

其它顯著的特性

關於redis的介面,還有其它一些重要的資訊不能放在這個文件中,但是非常值得引起你的注意:

  • 可以遞增地迭代鍵空間

  • 可以在伺服器端執行LUA指令碼獲取潛在因素和頻寬

  • redis還是一個釋出-訂閱型伺服器

更多

這篇教程不可能很完整,只是覆蓋了一些基礎的API。讀《命令引數》獲取更多資訊。 
感謝閱讀本文,祝你redis之旅愉快。

參考文獻: 
【1】什麼是二進位制安全? 
答: http://www.cnblogs.com/lovevivi/p/3159132.html  
【2】什麼是鍵空間? 
【3】這句話看不懂,原文如下 
Calling a read-only command such as LLEN (which returns the length of the list), or a write command removing elements, with an empty key, always produces the same result as if the key is holding an empty aggregate type of the type the command expects to find. 
【4】原文 
so they are not ordered on request, order is a peculiarity of the data structure used to represent sorted sets。 
【5】原文 
With recent versions of Redis 2.8, a new feature was introduced that allows getting ranges lexicographically, assuming elements in a sorted set are all inserted with the same identical score (elements are compared with the C memcmp function, so it is guaranteed that there is no collation, and every Redis instance will reply with the same output). 
【6】原文 
For example imagine you want to know the longest streak of daily visits of your web site users. 
【7】原文 
you end with an estimated measure with a standard error, in the case of the Redis implementation, which is less than 1% 
【8】 Every time you want to retrieve the current approximation of the unique elements added with PFADD so far, you use thePFCOUNT.

REDIS所有的命令

 

<<ABOUT LIST>>

LPOP key :                    刪除並取得LIST頭部一個元素

RPOP key :                    刪除並取得LIST尾部一個元素

BLPOP key [key ...] timeout : 刪除並取得LIST頭部一個元素,如果沒有就BLOCK

BRPOP key [key ...] timeout : 刪除並取得LIST尾部一個元素,如果沒有就BLOCK

LPUSH key value :             在LIST頭部擴充套件一個元素

RPUSH key value :             在LIST尾部擴充套件一個元素

LPUSHX key value :            如果LIST存在,在LIST頭部擴充套件一個元素

RPUSHX key value :            如果LIST存在,在LIST尾部擴充套件一個元素

LINDEX key index :            透過INDEX取得LIST的一個元素

LLEN key :                    取得LIST的長度

LRANGE key start stop :       取得LIST在指定範圍內的元素

LREM key count value :        刪除LIST的元素們

LSET key index value :        設定LIST索引為INDEX的元素的值

LTRIM key start stop :        裁剪LIST,保留一定範圍的元素

RPOPLPUSH source destination :刪除當前LIST的尾部一個元素,並將其擴充套件到另一個LIST的尾部

BRPOPLPUSH source destination timeout :

                              彈出LIST一個元素,並將其插入到另一個LIST裡,然後返回,如果前個LIST空就BLOCK

SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] :

                              排序LIST,SET, SORTED SET

 

<<ABOUT SET>>

SADD key member :                       向SET中新增一個成員

SREM key member :                       從SET中刪除一個成員

SDIFF key [key ...] :                   集合求差集

SINTER key [key ...] :                  集合求交集

SUNION key [key ...] :                  集合求並集

SDIFFSTORE destination key [key ...] :  集合求差集,並儲存結果集到另一集合

SINTERSTORE destination key [key ...] : 集合求交集,並儲存結果集到另一集合

SUNIONSTORE destination key [key ...] : 集合求並集,並儲存結果集到另一集合

SCARD key :                             取得SET成員總數

SISMEMBER key member :                  判斷給定值是否為SET成員

SPOP key :                              刪除並返回SET任一成員

SRANDMEMBER key :                       返回SET任一成員

SMEMBERS key :                          取得SET所有成員

SMOVE source destination member :       將一個SET中一個成員移動到另一個SET中

 

<<ABOUT SORTED SET>>

ZADD key score member :                 在SSET中新增一個成員,或者說更新已有成員的SCORE

ZCARD key :                             取得SSET的成員總數

ZCOUNT key min max :                    計算SSET中SCORE在一個給定範圍內的成員總數

ZINCRBY key :                           為SSET中的成員自增SCORE

ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] :

                                        求SSET交集,並將結果集儲存到一個新KEY

ZRANGE key start stop [WITHSCORES] :    返回SSET中一定INDEX範圍內的成員

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] : 返回SSET中一定SCORE範圍內的成員

ZREM key member :                       刪除SSET中一個成員

ZREMRANGEBYRANK key start stop :        刪除SSET一定INDEX範圍內的成員

ZREMRANGEBYSCORE key min max :          刪除SSET一定SCORE範圍內的成員

ZREVRANGE key start stop [WITHSCORES] : 返回SSET中一定INDEX範圍內的成員,其順序是SCORE從高到低

ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] : 返回SSET中一定SCORE範圍內的成員,其順序是SCORE從高到低

ZSCORE key member :                     獲得SSET中與給定MEMBER關聯的SCORE

ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] :

                                        SSET求並集,並將結果集存到一個新的KEY中

 

<<ABOUT HASH>>

HDEL key field :                            刪除一個HASHFIELD

HEXISTS key field :                         判斷一個HASHFIELD是否存在

HGET key field :                            獲得一個HASHFIELD的值

HGETALL key :                               獲得一個HASH所有的FIELDs和VALUEs

HINCRBY key field increment :               對HASHFIELD的整數值增加increment的整數值

HKEYS key :                                 獲得HASH所有的FIELD

HLEN key :                                  獲得HASH內FIELD總數

HMGET key field [field ...] :               獲得HASH指定FIELD的所有值

HMSET key field value [field value ...] :   設定HASH的一些FILED和VALUE

HSET key field value :                      設定HASH的某FIELD為某值

HSETNX key field value :                    當HASH的某FIELD不存在時候,設定其為某值

HVALS key :                                 獲得HASH的所有值

 

<<ABOUT KEY>>

DEL key [key ...] :         刪除一個KEY

GET key :                   獲得一個KEY的值

SETEX key seconds value :   設定KEY的VALUE和EXP時間

SETNX key value :           設定KEY的VALUE,前提是KEY之前不存在

SET key value :             設定KEY,VALUE

APPEND key value :          向一個KEY擴充套件一個值

DEBUG OBJECT key :          獲得一個KEY的DEBUG資訊

DECR key :                  給一個KEY-1

DECRBY key integer :        給一個KEY-integer

EXISTS key :                判斷一個KEY是否存在

EXPIRE key seconds :        設定一個KEY的TTL(second)

EXPIREAT key timestamp :    設定一個KEY的TTL(unix timestamp)

TTL key :                   獲得KEY的TTL

PERSIST key :               刪除一個KEY的過期標誌

KEYS pattern :              查詢所有符合PATTERN的KEY

MOVE key db :               將一個KEY移動到另一個DATABASE中

RENAME key newkey :         重新命名一個KEY

RENAMENX key newkey :       給一個KEY重新命名,前提是新KEYNAME不存在

RANDOMKEY :                 從KEYSPACE返回一個任一的KEY

 

<<ABOUT VALUE>>

GETRANGE key start end :            獲得KEY對應的字串裡指定範圍的子串

GETSET key value :                  設定KEY對應的VALUE,並返回老的VALUE

INCR key :                          為KEY對應的整數值自增1

INCRBY key increment :              為KEY對應的整數值自增increment

MGET key [key ...] :                獲得所有指定KEY的值

MSET key value [key value ...] :    為指定的KEYS設定指定的VALUES

MSETNX key value [key value ...] :  當指定的KEY存在時候,為指定的KEYS設定指定的VALUES

STRLEN key :                        獲得KEY的VALUE長度

 

 

<<ABOUT SERVER>>

INFO :                                          獲得伺服器的狀態資訊和統計資訊

MONITOR :                                       實時監聽SERVER獲得的所有請求

PING :                                          Ping伺服器

QUIT :                                          關閉連結

PUBLISH channel message :                       釋出一個訊息到一個CHANNEL

AUTH password :                                 認證伺服器

LASTSAVE :                                      獲得最後一次成功SAVETODISK的時間戳

OBJECT subcommand [arguments [arguments ...]] : 偵測REDIS物件的內部

PSUBSCRIBE pattern [pattern ...] :              監聽釋出到CHANNEL的所有符合PATTERN的訊息

PUNSUBSCRIBE [pattern [pattern ...]] :          停止監聽釋出到CHANNEL的所有符合PATTERN的訊息

CONFIG RESETSTAT :                              重設INFO命令返回的狀態資訊

SUBSCRIBE channel [channel ...] :               監聽指定CHANNEL的訊息

UNSUBSCRIBE [channel [channel ...]] :           停止監聽指定CHANNEL的訊息

UNWATCH : Forget about all watched keys         停止監視所有被監視的KEY

WATCH key [key ...] :                           監視所有給定的KEY,來判斷MULTI和EXEC塊的執行

 

<<ABOUT DATABASE>>

SAVE Synchronously :    儲存DATASET到硬碟

SELECT index :          切換當前資料庫

BGSAVE :                非同步儲存DATASET到硬碟

DBSIZE :                    返回一個DATABASE的KEY總數

FLUSHALL :                  刪除所有DATABASE上所有的KEY

FLUSHDB :                   刪除當前DATABASE上所有的KEY

SHUTDOWN Synchronously :                        儲存DATASET到硬碟後,關閉伺服器

 

<<CONFIGURE>>

CONFIG GET parameter :          獲得一個配置引數值

CONFIG SET parameter value :    設定一個配置引數為給定值

 

<<OTHER>>

GETBIT key offset :             返回KEY對應的VALUE在OFFSET的位元值

MULTI :                         標識一個業務塊的開始

SETRANGE key offset value :     從指定的OFFSET開始覆蓋寫KEY對應的VALUE串

BGREWRITEAOF :                  非同步重寫append-only file

DEBUG SEGFAULT :                使伺服器crash

DISCARD :                       忽略所有的以MULTI開頭的命令

ECHO message :                  ECHO message

EXEC :                          執行所有以MULTI開頭的命令

SLAVEOF host port :             使本伺服器成為另一REDIS HOST的SLAVE,或者使本伺服器成為主伺服器

SYNC :                          內部備份命令

 

LINSERT key BEFORE|AFTER refvalue value : 向列表key的refvalue之前或者之後插入value

ZRANK key member : Determine the index of a member in a sorted set

ZREVRANK key member : Determine the index of a member in a sorted set, with scores ordered from high to low

SETBIT key offset value : Sets or clears the bit at offset in the string value stored at key

TYPE key : Determine the type stored at key


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31557424/viewspace-2285166/,如需轉載,請註明出處,否則將追究法律責任。

相關文章