《Redis分享之你不可不知的Redis常用命令》要點:
本文介紹了Redis分享之你不可不知的Redis常用命令,希望對您有用。如果有疑問,可以聯系我們。
本文對Redis最常用的一些命令進行了介紹,其中包括各種不同數據類型的常用命令、PUBLISH命令和SUBSCRIBE命令、SORT命令、兩個事務命令MULTI和EXEC,以及與過期時間有關的幾個命令.
本文的第一個目標是讓讀者知道—Redis為每種結構都提供了大量的處理命令,本章只展示了其中最重要的70多個命令,其余的命令可以在http://redis.io/commands看到.
本文的第二個目標是讓讀者知道—本書并非為每個問題都提供了完美的答案.通過在練習里面對第1章和第2章展示的示例進行回顧(練習的答案在本書附帶的源碼里面),本書向讀者提供了一個機會,讓讀者把已經不錯的代碼變得更好,或者變得更適合于讀者自己的問題.
在本文沒有介紹到的命令當中,有一大部分都是與配置相關的,想學習更全面的Redis知識,請看《Redis實戰》,這本書的下一章將向讀者介紹如何配置Redis以確保數據安全,以及如何確保Redis擁有良好的性能.
3.1 字符串
本書在第1章和第2章曾經說過,Redis的字符串就是一個由字節組成的序列,它們和很多編程語言里面的字符串沒有什么顯著的不同,跟C或者C++風格的字符數組也相去不遠.在Redis里面,字符串可以存儲以下3種類型的值.
字節串(byte string).
整數.
浮點數.
用戶可以通過給定一個任意的數值,對存儲著整數或者浮點數的字符串執行自增(increment)或者自減(decrement)操作,在有需要的時候,Redis還會將整數轉換成浮點數.整數的取值范圍和系統的長整數(long integer)的取值范圍相同(在32位系統上,整數就是32位有符號整數,在64位系統上,整數就是64位有符號整數),而浮點數的取值范圍和精度則與IEEE 754標準的雙精度浮點數(double)相同.Redis明確地區分字節串、整數和浮點數的做法是一種優勢,比起只能夠存儲字節串的做法,Redis的做法在數據表現方面具有更大的靈活性.
本節將對Redis里面最簡單的結構—字符串進行討論,介紹基本的數值自增和自減操作,以及二進制位(bit)和子串(substring)處理命令,讀者可能會驚訝地發現,Redis里面最簡單的結構居然也有如此強大的作用.
表3-1展示了對Redis字符串執行自增和自減操作的命令.
表3-1 Redis中的自增命令和自減命令
命令 | 用例和描述 |
---|---|
INCR | INCR key-name—將鍵存儲的值加上1 |
DECR | DECR key-name—將鍵存儲的值減去1 |
INCRBY | INCRBY key-name amount—將鍵存儲的值加上整數amount |
DECRBY | DECRBY key-name amount—將鍵存儲的值減去整數amount |
INCRBYFLOAT | INCRBYFLOAT key-name amount—將鍵存儲的值加上浮點數amount,這個命令在Redis 2.6或以上的版本可用 |
當用戶將一個值存儲到Redis字符串里面的時候,如果這個值可以被解釋(interpret)為十進制整數或者浮點數,那么Redis會察覺到這一點,并允許用戶對這個字符串執行各種INCR*和DECR*操作.如果用戶對一個不存在的鍵或者一個保存了空串的鍵執行自增或者自減操作,那么Redis在執行操作時會將這個鍵的值當作是0來處理.如果用戶嘗試對一個值無法被解釋為整數或者浮點數的字符串鍵執行自增或者自減操作,那么Redis將向用戶返回一個錯誤.代碼清單3-1展示了對字符串執行自增操作和自減操作的一些例子.
代碼清單3-1 這個交互示例展示了Redis的INCR操作和DECR操作
在讀完本書其他章節之后,讀者可能會發現本書只調用了 incr(),這是因為 Python的Redis庫在內部使用INCRBY命令來實現incr()方法,并且這個方法的第二個參數是可選的:如果用戶沒有為這個可選參數設置值,那么這個參數就會使用默認值1.在編寫本書的時候,Python的Redis客戶端庫支持Redis 2.6的所有命令,這個庫通過incrbyfloat()方法來實現INCRBYFLOAT命令,并且incrbyfloat()方法也有類似于incr()方法的可選參數特性.
除了自增操作和自減操作之外,Redis還擁有對字節串的其中一部分內容進行讀取或者寫入的操作(這些操作也可以用于整數或者浮點數,但這種用法并不常見),本書在第9章將展示如何使用這些操作來高效地將結構化數據打包(pack)存儲到字符串鍵里面.表3-2展示了用來處理字符串子串和二進制位的命令.
表3-2 供Redis處理子串和二進制位的命令
命令 | 用例和描述 |
---|---|
APPEND | APPEND key-name value—將值value追加到給定鍵key-name當前存儲的值的末尾 |
GETRANGE | GETRANGE key-name start end—獲取一個由偏移量start至偏移量end范圍內所有字符組成的子串,包括start和end在內 |
SETRANGE | SETRANGE key-name offset value—將從start偏移量開始的子串設置為給定值 |
GETBIT | GETBIT key-name offset—將字節串看作是二進制位串(bit string),并返回位串中偏移量為offset的二進制位的值 |
SETBIT | SETBIT key-name offset value—將字節串看作是二進制位串,并將位串中偏移量為offset的二進制位的值設置為value |
BITCOUNT | BITCOUNT key-name [start end]—統計二進制位串里面值為1的二進制位的數量,如果給定了可選的start偏移量和end偏移量,那么只對偏移量指定范圍內的二進制位進行統計 |
BITOP | BITOP operation dest-key key-name [key-name …]—對一個或多個二進制位串執行包括并(AND)、或(OR)、異或(XOR)、非(NOT)在內的任意一種按位運算操作(bitwise operation),并將計算得出的結果保存在dest-key鍵里面 |
GETRANGE和SUBSTR Redis現在的GETRANGE命令是由以前的SUBSTR命令改名而來的,因此,Python客戶端至今仍然可以使用substr()方法來獲取子串,但如果讀者使用的是2.6或以上版本的Redis,那么最好還是使用getrange()方法來獲取子串.
在使用SETRANGE或者SETBIT命令對字符串進行寫入的時候,如果字符串當前的長度不能滿足寫入的要求,那么Redis會自動地使用空字節(null)來將字符串擴展至所需的長度,然后才執行寫入或者更新操作.在使用GETRANGE讀取字符串的時候,超出字符串末尾的數據會被視為是空串,而在使用GETBIT讀取二進制位串的時候,超出字符串末尾的二進制位會被視為是0.代碼清單3-2展示了一些字符串處理命令的使用示例.
代碼清單3-2 這個交互示例展示了Redis的子串操作和二進制位操作
很多鍵值數據庫只能將數據存儲為普通的字符串,并且不提供任何字符串處理操作,有一些鍵值數據庫允許用戶將字節追加到字符串的前面或者后面,但是卻沒辦法像Redis一樣對字符串的子串進行讀寫.從很多方面來講,即使Redis只支持字符串結構,并且只支持本節列出的字符串處理命令,Redis也比很多別的數據庫要強大得多;通過使用子串操作和二進制位操作,配合WATCH命令、MULTI命令和EXEC命令(本書的3.7.2節將對這3個命令進行初步的介紹,并在第4章對它們進行更深入的講解),用戶甚至可以自己動手去構建任何他們想要的數據結構.第9章將介紹如何使用字符串去存儲一種簡單的映射,這種映射可以在某些情況下節省大量內存.
只要花些心思,我們甚至可以將字符串當作列表來使用,但這種做法能夠執行的列表操作并不多,更好的辦法是直接使用下一節介紹的列表結構,Redis為這種結構提供了豐富的列表操作命令.
3.2 列表
在第1章曾經介紹過,Redis的列表允許用戶從序列的兩端推入或者彈出元素,獲取列表元素,以及執行各種常見的列表操作.除此之外,列表還可以用來存儲任務信息、最近瀏覽過的文章或者常用聯系人信息.
本節將對列表這個由多個字符串值組成的有序序列結構進行介紹,并展示一些最常用的列表處理命令,閱讀本節可以讓讀者學會如何使用這些命令來處理列表.表3-3展示了其中一部分最常用的列表命令.
表3-3 一些常用的列表命令
命令 | 用例和描述 |
---|---|
RPUSH | RPUSH key-name value [value …]—將一個或多個值推入列表的右端 |
LPUSH | LPUSH key-name value [value …]—將一個或多個值推入列表的左端 |
RPOP | RPOP key-name—移除并返回列表最右端的元素 |
LPOP | LPOP key-name—移除并返回列表最左端的元素 |
LINDEX | LINDEX key-name offset—返回列表中偏移量為offset的元素 |
LRANGE | LRANGE key-name start end—返回列表從start偏移量到end偏移量范圍內的所有元素,其中偏移量為start和偏移量為end的元素也會包含在被返回的元素之內 |
LTRIM | LTRIM key-name start end—對列表進行修剪,只保留從start偏移量到end偏移量范圍內的元素,其中偏移量為start和偏移量為end的元素也會被保留 |
因為本書在第1章已經對列表的幾個推入和彈出操作進行了簡單的介紹,所以讀者應該不會對上面列出的推入和彈出操作感到陌生,代碼清單3-3展示了這些操作的用法.
代碼清單3-3 這個交互示例展示了Redis列表的推入操作和彈出操作
這個示例里面第一次用到了LTRIM命令,組合使用LTRIM和LRANGE可以構建出一個在功能上類似于LPOP或者RPOP的操作,它能夠一次返回并彈出多個元素.本章稍后將會介紹原子地執行多個命令的方法,而更高級的Redis事務特性則會在第4章介紹.
有幾個列表命令可以將元素從一個列表移動到另一個列表,或者阻塞(block)執行命令的客戶端直到有其他客戶端給列表添加元素為止,這些命令在第1章都沒有介紹過,表3-4列出了這些阻塞彈出命令和元素移動命令.
表3-4 阻塞式的列表彈出命令以及在列表之間移動元素的命令
命令 | 用例和描述 |
---|---|
BLPOP | BLPOP key-name [key-name …] timeout—從第一個非空列表中彈出位于最左端的元素,或者在timeout秒之內阻塞并等待可彈出的元素出現 |
BRPOP | BRPOP key-name [key-name …] timeout—從第一個非空列表中彈出位于最右端的元素,或者在timeout秒之內阻塞并等待可彈出的元素出現 |
RPOPLPUSH | RPOPLPUSH source-key dest-key—從source-key列表中彈出位于最右端的元素,然后將這個元素推入dest-key列表的最左端,并向用戶返回這個元素 |
BRPOPLPUSH | BRPOPLPUSH source-key dest-key timeout—從source-key列表中彈出位于最右端的元素,然后將這個元素推入dest-key列表的最左端,并向用戶返回這個元素;如果source-key為空,那么在timeout秒之內阻塞并等待可彈出的元素出現 |
在第6章討論隊列時,這組命令將會非常有用.代碼清單3-4展示了幾個使用BRPOPLPUSH移動列表元素的例子以及使用BLPOP從列表里面彈出多個元素的例子.
代碼清單3-4 這個交互示例展示了Redis列表的阻塞彈出命令以及元素移動命令
對于阻塞彈出命令和彈出并推入命令,最常見的用例就是消息傳遞(messaging)和任務隊列(task queue),本書將在第6章對這兩個主題進行介紹.
練習:通過列表來降低內存占用 在2.1節和2.5節中,我們使用了有序集合來記錄用戶最近瀏覽過的商品,并把用戶瀏覽這些商品時的時間戳設置為分值,從而使得程序可以在清理舊會話的過程中或是執行完購買操作之后,進行相應的數據分析.但由于保存時間戳需要占用相應的空間,所以如果分析操作并不需要用到時間戳的話,那么就沒有必要使用有序集合來保存用戶最近瀏覽過的商品了.為此,請在保證語義不變的情況下,將update_token()函數里面使用的有序集合替換成列表.提示:如果讀者在解答這個問題時遇上困難的話,可以到6.1.1節中找找靈感.
列表的一個主要優點在于它可以包含多個字符串值,這使得用戶可以將數據集中在同一個地方.Redis的集合也提供了與列表類似的特性,但集合只能保存各不相同的元素.接下來的一節中就讓我們來看看不能保存相同元素的集合都能做些什么.
3.3 集合
Redis的集合以無序的方式來存儲多個各不相同的元素,用戶可以快速地對集合執行添加元素操作、移除元素操作以及檢查一個元素是否存在于集合里.第1章曾經對集合進行過簡單的介紹,并在構建文章投票網站時,使用集合記錄文章已投票用戶名單以及群組屬下的所有文章.
本節將對最常用的集合命令進行介紹,包括插入命令、移除命令、將元素從一個集合移動到另一個集合的命令,以及對多個集合執行交集運算、并集運算和差集運算的命令.閱讀本節也有助于讀者更好地理解本書在第7章介紹的搜索示例.
表3-5展示了其中一部分最常用的集合命令.
表3-5 一些常用的集合命令
命令 | 用例和描述 |
---|---|
SADD | SADD key-name item [item …]—將一個或多個元素添加到集合里面,并返回被添加元素當中原本并不存在于集合里面的元素數量 |
SREM | SREM key-name item [item …]—從集合里面移除一個或多個元素,并返回被移除元素的數量 |
SISMEMBER | SISMEMBER key-name item—檢查元素item是否存在于集合key-name 里 |
SCARD | SCARD key-name—返回集合包含的元素的數量 |
SMEMBERS | SMEMBERS key-name—返回集合包含的所有元素 |
SRANDMEMBER | SRANDMEMBER key-name [count]—從集合里面隨機地返回一個或多個元素.當count為正數時,命令返回的隨機元素不會重復;當count為負數時,命令返回的隨機元素可能會出現重復 |
SPOP | SPOP key-name—隨機地移除集合中的一個元素,并返回被移除的元素 |
SMOVE | SMOVE source-key dest-key item—如果集合source-key包含元素item,那么從集合source-key里面移除元素item,并將元素item添加到集合dest-key中;如果item被成功移除,那么命令返回1,否則返回0 |
表3-5里面的不少命令都已經在第1章介紹過了,代碼清單3-5展示了這些命令的使用示例.
代碼清單3-5 這個交互示例展示了Redis中的一些常用的集合命令
通過使用上面展示的命令,我們可以將各不相同的多個元素添加到集合里面,比如第1章就使用集合記錄了文章已投票用戶名單,以及文章屬于哪個群組.但集合真正厲害的地方在于組合和關聯多個集合,表3-6展示了相關的命令.
表3-6 用于組合和處理多個集合的Redis命令
命令 | 用例和描述 |
---|---|
SDIFF | SDIFF key-name [key-name …]—返回那些存在于第一個集合、但不存在于其他集合中的元素(數學上的差集運算) |
{–:}續表
命令 | 用例和描述 |
---|---|
SDIFFSTORE | SDIFFSTORE dest-key key-name [key-name …]—將那些存在于第一個集合但并不存在于其他集合中的元素(數學上的差集運算)存儲到dest-key鍵里面 |
SINTER | SINTER key-name [key-name …]—返回那些同時存在于所有集合中的元素(數學上的交集運算) |
SINTERSTORE | SINTERSTORE dest-key key-name [key-name …]—將那些同時存在于所有集合的元素(數學上的交集運算)存儲到dest-key鍵里面 |
SUNION | SUNION key-name [key-name …]—返回那些至少存在于一個集合中的元素(數學上的并集計算) |
SUNIONSTORE | SUNIONSTORE dest-key key-name [key-name …]—將那些至少存在于一個集合中的元素(數學上的并集計算)存儲到dest-key鍵里面 |
這些命令分別是并集運算、交集運算和差集運算這3個基本集合操作的“返回結果”版本和“存儲結果”版本,代碼清單3-6展示了這些命令的使用示例.
代碼清單3-6 這個交互示例展示了Redis的差集運算、交集運算以及并集運算
和Python的集合相比,Redis的集合除了可以被多個客戶端遠程地進行訪問之外,其他的語義和功能基本都是相同的.
接下來的一節將對Redis的散列處理命令進行介紹,這些命令允許用戶將多個相關的鍵值對存儲在一起,以便執行獲取操作和更新操作.
3.4 散列
第1章提到過,Redis的散列可以讓用戶將多個鍵值對存儲到一個Redis鍵里面.從功能上來說,Redis為散列值提供了一些與字符串值相同的特性,使得散列非常適用于將一些相關的數據存儲在一起.我們可以把這種數據聚集看作是關系數據庫中的行,或者文檔數據庫中的文檔.
本節將對最常用的散列命令進行介紹:其中包括添加和刪除鍵值對的命令、獲取所有鍵值對的命令,以及對鍵值對的值進行自增或者自減操作的命令.閱讀這一節可以讓讀者學習到如何將數據存儲到散列里面,以及這樣做的好處是什么.表3-7展示了一部分常用的散列命令.
表3-7 用于添加和刪除鍵值對的散列操作
命令 | 用例和描述 |
---|---|
HMGET | HMGET key-name key [key …]—從散列里面獲取一個或多個鍵的值 |
HMSET | HMSET key-name key value [key value …]—為散列里面的一個或多個鍵設置值 |
HDEL | HDEL key-name key [key …]—刪除散列里面的一個或多個鍵值對,返回成功找到并刪除的鍵值對數量 |
HLEN | HLEN key-name—返回散列包含的鍵值對數量 |
在表3-7列出的命令當中,HDEL命令已經在第1章中介紹過了,而HLEN命令以及用于一次讀取或者設置多個鍵的HMGET和HMSET則是新出現的命令.像HMGET和HMSET這種批量處理多個鍵的命令既可以給用戶帶來方便,又可以通過減少命令的調用次數以及客戶端與Redis之間的通信往返次數來提升Redis的性能.代碼清單3-7展示了這些命令的使用方法.
代碼清單3-7 這個交互示例展示了Redis中的一些常用的散列命令
第1章介紹的HGET命令和HSET命令分別是HMGET命令和HMSET命令的單參數版本,這些命令的唯一區別在于單參數版本每次執行只能處理一個鍵值對,而多參數版本每次執行可以處理多個鍵值對.
表3-8列出了散列的其他幾個批量操作命令,以及一些和字符串操作類似的散列命令.
表3-8 展示Redis散列的更高級特性
命令 | 用例和描述 |
---|---|
HEXISTS | HEXISTS key-name key—檢查給定鍵是否存在于散列中 |
HKEYS | HKEYS key-name—獲取散列包含的所有鍵 |
HVALS | HVALS key-name—獲取散列包含的所有值 |
HGETALL | HGETALL key-name—獲取散列包含的所有鍵值對 |
HINCRBY | HINCRBY key-name key increment—將鍵key保存的值加上整數increment |
HINCRBYFLOAT | HINCRBYFLOAT key-name key increment—將鍵key保存的值加上浮點數increment |
盡管有HGETALL存在,但HKEYS和HVALUES也是非常有用的:如果散列包含的值非常大,那么用戶可以先使用HKEYS取出散列包含的所有鍵,然后再使用HGET一個接一個地取出鍵的值,從而避免因為一次獲取多個大體積的值而導致服務器阻塞.
HINCRBY和HINCRBYFLOAT可能會讓讀者回想起用于處理字符串的INCRBY和INCRBYFLOAT,這兩對命令擁有相同的語義,它們的不同在于HINCRBY和HINCRBYFLOAT處理的是散列,而不是字符串.代碼清單3-8展示了這些命令的使用方法.
代碼清單3-8 這個交互示例展示了Redis散列的一些更高級的特性
正如前面所說,在對散列進行處理的時候,如果鍵值對的值的體積非常龐大,那么用戶可以先使用HKEYS獲取散列的所有鍵,然后通過只獲取必要的值來減少需要傳輸的數據量.除此之外,用戶還可以像使用SISMEMBER檢查一個元素是否存在于集合里面一樣,使用HEXISTS檢查一個鍵是否存在于散列里面.另外第1章也用到了本節剛剛回顧過的HINCRBY來記錄文章被投票的次數.
在接下來的一節中,我們要了解的是之后的章節里面會經常用到的有序集合結構.
3.5 有序集合
和散列存儲著鍵與值之間的映射類似,有序集合也存儲著成員與分值之間的映射,并且提供了分值處理命令,以及根據分值大小有序地獲取(fetch)或掃描(scan)成員和分值的命令.本書曾在第1章使用有序集合實現過基于發表時間排序的文章列表和基于投票數量排序的文章列表,還在第2章使用有序集合存儲過cookie的過期時間.
本節將對操作有序集合的命令進行介紹,其中包括向有序集合添加新元素的命令、更新已有元素的命令,以及對有序集合進行交集運算和并集運算的命令.閱讀本節可以加深讀者對有序集合的認識,從而幫助讀者更好地理解本書在第1章、第5章、第6章和第7章展示的有序集合示例.
表3-9展示了一部分常用的有序集合命令.
表3-9 一些常用的有序集合命令
命令 | 用例和描述 |
---|---|
ZADD | ZADD key-name score member [score member …]—將帶有給定分值的成員添加到有序集合里面 |
ZREM | ZREM key-name member [member …]—從有序集合里面移除給定的成員,并返回被移除成員的數量 |
ZCARD | ZCARD key-name—返回有序集合包含的成員數量 |
ZINCRBY | ZINCRBY key-name increment member—將member成員的分值加上increment |
ZCOUNT | ZCOUNT key-name min max—返回分值介于min和max之間的成員數量 |
ZRANK | ZRANK key-name member—返回成員member在有序集合中的排名 |
ZSCORE | ZSCORE key-name member—返回成員member的分值 |
ZRANGE | ZRANGE key-name start stop [WITHSCORES]—返回有序集合中排名介于start和stop之間的成員,如果給定了可選的WITHSCORES選項,那么命令會將成員的分值也一并返回 |
在上面列出的命令當中,有一部分命令已經在第1章和第2章使用過了,所以讀者應該不會對它們感到陌生,代碼清單3-9回顧了這些命令的用法.
代碼清單3-9 這個交互示例展示了Redis中的一些常用的有序集合命令
因為ZADD、ZREM、ZINCRBY、ZSCORE和ZRANGE都已經在第1章和第2章介紹過了,所以讀者應該不會對它們感到陌生.ZCOUNT命令和其他命令不太相同,它主要用于計算分值在給定范圍內的成員數量.
表3-10展示了另外一些非常有用的有序集合命令.
表3-10 有序集合的范圍型數據獲取命令和范圍型數據刪除命令,以及并集命令和交集命令
在表3-10展示的命令里面,有幾個是之前沒介紹過的新命令.除了使用逆序來處理有序集合之外,ZREV*命令的工作方式和相對應的非逆序命令的工作方式完全一樣(逆序就是指元素按照分值從大到小地排列).代碼清單3-10展示了ZINTERSTORE和ZUNIONSTORE的用法.
代碼清單3-10 這個交互示例展示了ZINTERSTORE命令和ZUNIONSTORE命令的用法
有序集合的并集運算和交集運算在剛開始接觸時可能會比較難懂,所以本節將使用圖片來展示交集運算和并集運算的執行過程.圖3-1展示了對兩個輸入有序集合執行交集運算并得到輸出有序集合的過程,這次交集運算使用的是默認的聚合函數sum,所以輸出有序集合成員的分值都是通過加法計算得出的.
圖3-1 執行conn.zinterstore(‘zset-i’, [‘zset-1’, ‘zset-2’])
將使得同時存在于zset-1和zset-2里面的元素被添加到zset-i里面
并集運算和交集運算不同,只要某個成員存在于至少一個輸入有序集合里面,那么這個成員就會被包含在輸出有序集合里面.圖3-2展示了使用聚合函數min執行并集運算的過程,min函數在多個輸入有序集合都包含同一個成員的情況下,會將最小的那個分值設置為這個成員在輸出有序集合的分值.
圖3-2 執行conn.zunionstore(‘zset-u’, [‘zset-1’, ‘zset-2’], aggregate=’min’)
會將存在于zset-1或者zset-2里面的元素通過min函數組合到zset-u里面
在第1章中,我們就基于“集合可以作為ZUNIONSTORE操作和ZINTERSTORE操作的輸入”這個事實,在沒有使用額外的有序集合來存儲群組文章的評分和發布時間的情況下,實現了群組文章的添加和刪除操作.圖3-3展示了如何使用ZUNIONSTORE命令來將兩個有序集合和一個集合組合成一個有序集合.
圖3-3 執行conn.zunionstore(‘zset-u2’, [‘zset-1’, ‘zset-2’, ‘set-1’])
將使得所有存在于zset-1、zset-2或者set-1里面的元素都被添加到zset-u2里面
第7章將使用ZINTERSTORE和ZUNIONSTORE來構建幾個不同類型的搜索系統,并說明如何通過可選的WEIGHTS參數來以幾種不同的方式組合有序集合的分值,從而使得集合和有序集合可以用于解決更多問題.
讀者在開發應用的過程中,也許曾經聽說過發布與訂閱(publish/subscribe)模式,又稱pub/sub模式,Redis也實現了這種模式,接下來的一節將對其進行介紹.
3.6 發布與訂閱
如果你因為想不起來本書在前面的哪個章節里面介紹過發布與訂閱而困惑,那么大可不必—這是本書目前為止第一次介紹發布與訂閱.一般來說,發布與訂閱(又稱pub/sub)的特點是訂閱者(listener)負責訂閱頻道(channel),發送者(publisher)負責向頻道發送二進制字符串消息(binary string message).每當有消息被發送至給定頻道時,頻道的所有訂閱者都會收到消息.我們也可以把頻道看作是電臺,其中訂閱者可以同時收聽多個電臺,而發送者則可以在任何電臺發送消息.
本節將對發布與訂閱的相關操作進行介紹,閱讀這一節可以讓讀者學會怎樣使用發布與訂閱的相關命令,并了解到為什么本書在之后的章節里面會使用其他相似的解決方案來代替Redis提供的發布與訂閱.
表3-11展示了Redis提供的5個發布與訂閱命令.
表3-11 Redis提供的發布與訂閱命令
命令 | 用例和描述 |
---|---|
SUBSCRIBE | SUBSCRIBE channel [channel …]—訂閱給定的一個或多個頻道 |
UNSUBSCRIBE | UNSUBSCRIBE [channel [channel …]]—退訂給定的一個或多個頻道,如果執行時沒有給定任何頻道,那么退訂所有頻道 |
PUBLISH | PUBLISH channel message—向給定頻道發送消息 |
PSUBSCRIBE | PSUBSCRIBE pattern [pattern …]—訂閱與給定模式相匹配的所有頻道 |
PUNSUBSCRIBE | PUNSUBSCRIBE [pattern [pattern …]]—退訂給定的模式,如果執行時沒有給定任何模式,那么退訂所有模式 |
考慮到PUBLISH命令和SUBSCRIBE命令在Python客戶端的實現方式,一個比較簡單的演示發布與訂閱的方法,就是像代碼清單3-11那樣使用輔助線程(helper thread)來執行PUBLISH命令.
代碼清單3-11 這個交互示例展示了如何使用Redis中的PUBLISH命令以及SUBSCRIBE命令
雖然Redis的發布與訂閱模式非常有用,但本書只在這一節和8.5節中使用了這個模式,這樣做的原因有以下兩個.
第一個原因和Redis系統的穩定性有關.對于舊版Redis來說,如果一個客戶端訂閱了某個或某些頻道,但它讀取消息的速度卻不夠快的話,那么不斷積壓的消息就會使得Redis輸出緩沖區的體積變得越來越大,這可能會導致Redis的速度變慢,甚至直接崩潰.也可能會導致Redis被操作系統強制殺死,甚至導致操作系統本身不可用.新版的Redis不會出現這種問題,因為它會自動斷開不符合client-output-buffer-limit pubsub配置選項要求的訂閱客戶端(本書第8章將對這個選項做更詳細的介紹).
第二個原因和數據傳輸的可靠性有關.任何網絡系統在執行操作時都可能會遇上斷線情況,而斷線產生的連接錯誤通常會使得網絡連接兩端中的其中一端進行重新連接.本書使用的Python語言的Redis客戶端會在連接失效時自動進行重新連接,也會自動處理連接池(connection pool,具體信息將在第4章介紹),諸如此類.但是,如果客戶端在執行訂閱操作的過程中斷線,那么客戶端將丟失在斷線期間發送的所有消息,因此依靠頻道來接收消息的用戶可能會對Redis提供的PUBLISH命令和SUBSCRIBE命令的語義感到失望.
基于以上兩個原因,本書在第6章編寫了兩個不同的方法來實現可靠的消息傳遞操作,這兩個方法除了可以處理網絡斷線之外,還可以防止Redis因為消息積壓而耗費過多內存(這個方法即使對于舊版Redis也是有效的).
如果你喜歡簡單易用的PUBLISH命令和SUBSCRIBE命令,并且能夠承擔可能會丟失一小部分數據的風險,那么你也可以繼續使用Redis提供的發布與訂閱特性,而不是8.5節中提供的實現,只要記得先把client-output-buffer-limit pubsub選項設置好就行了.
到目前為止,本書介紹的大多數命令都是與特定數據類型相關的.接下來的一節要介紹的命令你可能也會用到,但它們既不屬于Redis提供的5種數據結構,也不屬于發布與訂閱特性.
3.7 其他命令
到目前為止,本章介紹了Redis提供的5種結構以及Redis的發布與訂閱模式.本節將要介紹的命令則可以用于處理多種類型的數據:首先要介紹的是可以同時處理字符串、集合、列表和散列的SORT命令;之后要介紹是用于實現基本事務特性的MULTI命令和EXEC命令,這兩個命令可以讓用戶將多個命令當作一個命令來執行;最后要介紹的是幾個不同的自動過期命令,它們可以自動刪除無用數據.
閱讀本節有助于讀者更好地理解如何同時組合和操作多種數據類型.
3.7.1 排序
Redis的排序操作和其他編程語言的排序操作一樣,都可以根據某種比較規則對一系列元素進行有序的排列.負責執行排序操作的SORT命令可以根據字符串、列表、集合、有序集合、散列這5種鍵里面存儲著的數據,對列表、集合以及有序集合進行排序.如果讀者之前曾經使用過關系數據庫的話,那么可以將SORT命令看作是SQL語言里的order by子句.表3-12展示了SORT命令的定義.
表3-12 SORT命令的定義
使用SORT命令提供的選項可以實現以下功能:根據降序而不是默認的升序來排序元素;將元素看作是數字來進行排序,或者將元素看作是二進制字符串來進行排序(比如排序字符串’110’和’12’的結果就跟排序數字110和12的結果不一樣);使用被排序元素之外的其他值作為權重來進行排序,甚至還可以從輸入的列表、集合、有序集合以外的其他地方進行取值.
代碼清單3-12展示了一些SORT命令的使用示例.其中,最開頭的幾行代碼設置了一些初始數據,然后對這些數據進行了數值排序和字符串排序,最后的代碼演示了如何通過SORT命令的特殊語法來將散列存儲的數據作為權重進行排序,以及怎樣獲取并返回散列存儲的數據.
代碼清單3-12 這個交互示例展示了SORT命令的一些簡單的用法
SORT命令不僅可以對列表進行排序,還可以對集合進行排序,然后返回一個列表形式的排序結果.代碼清單3-12除了展示如何使用alpha關鍵字參數對元素進行字符串排序之外,還展示了如何基于外部數據對元素進行排序,以及如何獲取并返回外部數據.第7章將介紹如何組合使用集合操作和SORT命令:當集合結構計算交集、并集和差集的能力,與SORT命令獲取散列存儲的外部數據的能力相結合時,SORT命令將變得非常強大.
盡管SORT是Redis中唯一一個可以同時處理3種不同類型的數據的命令,但基本的Redis事務同樣可以讓我們在一連串不間斷執行的命令里面操作多種不同類型的數據.
3.7.2 基本的Redis事務
有時候為了同時處理多個結構,我們需要向Redis發送多個命令.盡管Redis有幾個可以在兩個鍵之間復制或者移動元素的命令,但卻沒有那種可以在兩個不同類型之間移動元素的命令(雖然可以使用ZUNIONSTORE命令將元素從一個集合復制到一個有序集合).為了對相同或者不同類型的多個鍵執行操作,Redis有5個命令可以讓用戶在不被打斷(interruption)的情況下對多個鍵執行操作,它們分別是WATCH、MULTI、EXEC、UNWATCH和DISCARD.
這一節只介紹最基本的Redis事務用法,即使用MULTI命令和EXEC命令.如果讀者想看看使用WATCH、MULTI、EXEC和UNWATCH等多個命令的事務是什么樣子的,可以閱讀4.4節,其中解釋了為什么需要在使用MULTI和EXEC的同時使用WATCH和UNWATCH.
什么是Redis的基本事務
Redis的基本事務(basic transaction)需要用到MULTI命令和EXEC命令,這種事務可以讓一個客戶端在不被其他客戶端打斷的情況下執行多個命令.和關系數據庫那種可以在執行的過程中進行回滾(rollback)的事務不同,在Redis里面,被MULTI命令和EXEC命令包圍的所有命令會一個接一個地執行,直到所有命令都執行完畢為止.當一個事務執行完畢之后,Redis才會處理其他客戶端的命令.
要在Redis里面執行事務,我們首先需要執行MULTI命令,然后輸入那些我們想要在事務里面執行的命令,最后再執行EXEC命令.當Redis從一個客戶端那里接收到MULTI命令時,Redis會將這個客戶端之后發送的所有命令都放入到一個隊列里面,直到這個客戶端發送EXEC命令為止,然后Redis就會在不被打斷的情況下,一個接一個地執行存儲在隊列里面的命令.從語義上來說,Redis事務在Python客戶端上面是由流水線(pipeline)實現的:對連接對象調用piepline()方法將創建一個事務,在一切正常的情況下,客戶端會自動地使用MULTI和EXEC包裹起用戶輸入的多個命令.此外,為了減少Redis與客戶端之間的通信往返次數,提升執行多個命令時的性能,Python的Redis客戶端會存儲起事務包含的多個命令,然后在事務執行時一次性地將所有命令都發送給Redis.
跟介紹PUBLISH命令和SUBSCRIBE命令時的情況一樣,要展示事務執行結果,最簡單的方法就是將事務放到線程里面執行.代碼清單3-13展示了在沒有使用事務的情況下,執行并行(parallel)自增操作的結果.
代碼清單3-13 在并行執行命令時,缺少事務可能會引發的問題
因為沒有使用事務,所以3個線程都可以在執行自減操作之前,對notrans:計數器執行自增操作.雖然代碼清單里面通過休眠100毫秒的方式來放大了潛在的問題,但如果我們確實需要在不受其他命令干擾的情況下,對計數器執行自增操作和自減操作,那么我們就不得不解決這個潛在的問題.代碼清單3-14展示了如何使用事務來執行相同的操作.
代碼清單3-14 使用事務來處理命令的并行執行問題
可以看到,盡管自增操作和自減操作之間有一段延遲時間,但通過使用事務,各個線程都可以在不被其他線程打斷的情況下,執行各自隊列里面的命令.記住,Redis要在接收到EXEC命令之后,才會執行那些位于MULTI和EXEC之間的入隊命令.
使用事務既有利也有弊,本書的4.4節將對這個問題進行討論.
練習:移除競爭條件 正如前面的代碼清單3-13所示,MULTI和EXEC事務的一個主要作用是移除競爭條件.第1章展示的article_vote()函數包含一個競爭條件以及一個因為競爭條件而出現的bug.函數的競爭條件可能會造成內存泄漏,而函數的bug則可能會導致不正確的投票結果出現.盡管article_ vote()函數的競爭條件和bug出現的機會都非常少,但為了防范于未然,你能想個辦法修復它們么?提示:如果你覺得很難理解競爭條件為什么會導致內存泄漏,那么可以在分析第1章的post_article()函數的同時,閱讀一下6.2.5節.
練習:提高性能 在Redis里面使用流水線的另一個目的是提高性能(詳細的信息會在之后的4.4節至4.6節中介紹).在執行一連串命令時,減少Redis與客戶端之間的通信往返次數可以大幅降低客戶端等待回復所需的時間.第1章的get_articles()函數在獲取整個頁面的文章時,需要在Redis與客戶端之間進行26次通信往返,這種做法簡直低效得令人發指,你能否想個辦法將get_articles()函數的往返次數從26次降低為2次呢?
在使用Redis存儲數據的時候,有些數據僅在一段很短的時間內有用,雖然我們可以在數據的有效期過了之后手動刪除無用的數據,但更好的辦法是使用Redis提供的鍵過期操作來自動刪除無用數據.
3.7.3 鍵的過期時間
在使用Redis存儲數據的時候,有些數據可能在某個時間點之后就不再有用了,用戶可以使用DEL命令顯式地刪除這些無用數據,也可以通過Redis的過期時間(expiration)特性來讓一個鍵在給定的時限(timeout)之后自動被刪除.當我們說一個鍵“帶有生存時間(time to live)”或者一個鍵“會在特定時間之后過期(expire)”時,我們指的是Redis會在這個鍵的過期時間到達時自動刪除該鍵.
雖然過期時間特性對于清理緩存數據非常有用,不過如果讀者翻一下本書的其他章節,就會發現除了6.2節、7.1節和7.2節之外,本書使用過期時間特性的情況并不多,這主要和本書使用的結構類型有關.在本書常用的命令當中,只有少數幾個命令可以原子地為鍵設置過期時間,并且對于列表、集合、散列和有序集合這樣的容器(container)來說,鍵過期命令只能為整個鍵設置過期時間,而沒辦法為鍵里面的單個元素設置過期時間(為了解決這個問題,本書在好幾個地方都使用了存儲時間戳的有序集合來實現針對單個元素的過期操作).
本節將對那些可以在給定時限之后或者給定時間之后自動刪除過期鍵的Redis命令進行介紹,閱讀本節可以讓讀者學習到使用過期操作來自動刪除過期數據并降低Redis內存占用的方法.
表3-13列出了Redis提供的用于為鍵設置過期時間的命令,以及查看鍵的過期時間的命令.
表3-13 用于處理過期時間的Redis命令
命令 | 示例和描述 |
---|---|
PERSIST | PERSIST key-name—移除鍵的過期時間 |
TTL | TTL key-name—查看給定鍵距離過期還有多少秒 |
EXPIRE | EXPIRE key-name seconds—讓給定鍵在指定的秒數之后過期 |
EXPIREAT | EXPIREAT key-name timestamp—將給定鍵的過期時間設置為給定的UNIX時間戳 |
PTTL | PTTL key-name—查看給定鍵距離過期時間還有多少毫秒,這個命令在Redis 2.6或以上版本可用 |
PEXPIRE | PEXPIRE key-name milliseconds—讓鍵給定鍵在指定的毫秒數之后過期,這個命令在Redis 2.6或以上版本可用 |
PEXPIREAT | PEXPIREAT key-name timestamp-milliseconds—將一個毫秒級精度的UNIX時間戳設置為給定鍵的過期時間,這個命令在Redis 2.6或以上版本可用 |
代碼清單3-15展示了幾個對鍵執行過期時間操作的例子.
代碼清單3-15 展示Redis中過期時間相關的命令的使用方法
練習:使用EXPIRE命令代替時間戳有序集合 2.1節、2.2節和2.5節中使用了一個根據時間戳進行排序、用于清除會話ID的有序集合,通過這個有序集合,程序可以在清理會話的時候,對用戶瀏覽過的商品以及用戶購物車里面的商品進行分析.但是,如果我們決定不對商品進行分析的話,那么就可以使用Redis提供的過期時間操作來自動清理過期的會話ID,而無須使用清理函數.那么,你能否想辦法修改在第2章定義的update_token()函數和add_to_cart()函數,讓它們使用過期時間操作來刪除會話ID,從而代替目前使用有序集合來記錄并清除會話ID的做法呢?
本文來自《Redis實戰》
維易PHP培訓學院每天發布《Redis分享之你不可不知的Redis常用命令》等實戰技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養人才。
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/9607.html