1、redis中的事務java
在關係型數據庫中事務是必不可少的一個核心功能,生活中也是到處可見,好比咱們去銀行轉帳,首先須要將A帳戶的錢划走,而後存到B帳戶上,這兩個步驟必須在同一事務中,要麼都執行,要麼都不執行,否則錢憑空消失了,換了誰也沒法接受。redis
一樣,redis中也爲咱們提供了事務,原理是:先把一組同一事務中的命令發送給redis,而後redis進行依次執行。數據庫
一、事務的語法:緩存
multi 命令1 命令2 ... exec
解釋下語法:首先經過multi命令告訴redis:「下面我發給你的命令是屬於同一事務的,你呢,先不要執行,能夠把它們先存儲起來」。redis回答:「okay啦」。然後咱們就發送銀行轉帳的命令1和命令2,這時redis將聽從約定不會執行命令,而是返回queued,表示把這兩條命令存儲到等待執行的事務隊列中了。最後咱們發送exec命令,redis開始依次執行在等待隊列中的命令,完成一個事務的操做。性能優化
redis保證事務中全部命令要麼全執行,要麼都不執行。若是在發送exec前客戶端斷線了,redis會清空等待的事務隊列,全部命令都不會執行。而一旦發送了exec,即便在執行過程當中客戶端斷線了也沒有關係,由於redis早已存儲了命令。session
下面咱們模擬下銀行轉帳的業務,從銀行A轉帳5000到銀行B,中間企圖修改銀行A的餘額,這時看看可否轉帳成功並保證金額正確?post
127.0.0.1:6379> set bankA 10000 OK 127.0.0.1:6379> get bankA "10000" 127.0.0.1:6379> set bankB 10000 OK 127.0.0.1:6379> get bankB "10000"
127.0.0.1:6379> multi OK 127.0.0.1:6379> decrby bankA 5000 QUEUED 127.0.0.1:6379> incrby bankB 5000 QUEUED 127.0.0.1:6379>
127.0.0.1:6379> decrby bankA 10000 (integer) 0 127.0.0.1:6379> get bankA "0"
127.0.0.1:6379> exec 1) (integer) -5000 #這裏假設餘額能夠透支,哈哈。變成﹣5000,而不是原來想的5000,若是實際業務,這時是沒法進行轉帳的。事務保證了餘額正確 2) (integer) 15000
二、事務錯誤處理性能
若是在執行一個事務時,裏面某個命令出錯了,redis怎麼處理呢?在redis中,須要分析致使命令錯誤的緣由,不一樣的緣由會有不一樣的處理方式。大數據
1)語法錯誤優化
語法錯誤是指該命令不存在或者參數個數不正確。對於這類錯誤,redis(2.6.5以後的版本)的處理方式是直接返回錯誤,所有不執行,即便裏面有正確的命令。
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> iiiget key (error) ERR unknown command 'iiiget' 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get key (nil) #事務中有語法錯誤的命令,即便有一個命令正確也不會被執行 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 127.0.0.1:6379> get key "3"
可見sadd key 2出錯了,可是set key 3依然被執行了。
redis中的事務不像關係型數據庫有回滾機制,爲此如出現這樣的問題,開發者必須本身收拾形成這樣的爛攤子了。
爲了保證儘可能不出現命令和數據類型不匹配的運行錯誤,事前規劃數據庫(如保證鍵名規範)是尤其重要的。
三、watch命令
watch命令能夠監控一個和多個鍵,一旦被監控鍵的值被修改,阻止以後的一個事務執行(即執行exec時返回nil,但這時watch監控也會失效),還記得上面轉帳嗎?當在轉帳事務過程當中,bankA被取走了10000,餘額變成0,這時操做轉帳時應該提示餘額不足,沒法轉帳。可使用watch命令來阻止轉帳事務的執行。下面優化一下上面的轉帳業務:
127.0.0.1:6379> watch bankA #監控銀行A帳號 OK
127.0.0.1:6379> decrby bankA 10000
(integer) 0
127.0.0.1:6379> multi
OK 127.0.0.1:6379> decrby bankA 5000 QUEUED 127.0.0.1:6379> incrby bankB 5000 QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> get bankA "0" 127.0.0.1:6379> get bankB "10000" 127.0.0.1:6379>
2、生存時間
在實際開發中常常會遇到一些有時效的數據,好比限時優惠活動、緩存或驗證碼等,過一段時間須要刪除這些數據。在關係數據庫中通常須要維護一個額外的字段來存儲過時時間,而後按期檢測刪除過時數據。而在redis中命令就能夠搞定。
一、命令
expire key seconds:返回1表示設置成功,0表示設置失敗或該鍵不存在;
127.0.0.1:6379> set lifecycle 'test life cycle' OK 127.0.0.1:6379> get lifecycle "test life cycle" 127.0.0.1:6379> expire lifecycle 30 #設置生存時間爲30秒,最小單位是秒 (integer) 1 127.0.0.1:6379> ttl lifecycle #ttl查看還剩多久 (integer) 13 127.0.0.1:6379> ttl lifecycle (integer) 11 127.0.0.1:6379> ttl lifecycle (integer) 9 127.0.0.1:6379> ttl lifecycle (integer) 8 127.0.0.1:6379> ttl lifecycle #當時間到了刪除後會返回-2,不存在的鍵也返回-2 (integer) -2
取消設置時間:persist key
除了專用的取消命令,set,getset命令也會清除key的生存時間。
pexpire key mileseconds 精確到毫秒
3、排序
在咱們實際的開發中,不少地方用到排序這個功能,上節我們說過有序集合就能夠實現排序功能,是經過它分數,redis除了有序集合外還有sort命令,sort命令很複雜,能用好它不是很容易,並且一不當心就可能致使性能問題,下面就說說這些排序。
一、有序集合排序
有序集合經常使用的場景是大數據排序,如遊戲玩家的排行榜,通常不多須要得到鍵中的全部數據。
二、sort命令
除了上面的有序集合,redis還提供了sort命令,它能夠對列表、集合、有序集合類型鍵進行排序,而且完成如關係數據庫中關聯查詢相似的任務。
127.0.0.1:6379> sadd set_sort 5 2 1 0 7 9 (integer) 6
#不是說集合是無序的嘛,明明上面添加時無序的,這裏打印出來怎麼排序了呢?是這樣的,集合經常用來存儲對象的id,通常都是整數,對於這種狀況,進行了優化,因此這裏是排序了
127.0.0.1:6379> smembers set_sort 1) "0" 2) "1" 3) "2" 4) "5" 5) "7" 6) "9" 127.0.0.1:6379> sort set_sort desc #desc是按降序排序,固然這裏的排序並不影響原始的數據 1) "9" 2) "7" 3) "5" 4) "2" 5) "1" 6) "0"
127.0.0.1:6379> lpush mylist 2 -1 3 44 5 (integer) 5 127.0.0.1:6379> lrange mylist 0 -1 1) "5" 2) "44" 3) "3" 4) "-1" 5) "2" 127.0.0.1:6379> sort mylist 1) "-1" 2) "2" 3) "3" 4) "5" 5) "44"
127.0.0.1:6379> zadd myzset 0 tom 1 helen 2 allen 3 jack (integer) 4 127.0.0.1:6379> zrange myzset 0 -1 #這是按照分數排序取得結果 1) "tom" 2) "helen" 3) "allen" 4) "jack" 127.0.0.1:6379> sort myzset (error) ERR One or more scores can't be converted into double 127.0.0.1:6379> sort myzset alpha #經過sort按照字母字典排序後的結果,這時忽略有序集合的分數了,按照元素的字典排序。 1) "allen" 2) "helen" 3) "jack" 4) "tom"
固然,若是結果集數據太多,須要分頁顯示,這時能夠用limit參數搞定:
limit offset count 表示從第offset起,共取得count個數據
127.0.0.1:6379> sort myzset alpha desc limit 2 2 #表示從第二條取2條記錄 1) "helen" 2) "allen"
三、sort命令的參數
1)By參數
有時咱們常常遇到對一個散列表中某個字段進行排序,好比寫博客時按照文章的發佈時間進行排序,取最新的文章放到首頁,這個時候sort命令的by參數就能夠派上用場了。
By參數的語法是:「by 參考鍵」,其中參考鍵能夠是字符串類型或者散列類型鍵的某個字段(散列表示爲:鍵名->字段名)若是提供了by參數,sort將不會再按照元素自身值進行排序,而是對每一個元素使用元素值替換參考鍵中的第一個‘*’並獲取其值,而後依據該值進行排序。
有點繞啊,下面舉例說明:假如java類別下有4篇博客文章,能夠按照下圖創建文章類別和文章的數據模型。具體類別和文章的結構圖以下:
Tag:java:posts:表示文章的類別java,咱們用集合類型表示,裏面只存儲文章的ID
Post:id:表示文章,有三個字段,咱們用散列類型存儲,一個id對應一個散列
這樣模型能夠這樣創建了:
127.0.0.1:6379> hmset Post:1 title 'java study' content 'java is a good programming language' date '201509122110' OK 127.0.0.1:6379> hmset Post:22 title 'java study2' content 'java is a good programming language' date '201409121221' OK 127.0.0.1:6379> hmset Post:26 title 'java study3' content 'java is a good programming language' date '201709121221' OK 127.0.0.1:6379> hmset Post:60 title 'java study4' content 'java is a good programming language' date '201510221221' OK 127.0.0.1:6379> sadd Tag:java:posts 1 22 26 60 (integer) 4
這個時候我想對Tag:java:posts類別下的文章按照發布日期進行排序,能夠這樣來:
127.0.0.1:6379> smembers Tag:java:posts #排序以前直接取得的文章 1) "1" 2) "22" 3) "26" 4) "60" 127.0.0.1:6379> sort Tag:java:posts by Post:*->date desc #按照日期排序後的結果,這裏by後面參數是散列類型的 1) "26" 2) "60" 3) "1" 4) "22"
固然了,by後面還能夠跟上字符串類型,以下:
127.0.0.1:6379> lpush sortbylist 2 1 3 (integer) 3 127.0.0.1:6379> set item:1 50 OK 127.0.0.1:6379> set item:2 90 OK 127.0.0.1:6379> set item:3 20 OK 127.0.0.1:6379> sort sortbylist by item:* desc 1) "2" 2) "1" 3) "3"
2)get參數
上面事例中的文章排序後,若是我想獲取文章標題,須要針對每一個文章id進行hget,有沒有以爲很麻煩呢?其實sort還提供了get參數,能夠很輕鬆的得到文章的標題
get參數不影響排序,它的做用是返回get參數指定的鍵值。get參數和by參數同樣,也支持散列和字符串類型的鍵,並使用*做爲佔位符,要實現排序後直接返回文章的標題,能夠這樣作:
127.0.0.1:6379> sort Tag:java:posts by Post:*->date desc get Post:*->title 1) "java study3" 2) "java study4" 3) "java study" 4) "java study2" 127.0.0.1:6379>
在一個sort中能夠有多個get參數,可是by只能有一個,get #表示返回元素自己的值。還能夠這樣:
127.0.0.1:6379> sort Tag:java:posts by Post:*->date desc get Post:*->title get Post:*->date get #
1) "java study3"
2) "201709121221"
3) "26"
4) "java study4"
5) "201510221221"
6) "60"
7) "java study"
8) "201509122110"
9) "1"
10) "java study2"
11) "201409121221"
12) "22"
3)store參數
若是但願保存排序後的結果集,可使用store參數,默認保存後的數據類型是列表類型,若是該鍵已存在,則覆蓋它。
127.0.0.1:6379> sort Tag:java:posts by Post:*->date desc get Post:*->title get Post:*->date get # store sort.result (integer) 12 127.0.0.1:6379> lrange sort.result 0 -1 1) "java study3" 2) "201709121221" 3) "26" 4) "java study4" 5) "201510221221" 6) "60" 7) "java study" 8) "201509122110" 9) "1" 10) "java study2" 11) "201409121221" 12) "22" 127.0.0.1:6379>
store參數經常用來跟exprie命令實現排序結果的緩存功能,如上面提到的遊戲排行榜數據,實現的僞代碼以下
#判斷是否存在排序結果的緩存 $isExistsCache = Exists cache.sort if ($isExistsCache = 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命令是redis中最強大複雜的命令之一,若是用很差很容易出現性能問題,sort命令的時間複雜度爲:O(N+MlogM),其中N表示須要排序的元素個數,M表示返回的元素個數,當N數值很大時sort排序性能較低,而且redis在排序前會創建一個長度爲N的容器來存儲排序的元素,雖然是臨時的,可是若是遇到大數據量的排序則會成爲性能瓶頸。因此在開發中須要注意如下幾個方面: