基於Lua腳本解決實時數據處理流程中的關鍵問題

来源:http://www.cnblogs.com/zhu-wj/archive/2017/11/03/7777762.html
-Advertisement-
Play Games

在處理實時數據的過程中需要緩存的參與,由於在更新實時數據時併發處理的特點,因此在更新實時數據時經常產生新老數據相互覆蓋的情況,針對這個情況調查了Redis事務和Lua腳本後,發現Redis事務並不能很好的滿足該場景的業務需要,必須藉助Lua腳本執行原子化的操作才能在理論上解決數據更新的準確性問題。 ...


摘要

在處理實時數據的過程中需要緩存的參與,由於在更新實時數據時併發處理的特點,因此在更新實時數據時經常產生新老數據相互覆蓋的情況,針對這個情況調查了Redis事務和Lua腳本後,發現Redis事務並不能很好的滿足該場景的業務需要,必須藉助Lua腳本執行原子化的操作才能在理論上解決數據更新的準確性問題。

實時數據處理過程中遇到的問題

在處理實時數據的過程中,經常使用Redis存取數據執行CAS(check and set)操作。一般做法是先從Redis中獲取到目標數據,然後根據數據的特征指標判斷是應該更新還是放棄更新。在實時數據流量較小時這個辦法簡單粗暴的解決了數據更新的邏輯問題,但是面對上傳頻率較高的場景或者在更新實時數據時同步更新相關數據的彙總值時就會經常面臨更新時新數據被老數據覆蓋的問題,而且問題的出現具有隨機性,無法有效解決數據的緩存準確性的要求。

此過程中的調用示意圖:

        發送命令請求,獲取時間戳                    
Caller ----------------------------------------> Redis

                            發送時間戳給客戶端
Caller <---------------------------------------- Redis

        發送更新指令
Caller ----------------------------------------> Redis

                                    返回執行結果
Caller <---------------------------------------- Redis

由上圖可見在獲取時間戳到發送更新指令之前由於不是原子操作,因此存在數據被更新的可能。在解決這個問題的時候不禁會想:“如果這個場景發生在資料庫中會怎樣?”

如果在資料庫中,可以使用語句中的where條件來限制SQL語句的執行,做到按條件執行的目的。

上述思想可以用偽代碼表達為:

UPDATE 終端狀態 set status='XXXXX' where code='XXXX' and 時間戳>timestamp

這是個典型的先讀後寫的操作,該語句在資料庫中以鎖的方式保證了處理串列化和操作的原子性。

那麼,問題是:Redis事務中能不能做到?

Redis事務

Redis的事務由四個關鍵命令構成:MULTI、EXEC、DISCARD和WATCH構成。

名稱 作用
MULTI 聲明開啟事務通道
EXEC 開始批量執行
DISCARD 放棄執行之前發生的命令
WATCH 監聽某個KEY的變化

Redis事務的基本執行方式是:

  1. 使用WATCH聲明監聽某個KEY值的變化
  2. 發送MULTI命令
  3. 發送事務中需要執行的指令
  4. 發送EXEC指令,如果監聽到數據在watch後發生變化則放棄提交

Redis事務的執行示意圖:


        發送命令請求,獲取時間戳                    
Caller ----------------------------------------> Redis

                            發送時間戳給客戶端
Caller <---------------------------------------- Redis

        發送WATCH指令                  
Caller ----------------------------------------> Redis

        發送MULTI指令                  
Caller ----------------------------------------> Redis

        發送更新指令
Caller ----------------------------------------> Redis

        發送EXEC指令
Caller ----------------------------------------> Redis

                                    返回執行結果
Caller <---------------------------------------- Redis

Redis的事務跟資料庫的事務有極大不同,其事務實際是由WATCH監聽KEY值的變化加批量執行來完成的,而且事務執行過程中無法與客戶端進行交互的。這個事務的實現方式就限制了其所能滿足的業務場景,比如本文中遇到的時間戳+實時數據的更新場景中,要求時時刻刻都將最新的數據更新到Redis中,而不是在更新時發現有其他client搶先更新了目標KEY之後就放棄當前比較新的時間戳的更新權。

那麼,如何才能滿足將最新的數據更新到Redis中這個業務需求呢?方法也是有的,那就是使用Lua腳本

Redis+Lua腳本

