1. Redis事務
Redis中的事務(transaction)是一組命令的集合,一個事務中的命令要么都執行,要么都不執行。事務的原理是先將屬於一個事務的命令發送給Redis,然后再讓Redis依次執行這些命令。
127.0.0.1:6379> multi OK 127.0.0.1:6379> sadd user:1:following 2 QUEUED 127.0.0.1:6379> sadd user:2:followers 1 QUEUED 127.0.0.1:6379> EXEC 1) (integer) 1 2) (integer) 1
multi命令告訴redis,發送的sadd命令屬於同一個事務,先將其暫存起來,隨后Redis沒有執行這些命令,返回QUEUE表示這兩條命令已進入等待執行的事務隊列。EXEC命令將等待執行的事務隊列中的所有命令按發送順序依次執行,其返回值為這些命令的返回值組成的列表。
若在發送EXEC命令前客戶端斷線了,則Redis會清空事務隊列,事務中的所有命令均不執行;客戶端若發送了EXEC命令,即使客戶端斷線,事務隊列中的命令也會執行。Redis中的事務也能保證一個事務內的命令依次執行而不被其他命令插入。
(1) 錯誤處理
1) 語法錯誤,當命令不存在或命令參數個數不對。只要一個命令有語法錯誤,執行EXEC命令后Redis就會直接返回錯誤,語法正確也不會執行。
例:
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET key value QUEUED 127.0.0.1:6379> SET key (error) ERR wrong number of arguments for 'set' command 127.0.0.1:6379> ERRORCOMMAND key (error) ERR unknown command 'ERRORCOMMAND' 127.0.0.1:6379> EXEC (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379>
2) 運行錯誤,指在命令執行時出現的錯誤,在事務中這樣的命令會被Redis接受並執行,若事務中一條命令出現運行錯誤,其他命令依然會執行。
例:
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET key 1 QUEUED 127.0.0.1:6379> SADD key 2 QUEUED 127.0.0.1:6379> SET key 3 QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 3) OK
注:Redis的事務沒有關系數據庫事務提供的回滾(rollback)功能。
(2) WATCH命令
在某些情況下,需要先獲得一條命令的返回值,然后再根據該值執行下一條命令。WAHCH命令可以監控一個或多個鍵,一旦其中有一個鍵被修改,之后的事務就不會執行。監控一直持續到EXEC命令(事務中的命令是在EXEC之后才執行的,所以在MULTI命令后可以修改WATCH監控的鍵值)。
例:
127.0.0.1:6379> SET key 1 OK 127.0.0.1:6379> WATCH key OK 127.0.0.1:6379> SET key 2 OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET key 3 QUEUED 127.0.0.1:6379> EXEC (nil)
例:通過事務實現incr函數
def incr($key) WATCH $key $value = GET $key if not $value $value = 0 $value = $value + 1 MULTI SET $key $value result = EXEC return result[0]
執行EXEC命令后會取消對所有鍵的監控,若不想執行事務中的命令也可以使用UNWATCH命令來取消監控。
例:實現與HSETNX命令類似的函數hsetxx
def hsetxx($key,$field,$value) WATCH $key $isFieldExists = HEXISTS $key, $field # 判斷要賦值的字段是否存在 if $isFieldExists is 1 MULTI HSET $key, $field, $value EXEC else #不存在時需使用UNWATCH保證下一個事務的執行不回受到影響。 UNWATCH return $isFieldExists
2. 生存時間
(1) 在Redis中可以使用EXPIRE命令設置一個鍵的生存時間,到時間后Redis會自動刪除它。格式為:EXPIRE key seconds,seconds參數表示鍵的生存時間,單位是秒。
例:
127.0.0.1:6379> set session:29e3d uid1314 OK # 設置鍵在15分鍾被刪除,返回1表示設置成功,返回0表示不存在或設置失敗 127.0.0.1:6379> EXPIRE session:29e3d 900 (integer) 1 127.0.0.1:6379> del session:29e3d (integer) 1 127.0.0.1:6379> EXPIRE session:29e3d 900 (integer) 0
可以使用TTL命令查詢鍵的剩余時間。注意:返回值為-1時表示沒有為鍵設置生存時間,即永久存在。
例:
127.0.0.1:6379> SET foo bar OK 127.0.0.1:6379> EXPIRE foo 20 (integer) 1 127.0.0.1:6379> TTL foo (integer) 14 #當鍵不存在時TTL命令會返回-2 127.0.0.1:6379> TTL foo (integer) -2
使用PERSIST命令可以取消鍵的生存時間設置,生存時間成功清除返回1。
例:
127.0.0.1:6379> set foo bar OK 127.0.0.1:6379> EXPIRE foo 40 (integer) 1 127.0.0.1:6379> ttl foo (integer) 36 127.0.0.1:6379> PERSIST foo (integer) 1 127.0.0.1:6379> TTL foo (integer) -1
使用SET或GETSET命令為鍵賦值也會同時清除鍵的生存時間,如:
127.0.0.1:6379> EXPIRE foo 40 (integer) 1 127.0.0.1:6379> TTL foo (integer) 36 127.0.0.1:6379> SET foo bar OK 127.0.0.1:6379> TTL foo (integer) -1
使用EXPIRE命令會重新設置鍵的生存時間,其余只對鍵進行操作的命令均不會影響鍵的生存時間。
EXPIRE命令的seconds參數最小單位是1秒,PEXPIRE命令的單位是毫秒,對應的可以使用PTTL命令以毫秒為單位返回鍵的剩余時間。
若使用WATCH命令監測一個擁有生存時間的鍵,該鍵時間到期自動刪除並不會被WATCH命令認為該鍵被改變。
EXPIREAT(PEXPIREAT)與EXPIRE(PEXPIRE)的差別在於前者使用UNIX時間戳作為生存時間的截止時間,如:
127.0.0.1:6379> SET foo bar OK 127.0.0.1:6379> EXPIRE foo 1455113775 (integer) 1 127.0.0.1:6379> TTL foo (integer) 1455113770 127.0.0.1:6379> PEXPIRE foo 1455113675000 (integer) 1 127.0.0.1:6379> TTL foo (integer) 1455113671
(2) 實現訪問頻率
限制每分鍾每個用戶最多只能訪問100個頁面:
$isKeyExists = EXISTS rate.limiting:$IP if $isKeyExists is 1 $times = INCR rate.limiting:$IP if $times > 100 print 訪問頻率超過了限制,請稍后再試 exit else MULTI INCR rate.limiting:$IP EXPIRE $keyName, 60 EXEC
3. 排序
(1) 有序集合的集合操作
對於不常用到的或在不損失過多性能的前提下可使用現有命令實現的功能,Redis就不會單獨提供命令來實現。
(2) SORT命令
SORT命令可以對列表、集合、有序集合進行排序,並完成與關系數據庫中的連接查詢相類似的任務。
例:
# 對集合進行排序 127.0.0.1:6379> SADD tag:ruby:posts 2 12 6 26 (integer) 6 127.0.0.1:6379> SORT tag:ruby:posts 1) "2" 2) "6" 3) "12" 4) "26" # 對列表進行排序 127.0.0.1:6379> lpush list 4 2 6 1 3 7 (integer) 6 127.0.0.1:6379> lrange list 0 -1 1) "7" 2) "3" 3) "1" 4) "6" 5) "2" 6) "4" 127.0.0.1:6379> sort list 1) "1" 2) "2" 3) "3" 4) "4" 5) "6" 6) "7" # 對有序集合進行排序,會忽略元素的分數,只針對元素自身的值進行排序 127.0.0.1:6379> ZADD myzset 50 2 40 3 20 1 60 5 (integer) 4 127.0.0.1:6379> ZRANGE myzset 0 -1 withscores 1) "1" 2) "20" 3) "3" 4) "40" 5) "2" 6) "50" 7) "5" 8) "60" 127.0.0.1:6379> SORT myzset 1) "1" 2) "2" 3) "3" 4) "5"
SORT命令也可通過ALPHA參數實現按照字典順序排列非數字元素:
127.0.0.1:6379> LPUSH mylistalpha a c e d B C A (integer) 7 127.0.0.1:6379> SORT mylistalpha (error) ERR One or more scores can't be converted into double 127.0.0.1:6379> SORT mylistalpha ALPHA 1) "a" 2) "A" 3) "B" 4) "c" 5) "C" 6) "d" 7) "e"
SORT命令的DESC參數可以實現將元素按照從大到小的順序排列:
127.0.0.1:6379> SORT tag:ruby:posts DESC 1) "26" 2) "12" 3) "6" 4) "2"
SORT命令還支持LIMIT參數返回指定范圍的結果,格式為LIMIT offset count,表示跳過offset個元素並獲取之后的count個元素。
127.0.0.1:6379> SORT tag:ruby:posts DESC LIMIT 1 2 1) "12" 2) "6"
SORT對文章ID排序意義不大,如博客使用散列類型存儲文章對象,time字段對應文章的發布時間,ID為2,6,12,26的四篇文章的time字段分別為1452619200,1452619600,1452620100,1452620000,如果按照文章的發布時間遞減排序結果應為12,26,6,2,可通過SORT的BY參數可以實現。格式為:BY 參考鍵,其中參考鍵可為字符串類型鍵或散列類型鍵的某個字段(表示為鍵名->字段名)。SORT命令對每個元素使用元素的值替換參考鍵的第一個"*"並獲取其值,然后依據該值對元素排序。
127.0.0.1:6379> HSET post:2 time 1452619200 (integer) 1 127.0.0.1:6379> HSET post:6 time 1452619600 (integer) 1 127.0.0.1:6379> HSET post:12 time 1452620100 (integer) 1 127.0.0.1:6379> HSET post:26 time 1452620000 (integer) 1 # 散列類型 127.0.0.1:6379> SORT tag:ruby:posts BY post:*->time DESC 1) "12" 2) "26" 3) "6" 4) "2" #字符串類型 127.0.0.1:6379> LPUSH sortbylist 2 1 3 (integer) 3 127.0.0.1:6379> SET itemscore:1 50 OK 127.0.0.1:6379> SET itemscore:2 100 OK 127.0.0.1:6379> SET itemscore:3 -10 OK 127.0.0.1:6379> LRANGE sortbylist 0 -1 1) "3" 2) "1" 3) "2" 127.0.0.1:6379> SORT sortbylist by itemscore:* DESC 1) "2" 2) "1" 3) "3" # 當參考鍵名不包含"*"時,SORT命令將不會執行排序操作 127.0.0.1:6379> SORT sortbylist by anytext 1) "3" 2) "1" 3) "2" # 如果幾個元素的參考值相同,則SORT命令會再比較元素本身的值來決定元素的順序 127.0.0.1:6379> SORT sortbylist BY itemscore:* DESC 1) "2" 2) "4" 3) "1" 4) "3" # 當某個元素的值不存在時,會默認參考鍵的值為0 127.0.0.1:6379> SORT sortbylist BY itemscore:* DESC 1) "2" 2) "4" 3) "1" 4) "5" 5) "3"
注:參考鍵雖然支持散列類型,但是"*"只能在"->"符號前面(即鍵名部分)才有用,在"->"后(即字段名部分)會被當成字段名本身而不會作為占位符被元素的值替換,即常量鍵名,因此如下結果:
127.0.0.1:6379> SORT sortbylist BY itescore:2->itemscore:* 1) "1" 2) "2" 3) "3" 4) "4" 5) "5"
SORT的GET參數不影響排序,它的作用時使SORT命令返回結果不再是元素自身的值,而是GET參數中指定的鍵值,GET參數也支持字符串類型和散列類型的鍵,並使用"*"作為占位符。
127.0.0.1:6379> HSET post:2 title Java (integer) 1 127.0.0.1:6379> HSET post:6 title MySQL (integer) 1 127.0.0.1:6379> HSET post:12 title Redis (integer) 1 127.0.0.1:6379> HSET post:26 title Hadoop (integer) 1 127.0.0.1:6379> SORT tag:ruby:posts BY post:*->time DESC GET post:*->title 1) "Redis" 2) "Hadoop" 3) "MySQL" 4) "Java" # SORT命令可以有多個GET參數,BY參數只能有一個 127.0.0.1:6379> SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time 1) "Redis" 2) "1452620100" 3) "Hadoop" 4) "1452620000" 5) "MySQL" 6) "1452619600" 7) "Java" 8) "1452619200" # "GET #"可以返回文章的ID,"GET #"會返回元素本身的值 127.0.0.1:6379> SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time GET # 1) "Redis" 2) "1452620100" 3) "12" 4) "Hadoop" 5) "1452620000" 6) "26" 7) "MySQL" 8) "1452619600" 9) "6" 10) "Java" 11) "1452619200" 12) "2"
SORT命令的STORE參數可以將排序結果保存起來,保存后的鍵的類型為list,返回值為list的個數:
127.0.0.1:6379> SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time GET # STORE sort.result (integer) 12 127.0.0.1:6379> TYPE sort.result list 127.0.0.1:6379> LRANGE sort.result 0 -1 1) "Redis" 2) "1452620100" 3) "12" 4) "Hadoop" 5) "1452620000" 6) "26" 7) "MySQL" 8) "1452619600" 9) "6" 10) "Java" 11) "1452619200" 12) "2"
STORE參數常用來結合EXPIRE命令緩存排序結果:
#判斷是否存在之前排序結果的緩存 $isCacheExists = EXISTS cache.sort if $isCacheExists is 1 # 若存在,直接返回 return LRANGE cache.sort 0 -1 else # 若不存在,則使用SORT命令排序並將結果存入cache.sort鍵中作為緩存 $sortResult = SORT some.list STORE cache.sort # 設置緩存的生存時間為10分鍾 EXPIRE cache.sort 600 # 返回排序結果 return $sortResult
注:SORT命令的時間復雜度是O(n+mlogm),其中n表示要排序的列表中元素的個數,m表示要返回的元素個數,所以在開發中使用SORT命令需注意:1)盡可能減少待排序鍵中元素的數量;2)使用LIMIT參數只獲取需要的數據;3)如果要排序的數據數量較大,盡可能使用STORE參數將結果緩存。