redis學習(五) redis進階之事務和過期時間的應用
事務
redis中的事務是一組命令的集合。使得一個事務中的redis命令要麼全執行,要麼全不執行
使用方式: multi 和exec完成
multi: 告訴redis將同一個事務的命令儲存起來。 之後傳送兩個SADD, redis返回queued表示命令進入了等待執行的事務佇列中。
exec:則是告知redis按照發送順序執行命令。一旦客戶端傳送exec名,所有命令將執行,即使此後客戶端斷線,沒有關係。
錯誤處理
1>語法錯誤。 命令不存在或者命令引數個數不對。
只要有一個命令有語法錯誤,執行exec命令後redis就會直接返回錯誤,連正確的命令也不會執行。
2> 執行錯誤。指的是命令執行時出現錯誤。比如使用雜湊型別的命令操作集合型別的鍵。這種錯誤在實際執行前redis是無法發現的。所以在事務裡這樣的命令會被redis接受並執行。如果事務裡的一條命令出現了執行錯誤,事務的其他命令
依然會繼續執行(包括出錯命令後面的命令)
redis的事務沒有提供回滾的功能,因此開發者必須在事務執行後自己將資料庫復原到原來執行前的狀態。
這兩種錯誤,其中語法錯誤是能在開發是找出並解決的。
WATCH命令介紹
在一個事務中只有當所有命令都依次執行完才能得到每個結果的返回值,可是有些情況需要先獲得一條命令的返回值。然後在根據這個值執行下一條命令。
比如get,set 命令實現increase時候就會出現競態條件。
如果將兩個命令放到一個事務中呢, 但是由於事務中的每個命令的執行結果都是一起返回的,無法將前一條命令的結果作為下一條命令的引數。也就是在set前無法獲取到get命令的返回值。
解決思路:將get獲得鍵值後保證改鍵值不被其他客戶端修改,直到函式執行完成後才允許其他客戶端操作,這樣也可以防止競態條件。
watch命令可以監控一個鍵或者多個鍵,一旦其中一個鍵被修改(刪除),之後的事務就不會執行,監控一直持續到exec命令(事務中的命令是在exec之後才執行的,所以在multi命令後可以修改watch監控的鍵值)
watch後面的事務不執行,key的結果還是2
這樣,通過watch命令實現incr命令
對於上面函式來說,由於watch命令只是當被監控的鍵值被修改後阻止後面事務的執行,如果這次執行失敗了,exec沒有執行成功,key還在在watch的,不能保證其他客戶端不修改這個值,也就是還是可能阻止其他事務執行的,必須重新執行這個函式。
執行exec命令後會取消對所有鍵的監控,如果不想執行事務中的命令可以使用UNwatch命令來取消監控
舉個例子來說,實現hsetnx命令的功能
def hsetxx($key,$field,$value)
watch $key
$isFieldExists = HEXISTS $key,$field
if $isFieldExists is 1
MULTI
HSET $key,$field,$value
EXEC
else
UNWATCH
return $isFieldExists
如果鍵不存在,就不執行事務中的命令,並取消監控
過期時間
實際使用開發中,遇到一些時效的資料,比如限時優惠活動,快取或驗證碼等。過了一定的時間就需要刪除這些資料。在關係資料庫中需要額外的一個欄位記錄到期時間,然後定期檢測刪除過期資料。而在redis中可以使用expire命令設定一個鍵的過期時間,到時間redis自動刪除它。
命令
1. expire key seconds seconds引數表示鍵的過期時間,單位是秒
返回: 1 表示成功 0 表示 鍵不存在或者設定失敗
2. ttl命令獲取key的剩餘時間(秒),如果鍵不存在ttl命令返回的是-2,-1表示鍵永久存在(這是建立一個鍵後的預設情況)
note: 2.6版中無論鍵不存在還是沒有過期時間都返回-1,直到2.8才區分返回-2和-1
3. 取消鍵的過期時間(將鍵恢復成永久的),則可以使用persist命令,如果過期時間被成功清除則返回1,否則返回0
(因為鍵不存在或者鍵本來就是永久的)
除了persist命令外,使用set或者getset命令為鍵賦值也會同時清除鍵的過期時間,但是其他對鍵值進行操作的
命令(INCR,LPUSH,HSET,ZREM)均不會影響鍵的過期時間
4. 精確控制鍵的過期時間 pexpire命令, pexpire key ms (過期時間為毫秒)
另外還有兩個相對不常用的命令,expireat 和 pexpireat
expireat命令和expire命令的區別在於前者使用unix時間作為第二個引數表示鍵的過期時刻
pexpireat名和expireat命令的區別是前者的單位是ms
過期時間的應用
1. 實現訪問頻率的限制
需要實現每個ip每分鐘只能訪問100個頁面,給每個ip設定一個key 過期時間設定為60秒,每次訪問自增一次。
超過一百次,限制訪問
虛擬碼:
$isKeyExists = EXISTS rate.limitling:$IP
if $isKeyExists is 1
$times = INCR rate.limitling:$IP
if $times>100
print 訪問頻率超過限制,稍後再試
exist
else
INCR rate.limitling:$IP
EXPIRE $keyName,60
如果設定過期時間失敗,前面的命令執行成功,那麼ip最多訪問100次
解決:將 incr和expire命令放在一個事務裡,修改虛擬碼如下
multi
INCR rate.limiting:$IP
EXPIRE $keyname,60
exec
繼續深入:
如果一個使用者在一分鐘的第一秒訪問了一次部落格,同一分鐘的最後一秒訪問了9次,又在下一分鐘的第一秒訪問了10秒,
這樣的訪問是可以通過限制的訪問頻率限制的,但是實際上使用者在兩秒內訪問了19次部落格,這和每分鐘訪問10次的限制差距較大。
優化:
對每個使用者,使用一個列表型別的鍵來記錄他最近10次的訪問部落格時間,一旦鍵中的元素超過了10個,就判斷時間最早的元素據現在的時間是否小於1分鐘,如果是表示使用者最近一分鐘的訪問次數超過了10次,如果不是鍵現在的時間加入到列表中,將最早的刪除。
$listLength = llen rate.limiting:$IP
if $listLength<10
LPUSH rate.limiting:$IP,now()
else
$time = LINDEX rate.limiting:$IP,-1
if now() - $time <60
print 訪問頻率超過限制,稍後再試
else
LPUSH rate.limiting:$IP,now()
LTRIM rate.limiting:$IP,0,9
當要限制"A時間最多訪問B次"時,如果B較大,此方法會佔用較多的儲存空間,除此之外也會出現競態條件的問題
2. 實現快取
為了提高網站的負載能力,需要將一些訪問頻率較高的但是對CPU或IO資源消耗較大的結果快取起來。並設定這些快取過一段時間自動過期。
實際開發中利用快取作為介面的令牌,防止多次提交,每次請求過來的時候校驗是否key存在,如果存在就
拋異常多次提交,不存在就設值,並設定過期時間,介面執行完畢將key刪除(放在finally中)
String key ="Y803_" + ganNo+ "_" + rpBatch;
long flag = jedis.setnx(key, String.valueOf(System.currentTimeMillis()));
if(flag ==1)
{
jedis.expire(key, time);
}else
{
throw new GessException(MarketErrCode.YX2037, "不能重複提交");
}
redis作為快取存在的問題
當伺服器記憶體有限時候,如果大量使用快取鍵而且過期時間設定過長會導致redis佔滿記憶體;另一方面如果為了防止redis佔用記憶體過大而將快取鍵的過期時間設得太短,就可能導致快取命中率過低並且大量記憶體白白閒置。實際開發中很難為快取鍵設定合理的過期時間。
解決: 限制redis能使用的最大記憶體,並讓redis按照一定的規則淘汰不需要的快取鍵。
修改配置檔案maxmemory引數,限制redis最大可用記憶體大小,當超出限制redis根據maxmemory-policy指定策略刪除key