Redis 2.6+都集成了Lua腳本。通過內嵌對於Lua的支持,Redis解決了長久以來不能高效處理CAS的缺點。在Redis中執行Lua腳本主要涉及到兩個關鍵命令:EVALEVALSHA,另外還有個輔助的命令SCRIPT EXISTS sha1 sha2 ... shaN可以用於查詢腳本是否已經緩存。

名稱 作用
EVAL 執行某個客戶端傳入的腳本
EVALSHA 執行某個已經在Redis Server中緩存的腳本

使用Lua腳本執行CAS操作的基本步驟:

  1. 客戶端發送EVAL 腳本 參數...命令
  2. 服務端執行腳本
    • 獲取用於進行判斷的鍵值
    • 判斷是否應該更新
    • 執行/放棄更新
  3. 返回腳本執行結果

其中第2步是完全在伺服器端執行,根據Redis官網的描述:

Redis uses the same Lua interpreter to run all the commands. Also Redis guarantees that a script is executed in an atomic way: no other script or Redis command will be executed while a script is being executed.

在Redis Server中執行Lua腳本是一個原子性的操作,時間戳較舊的數據會自動放棄更新緩存數據,因此就可以保證存入緩存中的數據永遠是最新的,因此也就解決了數據併發更新時老數據被新數據覆蓋的問題。

Lua腳本內部邏輯可以用偽代碼描述為:

timestamp=Redis.call('獲取時間戳的指令')

if (timestamp==nil)
then
    Redis.call('執行更新')
elseif (時間戳>timestamp)
then 
    Redis.call('執行更新')
end

使用Lua腳本的調用示意圖


          發送腳本
Caller ----------------------------------------> Redis

          為腳本創建 Lua 函數
Redis  ----------------------------------------> Lua

          綁定超時處理鉤子
Redis  ----------------------------------------> Lua

          執行腳本函數
Redis  ----------------------------------------> Lua

          返回函數執行結果(一個 Lua 值)
Redis  <---------------------------------------- Lua

          將 Lua 值轉換為 Redis 回覆
          並將結果返回給客戶端
Caller <---------------------------------------- Redis

總結

Redis中進行原子化操作有兩個方法:Redis事務或Lua腳本。使用Redis事務只能滿足數據在未發生變化進行更新而發生變化就放棄更新的場景。對於實時數據的處理場景來說,Redis的事務無法滿足根據時間戳進行業務處理的需要。由於Redis執行Lua腳本時是原子化的並且腳本內部可以編寫讀寫判斷邏輯,因此可以藉助Lua腳本完成實時數據更新的業務需要。

雖然使用Lua腳本可以較好的滿足業務需要,但是在使用Redis腳本時也有一定的註意事項,Lua腳本中不要編寫太複雜的操作,應該以儘量簡單的邏輯完成整個操作過程,避免因為腳本的執行產生阻塞效應。

