在處理實時資料的過程中需要緩存的參與,由于在更新實時資料時并發處理的特點,是以在更新實時資料時經常産生新老資料互相覆寫的情況,針對這個情況調查了Redis事務和Lua腳本後,發現Redis事務并不能很好的滿足該場景的業務需要,必須借助Lua腳本執行原子化的操作才能在理論上解決資料更新的準确性問題。

在處理實時資料的過程中,經常使用Redis存取資料執行CAS(check and set)操作。一般做法是先從Redis中擷取到目标資料,然後根據資料的特征名額判斷是應該更新還是放棄更新。在實時資料流量較小時這個辦法簡單粗暴的解決了資料更新的邏輯問題,但是面對上傳頻率較高的場景或者在更新實時資料時同步更新相關資料的彙總值時就會經常面臨更新時新資料被老資料覆寫的問題,而且問題的出現具有随機性,無法有效解決資料的緩存準确性的要求。
此過程中的調用示意圖:
由上圖可見在擷取時間戳到發送更新指令之前由于不是原子操作,是以存在資料被更新的可能。在解決這個問題的時候不禁會想:“如果這個場景發生在資料庫中會怎樣?”
如果在資料庫中,可以使用語句中的where條件來限制SQL語句的執行,做到按條件執行的目的。
上述思想可以用僞代碼表達為:
這是個典型的先讀後寫的操作,該語句在資料庫中以鎖的方式保證了處理串行化和操作的原子性。
那麼,問題是:Redis事務中能不能做到?
Redis的事務由四個關鍵指令構成:MULTI、EXEC、DISCARD和WATCH構成。
名稱
作用
MULTI
聲明開啟事務通道
EXEC
開始批量執行
DISCARD
放棄執行之前發生的指令
WATCH
監聽某個KEY的變化
Redis事務的基本執行方式是:
使用<code>WATCH</code>聲明監聽某個KEY值的變化
發送<code>MULTI</code>指令
發送事務中需要執行的指令
發送<code>EXEC指令</code>,如果監聽到資料在watch後發生變化則放棄送出
Redis事務的執行示意圖:
Redis的事務跟資料庫的事務有極大不同,其事務實際是由WATCH監聽KEY值的變化加批量執行來完成的,而且事務執行過程中無法與用戶端進行互動的。這個事務的實作方式就限制了其所能滿足的業務場景,比如本文中遇到的時間戳+實時資料的更新場景中,要求時時刻刻都将最新的資料更新到Redis中,而不是在更新時發現有其他client搶先更新了目标KEY之後就放棄目前比較新的時間戳的更新權。
那麼,如何才能滿足将最新的資料更新到Redis中這個業務需求呢?方法也是有的,那就是使用Lua腳本
Redis 2.6+都內建了Lua腳本。通過内嵌對于Lua的支援,Redis解決了長久以來不能高效處理CAS的缺點。在Redis中執行Lua腳本主要涉及到兩個關鍵指令:<code>EVAL</code>和<code>EVALSHA</code>,另外還有個輔助的指令<code>SCRIPT EXISTS sha1 sha2 ... shaN</code>可以用于查詢腳本是否已經緩存。
EVAL
執行某個用戶端傳入的腳本
EVALSHA
執行某個已經在Redis Server中緩存的腳本
使用Lua腳本執行CAS操作的基本步驟:
用戶端發送<code>EVAL 腳本 參數...</code>指令
服務端執行腳本
擷取用于進行判斷的鍵值
判斷是否應該更新
執行/放棄更新
傳回腳本執行結果
其中第2步是完全在伺服器端執行,根據Redis官網的描述:
在Redis Server中執行Lua腳本是一個原子性的操作,時間戳較舊的資料會自動放棄更新緩存資料,是以就可以保證存入緩存中的資料永遠是最新的,是以也就解決了資料并發更新時老資料被新資料覆寫的問題。
Lua腳本内部邏輯可以用僞代碼描述為:
使用Lua腳本的調用示意圖:
Redis中進行原子化操作有兩個方法:Redis事務或Lua腳本。使用Redis事務隻能滿足資料在未發生變化進行更新而發生變化就放棄更新的場景。對于實時資料的處理場景來說,Redis的事務無法滿足根據時間戳進行業務處理的需要。由于Redis執行Lua腳本時是原子化的并且腳本内部可以編寫讀寫判斷邏輯,是以可以借助Lua腳本完成實時資料更新的業務需要。
雖然使用Lua腳本可以較好的滿足業務需要,但是在使用Redis腳本時也有一定的注意事項,Lua腳本中不要編寫太複雜的操作,應該以盡量簡單的邏輯完成整個操作過程,避免因為腳本的執行産生阻塞效應。
本文連結:http://www.cnblogs.com/zhu-wj/p/7777762.html
Lua 腳本
EVAL script numkeys key [key ...] arg [arg ...]
Redis Transactions