熱身:系統級命令
1. 獲得符合規則的鍵名列表
KEYS pattern
模式匹配
產品的緩存:product+"."+....; => keys product*
訂單的緩存:order+"."+...; => keys order*
使用 KEYS *能獲得Redis中的所有鍵
提示:KEYS命令需要遍歷Redis中的所有鍵,當鍵的數量較多時會影響性能,不建議在生產環境中使用。Redis不區分命令大小寫。
2. 判斷一個鍵是否存在
EXISTS key
如果鍵存在則返回整數類型1,否則返回0。
3. 刪除鍵
DEL key [key …]
可以刪除一個或多個鍵,返回值是刪除的鍵的個數。
4. 獲得鍵值的數據類型
TYPE key
5.清空數據庫
Flushdb
6.過期時間
Expire(秒為單位)
如果要做緩存,那么一定要有一個過期時間。
7.查看剩余存活時間。
ttl 查看key還有多長存活時間。(秒為單位)
為了讓你更全面地了解 Redis的每種數據類型,接下來我會先講解如何將Redis作為數據庫使用,但是實際上 Redis可不只是數據庫這么簡單,更多的公司和團隊將 Redis用作緩存和隊列系統,而這部分內容等你掌握了Redis的基礎后我會再進行介紹,下面將介紹redis的五大數據類型,並通過實戰一個博客管理系統的原理來講解他們的應用。
一、字符串類型
1. 介紹
字符串類型是 Redis 中最基本的數據類型,它能存儲任何形式的字符串,包括二進制數據。你可以用其存儲用戶的郵箱、JSON 化的對象甚至是一張圖片。一個字符串類型鍵允許存儲的數據的最大容量是512 MB。
字符串類型是其他4種數據類型的基礎,其他數據類型和字符串類型的差別從某種角度來說只是組織字符串的形式不同。例如,列表類型是以列表的形式組織字符串,而集合類型是以集合的形式組織字符串。
2. 命令
(1)賦值與取值
SET key value
GET key
key等於”hello”在Redis中是這樣表示的:
redis> SET key hello
OK
想要讀取鍵值則更簡單:
redis> GET key "hello"
當鍵不存在時會返回空結果。
(2) 遞增數字
INCR key
前面說過字符串類型可以存儲任何形式的字符串,當存儲的字符串是整數形式時, Redis 提供了一個實用的命令 INCR,其作用是讓當前鍵值遞增,並返回遞增后的值,用法為:
redis> INCR num (integer) 1 redis> INCR num (integer) 2
當要操作的鍵不存在時會默認鍵值為0,所以第一次遞增后的結果是1。當鍵值不是整數時Redis會提示錯誤。
包括 INCR在內的所有Redis命令都是原子操作(atomic operation)的。
原子操作取“原子”的“不可拆分”的意思,原子操作是最小的執行單位,不會在執行的過程中被其他命令插入打斷。
3. 實戰應用
(1) 博客文章訪問量統計
我們可以為每篇文章使用一個名為post:文章ID:page.view的鍵來記錄文章的訪問量,每次訪問文章的時候使用INCR命令使相應的鍵值遞增。
Redis 對於鍵的命名並沒有強制的要求,但比較好的實踐是用“對象類型:對象ID:對象屬性”來命名一個鍵,如使用鍵user:1:friends來存儲ID為1的用戶的好友列表。對於多個單詞則推薦使用“.”分隔。
(2)生成自增ID
Redis中的實現方法:對於每一類對象使用名為對象類型(復數形式):count的鍵(如users:count)來存儲當前類型對象的數量,每增加一個新對象時都使用INCR命令遞增該鍵的值。由於使用INCR命令建立的鍵的初始鍵值是1,所以可以很容易得知, INCR命令的返回值既是加入該對象后的當前類型的對象總數,又是該新增對象的ID。
(3)存儲文章數據
由於每個字符串類型鍵只能存儲一個字符串,而一篇博客文章是由標題、正文、作者與發布時間等多個元素構成的。為了存儲這些元素,我們需要使用序列化函數(如PHP中的 serialize和JavaScript中的 JSON.stringify)將它們轉換成一個字符串。除此之外因為字符串類型鍵可以存儲二進制數據,所以也可以使用MessagePack進行序列化,速度更快,占用空間也更小。
4. 其他命令
(1)增加指定的整數
INCRBY key increment
INCRBY命令與INCR命令基本一樣,只不過前者可以通過increment參數指定一次增加的數值,如:
redis> INCRBY bar 2 (integer) 2 redis> INCRBY bar 3 (integer) 5
(2)減少指定的整數
DECR key DECRBY key decrement
DECR命令與INCR命令用法相同,只不過是讓鍵值遞減,例如:
redis> DECR bar (integer) 4
(3)增加指定浮點數
INCRBYFLOAT key increment
INCRBYFLOAT命令類似INCRBY命令,差別是前者可以遞增一個雙精度浮點數,如:
redis> INCRBYFLOAT bar 2.7 "6.7" redis> INCRBYFLOAT bar 5E+4 "50006.69999999999999929"
(4)向尾部追加值
APPEND key value
APPEND作用是向鍵值的末尾追加value。如果鍵不存在則將該鍵的值設置為value,即相當於 SET key value。返回值是追加后字符串的總長度。
如:
redis> SET key hello OK redis> APPEND key " world!" (integer) 12
此時 key 的值是"hello world!"。
APPEND 命令的第二個參數加了雙引號,原因是該參數包含空格,在redis-cli中輸入需要雙引號以示區分。
(5)獲取字符串長度
STRLEN key
STRLEN命令返回鍵值的長度,如果鍵不存在則返回0。
例如:
redis> STRLEN key (integer) 12 redis> SET key 你好 OK redis> STRLEN key (integer) 6
字符串類型可以存儲二進制數據,所以它可以存儲任何編碼的字符串。例子中Redis接收到的是使用UTF-8編碼的中文,由於“你”和“好”兩個字的UTF-8編碼的長度都是3,所以此例中會返回6。
(6)同時獲得/設置多個鍵值
MGET key [key …]
MSET key value [key value …]
MGET/MSET 與GET/SET 相似,不過MGET/MSET 可以同時獲得/設置多個鍵的鍵值。
例如:
redis> MSET key1 v1 key2 v2 key3 v3 OK redis> GET key2 "v2" redis> MGET key1 key3 1) "v1" 2) "v3"
(7)位操作
GETBIT key offset
SETBIT key offset value
BITCOUNT key [start] [end]
BITOP operation destkey key [key …]
一個字節由8個二進制位組成,Redis提供了4個命令可以直接對二進制位進行操作。
1)GETBIT命令可以獲得一個字符串類型鍵指定位置的二進制位的值(0或1),索引從0開始,如果需要獲取的二進制位的索引超出了鍵值的二進制位的實際長度則默認位值是0。
2)SETBIT 命令可以設置字符串類型鍵指定位置的二進制位的值,返回值是該位置的舊值。
3)BITCOUNT命令可以獲得字符串類型鍵中值是1的二進制位個數。
4)BITOP命令可以對多個字符串類型鍵進行位運算,並將結果存儲在destkey參數指定的鍵中。BITOP命令支持的運算操作有AND、OR、XOR和NOT。
5)Redis 2.8.7引入了 BITPOS命令,可以獲得指定鍵的第一個位值是0或者1的位置。
如果不設置結束字節且鍵值的所有二進制位都是1,則當要查詢值為0的二進制位偏移量時,返回結果會是鍵值長度的下一個字位的偏移量。這是因為 Redis 會認為鍵值長度之后的二進制位都是0。
(7)位操作應用舉例
利用位操作命令可以非常緊湊地存儲布爾值。比如如果網站的每個用戶都有一個遞增的整數ID,如果使用一個字符串類型鍵配合位操作來記錄每個用戶的性別(用戶ID作為索引,二進制位值1和0表示男性和女性),那么記錄100萬個用戶的性別只需占用100 KB多的空間,而且由於GETBIT和SETBIT的時間復雜度都是O(1),所以讀取二進制位值性能很高。
使用 SETBIT 命令時,如果當前鍵的鍵值長度小於要設置的二進制位的偏移量時,Redis會自動分配內存並將鍵值的當前長度到指定的偏移量之間的二進制位都設置為0。如果要分配的內存過大,則很可能會造成服務器的暫時阻塞而無法接收同一時間的其他請求。
還是舉剛才存儲網站用戶性別的例子,如果這個網站的用戶ID是從100000001開始的,那么會造成10多MB的浪費,正確的做法是給每個用戶的ID減去100000000再進行存儲。
二、散列類型
1. 介紹
我們現在已經知道 Redis 是采用字典結構以鍵值對的形式存儲數據的,而散列類型(hash)的鍵值也是一種字典結構,其存儲了字段(field)和字段值的映射,但字段值只能是字符串,不支持其他數據類型,換句話說,散列類型不能嵌套其他的數據類型。一個散列類型鍵可以包含至多2^32−1個字段。
除了散列類型,Redis 的其他數據類型同樣不支持數據類型嵌套。比如集合類型的每個元素都只能是字符串,不能是另一個集合或散列表等。
散列類型適合存儲對象:使用對象類別和 ID 構成鍵名,使用字段表示對象的屬性,而字段值則存儲屬性值。例如要存儲ID為2的汽車對象,可以分別使用名為color、name和price的3個字段來存儲該輛汽車的顏色、名稱和價格。
回想關系數據庫中存儲汽車對象:
增加一個屬性后對於ID為2和3的兩條字段而言data字段是冗余的。
Redis 的散列類型則不存在這個問題。雖然我們在圖 3-5 中描述了汽車對象的存儲結構,但是這個結構只是人為的約定,Redis並不要求每個鍵都依據此結構存儲,我們完全可以自由地為任何鍵增減字段而不影響其他鍵。
2. 命令
(1)HSET命令用來給字段賦值,而HGET命令用來獲得字段的值。
HSET 命令的方便之處在於不區分插入和更新操作,這意味着修改數據時不用事先判斷字段是否存在來決定要執行的是插入操作(update)還是更新操作(insert)。當執行的是插入操作時(即之前字段不存在)HSET命令會返回1,當執行的是更新操作時(即之前字段已經存在)HSET命令會返回0。更進一步,當鍵本身不存在時,HSET命令還會自動建立它。
需要同時設置多個字段的值時,可以使用HMSET命令。
Redis中每個鍵都屬於一個明確的數據類型,如通過 HSET命令建立的鍵是散列類型,通過SET命令建立的鍵是字符串類型等等。使用一種數據類型的命令操作另一種數據類型的鍵會提示錯誤:"ERR Operation against a key holding the wrong kind of value"。
如果想獲取鍵中所有字段和字段值卻不知道鍵中有哪些字段時應該使用HGETALL命令。如:
redis> HGETALL car 1) "price" 2) "500" 3) "name" 4) "BMW"
(2)判斷字段是否存在
HEXISTS key field
HEXISTS命令用來判斷一個字段是否存在。如果存在則返回1,否則返回0(如果鍵不存在也會返回0)。
(3)當字段不存在時賦值
HSETNX key field value
HSETNX命令與HSET命令類似,區別在於如果字段已經存在,HSETNX命令將不執行任何操作。
(4)增加指定數字
HINCRBY key field increment
(5)刪除字段
HDEL key field [field …]
HDEL命令可以刪除一個或多個字段,返回值是被刪除的字段個數:
redis> HDEL car price (integer) 1 redis> HDEL car price (integer) 0
3. 實戰應用
(1)存儲文章數據
可以使用 HGETALL 命令獲得一個對象的所有字段,刪除一個對象時只需要刪除一個鍵,另外存儲同樣的數據散列類型往往比字符串類型更加節約空間。
(2)存儲文章縮略名
每個文章的縮略名必須是唯一的,所以在發布文章時程序需要驗證用戶輸入的縮略名是否存在,同時也需要通過縮略名獲得文章的ID。
我們可以使用一個散列類型的鍵slug.to.id來存儲文章縮略名和ID之間的映射關系。其中字段用來記錄縮略名,字段值用來記錄縮略名對應的ID。這樣就可以使用HEXISTS命令來判斷縮略名是否存在,使用HGET命令來獲得縮略名對應的文章ID了。
4. 其他命令
(1)只獲取字段名或字段值
HKEYS key
HVALS key
(2)獲得字段數量
HLEN key
例如:
redis> HLEN car (integer) 2
三、列表類型
1. 介紹
列表類型(list)可以存儲一個有序的字符串列表,常用的操作是向列表兩端添加元素,或者獲得列表的某一個片段。
列表類型內部是使用雙向鏈表(double linked list)實現的,所以向列表兩端添加元素的時間復雜度為O(1),獲取越接近兩端的元素速度就越快。這意味着即使是一個有幾千萬個元素的列表,獲取頭部或尾部的10條記錄也是極快的(和從只有20個元素的列表中獲取頭部或尾部的10條記錄的速度是一樣的),不過使用鏈表的代價是通過索引訪問元素比較慢。
這種特性使列表類型能非常快速地完成關系數據庫難以應付的場景:
如社交網站的新鮮事,我們關心的只是最新的內容,使用列表類型存儲,即使新鮮事的總數達到幾千萬個,獲取其中最新的100條數據也是極快的。同樣因為在兩端插入記錄的時間復雜度是O(1),列表類型也適合用來記錄日志,可以保證加入新日志的速度不會受到已有日志數量的影響。
與散列類型鍵最多能容納的字段數量相同,一個列表類型鍵最多能容納2^32−1個元素。
2.命令
(1)向列表兩端增加元素
LPUSH key value [value …]
RPUSH key value [value …]
LPUSH命令用來向列表左邊增加元素,返回值表示增加元素后列表的長度。
redis> LPUSH numbers 1 (integer) 1
LPUSH命令還支持同時增加多個元素,例如:
redis> LPUSH numbers 2 3 (integer) 3
LPUSH會先向列表左邊加入"2",然后再加入"3",所以此時numbers鍵中的數據如圖3-9所示。
向列表右邊增加元素的話則使用RPUSH命令。
(2)從列表兩端彈出元素
LPOP key
RPOP key
有進有出,LPOP命令可以從列表左邊彈出一個元素。LPOP命令執行兩步操作:第一步是將列表左邊的元素從列表中移除,第二步是返回被移除的元素值。
例如,從 numbers列表左邊彈出一個元素(也就是"3"):
redis> LPOP numbers "3"
RPOP命令可以從列表右邊彈出一個元素。
結合上面提到的 4 個命令可以使用列表類型來模擬棧和隊列的操作:如果想把列表當做棧,則搭配使用LPUSH和LPOP或RPUSH和RPOP,如果想當成隊列,則搭配使用LPUSH和RPOP或RPUSH和LPOP。
(3)獲取列表中元素的個數
LLEN key
當鍵不存在時LLEN會返回0:
redis> LLEN numbers (integer) 3
LLEN 命令的功能類似SQL語句 SELECT COUNT(*) FROM table_name,但是 LLEN的時間復雜度為O(1),使用時Redis會直接讀取現成的值,而不需要像部分關系數據庫(如使用InnoDB存儲引擎的MySQL表)那樣需要遍歷一遍數據表來統計條目數量。
(4)獲得列表片段
LRANGE key start stop
LRANGE命令是列表類型最常用的命令之一,它能夠獲得列表中的某一片段。LRANGE命令將返回索引從 start到 stop之間的所有元素(包含兩端的元素)。與大多數人的直覺相同,Redis的列表起始索引為0。
LRANGE命令也支持負索引,表示從右邊開始計算序數,如"−1"表示最右邊第一個元素,"-2"表示最右邊第二個元素,依次類推。
顯然,LRANGE numbers 0 -1 可以獲取列表中的所有元素。
另外一些特殊情況如下:
1)如果start的索引位置比stop的索引位置靠后,則會返回空列表。
2)如果stop大於實際的索引范圍,則會返回到列表最右邊的元素。
(5)刪除列表中指定的值
LREM key count value
LREM命令會刪除列表中前count個值為value的元素,返回值是實際刪除的元素個數。根據count值的不同,LREM命令的執行方式會略有差異。
1)當 count > 0時 LREM 命令會從列表左邊開始刪除前 count 個值為 value的元素。
2)當 count < 0時 LREM 命令會從列表右邊開始刪除前|count|個值為 value 的元素。
3)當 count = 0是 LREM命令會刪除所有值為 value的元素。
3. 實戰應用
(1)存儲文章ID列表
我們使用列表類型鍵posts:list記錄文章ID列表。當發布新文章時使用LPUSH命令把新文章的ID加入這個列表中,另外刪除文章時也要記得把列表中的文章ID 刪除,就像這樣:
LREM posts:list 1 要刪除的文章的ID
有了文章 ID列表,就可以使用 LRANGE命令來實現文章的分頁顯示了。
(2)存儲評論列表
我們可以將一條評論的各個元素序列化成字符串后作為列表類型鍵中的元素來存儲。使用列表類型鍵post:文章的ID:comments來存儲某個文章的所有評論。
發布評論的偽代碼如下(以ID為42的文章為例):
#將評論序列化成字符串 $serializedComment = serialize($author, $email, $time, $content) LPUSH post:42:comments, $serializedComment
讀取評論時同樣使用LRANGE命令即可。
4.其他命令
(1)獲得/設置指定索引的元素值
LINDEX key index
LSET key index value
如果要將列表類型當作數組來用,LINDEX命令是必不可少的。LINDEX命令用來返回指定索引的元素,索引從0開始。
LSET是另一個通過索引操作列表的命令,它會將索引為index的元素賦值為value。
例如:
redis> LSET numbers 1 7 OK redis> LINDEX numbers 1 "7"
(2)只保留列表指定片段
LTRIM key start end LTRIM 命令可以刪除指定索引范圍之外的所有元素,其指定列表范圍的方法和LRANGE命令相同。
LTRIM命令常和LPUSH命令一起使用來限制列表中元素的數量,比如記錄日志時我們希望只保留最近的100條日志,則每次加入新元素時調用一次LTRIM命令即可:
LPUSH logs $newLog LTRIM logs 0 99
(3)向列表中插入元素
LINSERT key BEFORE|AFTER pivot value
LINSERT 命令首先會在列表中從左到右查找值為 pivot 的元素,然后根據第二個參數是BEFORE還是AFTER來決定將value插入到該元素的前面還是后面。
LINSERT命令的返回值是插入后列表的元素個數。
(4)將元素從一個列表轉到另一個列表
RPOPLPUSH source destination
RPOPLPUSH是個很有意思的命令,從名字就可以看出它的功能:先執行RPOP命令再執行LPUSH命令。RPOPLPUSH命令會先從source列表類型鍵的右邊彈出一個元素,然后將其加入到destination列表類型鍵的左邊,並返回這個元素的值,整個過程是原子的。
當把列表類型作為隊列使用時,RPOPLPUSH 命令可以很直觀地在多個隊列中傳遞數據。當source和destination相同時,RPOPLPUSH命令會不斷地將隊尾的元素移到隊首,借助這個特性我們可以實現一個網站監控系統:
使用一個隊列存儲需要監控的網址,然后監控程序不斷地使用 RPOPLPUSH 命令循環取出一個網址來測試可用性。這里使用RPOPLPUSH命令的好處在於在程序執行過程中仍然可以不斷地向網址列表中加入新網址,而且整個系統容易擴展,允許多個客戶端同時處理隊列。
四、集合類型
1.介紹
集合的概念高中的數學課就學習過。在集合中的每個元素都是不同的,且沒有順序。一個集合類型(set)鍵可以存儲至多2^32 −1個(相信這個數字對大家來說已經很熟悉了)字符串。
集合類型的常用操作是向集合中加入或刪除元素、判斷某個元素是否存在等,由於集合類型在Redis內部是使用值為空的散列表(hash table)實現的,所以這些操作的時間復雜度都是O(1)。最方便的是多個集合類型鍵之間還可以進行並集、交集和差集運算,稍后就會看到靈活運用這一特性帶來的便利。
2.命令
(1)增加/刪除元素
SADD key member [member …]
SREM key member [member …]
SADD 命令用來向集合中增加一個或多個元素,如果鍵不存在則會自動創建。因為在一個集合中不能有相同的元素,所以如果要加入的元素已經存在於集合中就會忽略這個元素。本命令的返回值是成功加入的元素數量(忽略的元素不計算在內)。
SREM命令用來從集合中刪除一個或多個元素,並返回刪除成功的個數。
(2)獲得集合中的所有元素
SMEMBERS key
SMEMBERS命令會返回集合中的所有元素。
(3)判斷元素是否在集合中
SISMEMBER key member
判斷一個元素是否在集合中是一個時間復雜度為O(1)的操作,無論集合中有多少個元素,SISMEMBER命令始終可以極快地返回結果。當值存在時 SISMEMBER命令返回1,當值不存在或鍵不存在時返回0。
(4)集合間運算
SDIFF key [key „]
SINTER key [key „]
SUNION key [key „]
1)SDIFF命令用來對多個集合執行差集運算。集合A與集合B的差集表示為A−B,代表所有屬於A且不屬於B的元素構成的集合。
2)SINTER命令用來對多個集合執行交集運算。集合A與集合B的交集表示為A ∩ B,代表所有屬於A 且屬於B的元素構成的集合。
3)SUNION命令用來對多個集合執行並集運算。
3. 實戰應用
(1)存儲文章標簽
考慮到一個文章的所有標簽都是互不相同的,而且展示時對這些標簽的排列順序並沒有要求,我們可以使用集合類型鍵存儲文章標簽。
具體操作偽代碼如下:
# 給 ID 為 42 的文章增加標簽: SADD post:42:tags, 閑言碎語, 技術文章, Java # 刪除標簽: SREM post:42:tags, 閑言碎語 # 顯示所有的標簽: $tags = SMEMBERS post:42:tags print $tags
(2)通過標簽搜索文章
有時我們還需要列出某個標簽下的所有文章,甚至需要獲得同時屬於某幾個標簽的文章列表,這種需求在傳統關系數據庫中實現起來比較復雜,下面舉一個例子。
現有3張表,即posts、tags和posts_tags,分別存儲文章數據、標簽、文章與標簽的對應關系。結構分別如表3-5、表3-6、表3-7所示。
為了找到同時屬於“Java”、“MySQL”和“Redis”這3個標簽的文章,需要使用如下的SQL語句:
SELECT p.post_title FROM posts_tags pt, posts p, tags t WHERE pt.tag_id = t.tag_id AND (t.tag_name IN ('Java', 'MySQL', 'Redis')) AND p.post_id = pt.post_id GROUP BY p.post_id HAVING COUNT(p.post_id)=3;
可以很明顯看到這樣的 SQL 語句不僅效率相對較低,而且不易閱讀和維護。而使用Redis可以很簡單直接地實現這一需求。
具體做法是為每個標簽使用一個名為tag:標簽名稱:posts的集合類型鍵存儲標有該標簽的文章ID列表。假設現在有3篇文章,ID分別為1、2、3,其中ID為1的文章標簽是“Java”,ID 為 2 的文章標簽是“Java”、“MySQL”,ID 為 3 的文章標簽是“Java”、“MySQL”和“Redis”,則有關標簽部分的存儲結構如圖3-18所示:
最簡單的,當需要獲取標記“MySQL”標簽的文章時只需要使用命令 SMEMBERS tag:MySQL:posts即可。如果要實現找到同時屬於Java、MySQL和Redis 3 個標簽的文章,只需要將tag:Java:posts、tag:MySQL:posts和tag:Redis:posts這3個鍵取交集,借助SINTER命令即可輕松完成。
4. 其他命令
(1)獲得集合中元素個數
SCARD key
SCARD命令用來獲得集合中的元素個數。
(2)進行集合運算並將結果存儲
SDIFFSTORE destination key [key …]
SINTERSTORE destination key [key …]
SUNIONSTORE destination key [key …]
SDIFFSTORE命令和SDIFF命令功能一樣,唯一的區別就是前者不會直接返回運算結果,而是將結果存儲在destination鍵中。
SDIFFSTORE命令常用於需要進行多步集合運算的場景中,如需要先計算差集再將結果和其他鍵計算交集。SINTERSTORE和SUNIONSTORE命令與之類似,不再贅述。
(3)隨機獲得集合中的元素
SRANDMEMBER key [count]
SRANDMEMBER命令用來隨機從集合中獲取一個元素,還可以傳遞count參數來一次隨機獲得多個元素。
根據count的正負不同,具體表現也不同。
1)當count為正數時,SRANDMEMBER會隨機從集合里獲得count個不重復的元素。如果count的值大於集合中的元素個數,則SRANDMEMBER會返回集合中的全部元素。
2)當count為負數時,SRANDMEMBER會隨機從集合里獲得|count|個的元素,這些元素有可能相同。
(4)從集合中彈出一個元素
SPOP key
由於集合類型的元素是無序的,所以 SPOP命令會從集合中隨機選擇一個元素彈出。
五、有序集合類型
1. 介紹
在集合類型的基礎上有序集合類型為集合中的每個元素都關聯了一個分數,這使得我們不僅可以完成插入、刪除和判斷元素是否存在等集合類型支持的操作,還能夠獲得分數最高(或最低)的前N個元素、獲得指定分數范圍內的元素等與分數有關的操作。雖然集合中每個元素都是不同的,但是它們的分數卻可以相同。
有序集合類型在某些方面和列表類型有些相似。
(1)二者都是有序的。
(2)二者都可以獲得某一范圍的元素。
但是二者有着很大的區別,這使得它們的應用場景也是不同的。
(1)列表類型是通過鏈表實現的,獲取靠近兩端的數據速度極快,而當元素增多后,訪問中間數據的速度會較慢,所以它更加適合實現如“新鮮事”或“日志”這樣很少訪問中間元素的應用。
(2)有序集合類型是使用散列表和跳躍表(Skip list)實現的,所以即使讀取位於中間部分的數據速度也很快(時間復雜度是O(log(N)))。
(3)列表中不能簡單地調整某個元素的位置,但是有序集合可以(通過更改這個元素的分數)。
(4)有序集合要比列表類型更耗費內存。
有序集合類型算得上是Redis的5種數據類型中最高級的類型了,在學習時可以與列表類型和集合類型對照理解。
2. 命令
(1)增加元素
ZADDkey score member [score member …]
ZADD 命令用來向有序集合中加入一個元素和該元素的分數,如果該元素已經存在則會用新的分數替換原有的分數。ZADD命令的返回值是新加入到集合中的元素個數(不包含之前已經存在的元素)。
+inf和-inf分別表示正無窮和負無窮。
(2)獲得元素的分數
ZSCORE key member
(3)獲得排名在某個范圍的元素列表
ZRANGE key start stop [WITHSCORES]
ZREVRANGE key start stop [WITHSCORES]
ZRANGE命令會按照元素分數從小到大的順序返回索引從 start到stop之間的所有元素(包含兩端的元素)。負數代表從后向前查找(−1表示最后一個元素)。
如果需要同時獲得元素的分數的話可以在 ZRANGE 命令的尾部加上 WITHSCORES 參數。
ZRANGE命令的時間復雜度為O(log n+m)(其中n為有序集合的基數,m為返回的元素個數)。
(4)獲得指定分數范圍的元素
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
ZRANGEBYSCORE 命令參數雖然多,但是都很好理解。該命令按照元素分數從小到大的順序返回分數在min和max之間(包含min和max)的元素:
redis> ZRANGEBYSCORE scoreboard 80 100 1) "Tom" 2) "David"
如果希望分數范圍不包含端點值,可以在分數前加上“(”符號。例如,希望返回80分到100分的數據,可以含80分,但不包含100分,則稍微修改一下上面的命令即可:
redis> ZRANGEBYSCORE scoreboard 80 (100 1) "Tom"
(5)增加某個元素的分數
ZINCRBY key increment member
ZINCRBY 命令可以增加一個元素的分數,返回值是更改后的分數。例如,想給 Jerry加4分:
redis> ZINCRBY scoreboard 4 Jerry "60"
increment也可以是個負數表示減分,例如,給Jerry減4分:
redis> ZINCRBY scoreboard -4 Jerry "56"
如果指定的元素不存在,Redis 在執行命令前會先建立它並將它的分數賦為 0 再執行操作。
3. 實戰應用
(1)實現按點擊量排序
要按照文章的點擊量排序,就必須再額外使用一個有序集合類型的鍵來實現。在這個鍵中以文章的 ID 作為元素,以該文章的點擊量作為該元素的分數。將該鍵命名為posts:page.view,每次用戶訪問一篇文章時,博客程序就通過 ZINCRBY posts:page. view 1 文章 ID更新訪問量。
需要按照點擊量的順序顯示文章列表時,有序集合的用法與列表的用法大同小異:
$postsPerPage = 10 $start = ($currentPage - 1) * $postsPerPage $end = $currentPage * $postsPerPage - 1 $postsID = ZREVRANGE posts:page.view, $start,$end for each $id in $postsID $postData = HGETALL post:$id print 文章標題:$postData.title
另外介紹字符串類型時用鍵post:文章ID:page.view來記錄單個文章的訪問量,現在這個鍵已經不需要了,想要獲得某篇文章的訪問量可以通過 ZSCORE posts:page. view文章ID 來實現。
(2)更改文章發布時間和獲得指定時間范圍內的文章列表
為了能夠自由地更改文章發布時間,可以采用有序集合類型代替列表類型。自然地,元素仍然是文章的ID,而此時元素的分數則是文章發布的Unix時間。通過修改元素對應的分數就可以達到更改時間的目的。另外借助 ZREVRANGEBYSCORE 命令還可以輕松獲得指定時間范圍的文章列表,借助這個功能可以實現類似WordPress的按月份查看文章的功能。
4. 其他命令
(1)獲得集合中元素的數量
ZCARD key
例如:
redis> ZCARD scoreboard (integer) 6
(2)獲得指定分數范圍內的元素個數
ZCOUNT key min max
例如:
redis> ZCOUNT scoreboard 90 100 (integer) 2
(3)刪除一個或多個元素
ZREM key member [member …]
ZREM命令的返回值是成功刪除的元素數量(不包含本來就不存在的元素)。
(4)按照排名范圍刪除元素
ZREMRANGEBYRANK key start stop
ZREMRANGEBYRANK 命令按照元素分數從小到大的順序(即索引0表示最小的值)刪除處在指定排名范圍內的所有元素,並返回刪除的元素數量。
(5)按照分數范圍刪除元素
ZREMRANGEBYSCORE key min max
ZREMRANGEBYSCORE命令會刪除指定分數范圍內的所有元素,參數min和max的特性和ZRANGEBYSCORE命令中的一樣。返回值是刪除的元素數量。
(6)獲得元素的排名
ZRANK key member
ZREVRANK key member
ZRANK命令會按照元素分數從小到大的順序獲得指定的元素的排名
(從0開始,即分數最小的元素排名為0)。如:
redis> ZRANK scoreboard Peter
(integer) 0
ZREVRANK命令則相反(分數最大的元素排名為0):
redis> ZREVRANK scoreboard Peter (integer) 4
(7)計算有序集合的交集
ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight…]] [AGGREGATESUM|MIN|MAX]
ZINTERSTORE命令用來計算多個有序集合的交集並將結果存儲在destination鍵中(同樣以有序集合類型存儲),返回值為destination鍵中的元素個數。
destination鍵中元素的分數是由AGGREGATE參數決定的。
(1)當AGGREGATE是SUM時(也就是默認值),destination鍵中元素
的分數是每個參與計算的集合中該元素分數的和。
(2)當AGGREGATE是MIN時,destination鍵中元素的分數是每個參與
計算的集合中該元素分數的最小值。
(3)當AGGREGATE是MAX時,destination鍵中元素的分數是每個參與
計算的集合中該元素分數的最大值。
ZINTERSTORE命令還能夠通過WEIGHTS參數設置每個集合的權重,每
個集合在參與計算時元素的分數會被乘上該集合的權重。
總結
數據類型 |
結構存儲的值 |
結構的讀寫能力 |
博客系統中的應用 |
字符串類型 |
可以是字符串、整數或者浮點數 |
對整個字符串或字符串的其中一部分執行操作;對整數和浮點數執行自增或者自減操作 |
(1) 博客文章訪問量統計 (2)生成自增ID |
散列類型 |
包含鍵值對的無序散列表 |
添加、獲取、移除單個鍵值對;獲取所有鍵值對 |
(1)存儲文章數據 (2)存儲文章縮略名 |
列表類型 |
一個鏈表,鏈表上的每個節點都包含了一個字符串 |
從鏈表的兩端推入或者彈出元素;根據偏移量對鏈表進行修剪(trim);讀取單個或多個元素;根據值查找或者移除元素 |
(1)存儲文章ID列表 (2)存儲評論列表 |
集合類型 |
包含字符串的無序收集器,並且被包含的每個字符串都獨一無二、各不相同 |
添加、獲取、移除單個元素;檢查一個元素是否存在於集合中;計算交集、並集、差集;從集合里面隨機獲取元素 |
(1)存儲文章標簽 (2)通過標簽搜索文章 |
有序集合類型 |
字符串成員與浮點數分值之間的有序映射,元素的排列順序由分值的大小決定 |
添加、獲取、刪除單個元素;根據分值范圍或者成員來獲取元素 |
(1)實現按點擊量排序 (2)更改文章發布時間和獲得指定時間范圍內的文章列表 |