參考資料

  1. Lua 腳本
  2. EVAL script numkeys key [key ...] arg [arg ...]
  3. Redis Transactions

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 實現效果: 實現原理: 給關閉按鈕綁定點擊事件,點擊以後觸發動畫效果。利用jQuery的animate方法,先讓顯示天氣的盒子高度變為0,接著讓整個包含天氣和事件的盒子寬度變為0,最後通過將display屬性值設為none,使得close按鈕消失。 實現代碼: ...
  • 一、函數聲明、函數表達式、匿名函數1.函數聲明:function fnName () {…};使用function關鍵字聲明一個函數,再指定一個函數名,叫函數聲明。2.函數表達式 var fnName = function () {…};使用function關鍵字聲明一個函數,但未給函數命名,最後將 ...
  • 最近在搞一個被很多人改了的框架,天天看代碼看的頭的暈了,不過感覺進步還挺大的,自己做了一個後臺可配置前臺查看兩個庫不同數據範圍的東西,還挺滿意,那天拿出來分享一下,今天先說一個這幾天做的功能,就是html頁面的查找功能。 這個功能主要是實現在查找框內輸入字元,之後按後面的上一個下一個按鈕,會自動把查 ...
  • 簡介 onunload,onbeforeunload都是在刷新或關閉時調用,可以在<script>腳本中通過 window.onunload來調用。區別在於onbeforeunload在onunload之前執行,它還可 以阻止onunload的執行。 onbeforeunload 是正要去伺服器讀 ...
  • 項目中需要調用webservice介面,android SDK中並沒有直接訪問webservice介面的方法,於是我引入了ksoap-android的jar包,來實現訪問webservice介面。剛開始一切還都比較順利,成功從webservice介面獲取到了返回的數據,直接運行在手機上一切也都正常。 ...
  • 註意Vietnamese_CI_AS排序規則下的特殊字元大小敏感問題 最近,在SQL Server中遇到了Vietnamese_CI_AS排序規則的特殊字元的大小寫敏感問題,是的,你沒有看錯,這句話並沒有語病(DBA老司機懂的)。遇到這個特殊情況的時候,我也大跌眼鏡,顛覆我的一些常識,OK,閑話少說... ...
  • http://kingxss.iteye.com/blog/1741076 ...
  • 這個系列大致想跟大家分享以下篇章(我會持續更新的↖(^ω^)↗): 1、mongo 3.4分片集群系列之一:淺談分片集群 2、mongo 3.4分片集群系列之二:搭建分片集群--哈希分片 3、mongo 3.4分片集群系列之三:搭建分片集群--哈希分片 + 安全 4、mongo 3.4分片集群系列之 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 推薦一款基於.NET 8、WPF、Prism.DryIoc、MVVM設計模式、Blazor以及MySQL資料庫構建的企業級工作流系統的WPF客戶端框架-AIStudio.Wpf.AClient 6.0。 項目介紹 框架採用了 Prism 框架來實現 MVVM 模式,不僅簡化了 MVVM 的典型 ...
  • 先看一下效果吧: 我們直接通過改造一下原版的TreeView來實現上面這個效果 我們先創建一個普通的TreeView 代碼很簡單: <TreeView> <TreeViewItem Header="人事部"/> <TreeViewItem Header="技術部"> <TreeViewItem He ...
  • 1. 生成式 AI 簡介 https://imp.i384100.net/LXYmq3 2. Python 語言 https://imp.i384100.net/5gmXXo 3. 統計和 R https://youtu.be/ANMuuq502rE?si=hw9GT6JVzMhRvBbF 4. 數 ...
  • 本文為大家介紹下.NET解壓/壓縮zip文件。雖然解壓縮不是啥核心技術,但壓縮性能以及進度處理還是需要關註下,針對使用較多的zip開源組件驗證,給大家提供個技術選型參考 之前在《.NET WebSocket高併發通信阻塞問題 - 唐宋元明清2188 - 博客園 (cnblogs.com)》講過,團隊 ...
  • 之前寫過兩篇關於Roslyn源生成器生成源代碼的用例,今天使用Roslyn的代碼修複器CodeFixProvider實現一個cs文件頭部註釋的功能, 代碼修複器會同時涉及到CodeFixProvider和DiagnosticAnalyzer, 實現FileHeaderAnalyzer 首先我們知道修 ...
  • 在軟體行業,經常會聽到一句話“文不如表,表不如圖”說明瞭圖形在軟體應用中的重要性。同樣在WPF開發中,為了程式美觀或者業務需要,經常會用到各種個樣的圖形。今天以一些簡單的小例子,簡述WPF開發中幾何圖形(Geometry)相關內容,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 在 C# 中使用 RabbitMQ 通過簡訊發送重置後的密碼到用戶的手機號上,你可以按照以下步驟進行 1.安裝 RabbitMQ 客戶端庫 首先,確保你已經安裝了 RabbitMQ 客戶端庫。你可以通過 NuGet 包管理器來安裝: dotnet add package RabbitMQ.Clien ...
  • 1.下載 Protocol Buffers 編譯器(protoc) 前往 Protocol Buffers GitHub Releases 頁面。在 "Assets" 下找到適合您系統的壓縮文件,通常為 protoc-{version}-win32.zip 或 protoc-{version}-wi ...
  • 簡介 在現代微服務架構中,服務發現(Service Discovery)是一項關鍵功能。它允許微服務動態地找到彼此,而無需依賴硬編碼的地址。以前如果你搜 .NET Service Discovery,大概率會搜到一大堆 Eureka,Consul 等的文章。現在微軟為我們帶來了一個官方的包:Micr ...
  • ZY樹洞 前言 ZY樹洞是一個基於.NET Core開發的簡單的評論系統,主要用於大家分享自己心中的感悟、經驗、心得、想法等。 好了,不賣關子了,這個項目其實是上班無聊的時候寫的,為什麼要寫這個項目呢?因為我單純的想吐槽一下工作中的不滿而已。 項目介紹 項目很簡單,主要功能就是提供一個簡單的評論系統 ...