Redis 定長隊列的探索和實踐

来源:https://www.cnblogs.com/vivotech/archive/2022/08/08/16549954.html
-Advertisement-
Play Games

vivo 互聯網伺服器團隊 - Wang Zhi 一、業務背景 從技術的角度來說,技術方案的選型都是受限於實際的業務場景,都以解決實際業務場景為目標。 在我們的實際業務場景中,需要以游戲的維度收集和上報行為數據,考慮數據的量級,執行盡最大努力交付且允許數據的部分丟棄。 數據上報支持游戲的維度的批量上 ...


vivo 互聯網伺服器團隊 - Wang Zhi

一、業務背景

從技術的角度來說,技術方案的選型都是受限於實際的業務場景,都以解決實際業務場景為目標。

在我們的實際業務場景中,需要以游戲的維度收集和上報行為數據,考慮數據的量級,執行盡最大努力交付且允許數據的部分丟棄。

數據上報支持游戲的維度的批量上報,支持同一款游戲128個行為進行批量上報。

數據上報需要時效控制,上報的數據必須是上報時刻的前3分鐘的數據。

整體數據的業務形態如下圖所示:

圖片

二、技術選型

從業務的角度來說包含數據的收集和數據的上報,我們把數據的收集比作生產者,數據的上報比作消費者,是一個典型的生產消費模型。

生產消費模型在JVM進程內部通過隊列+鎖或者無鎖的Disruptor來實現,在跨進程場景下通過MQ(RocketMQ/kafka)進行處理解耦。

但是細化到具體業務場景來看,消息的消費有諸多限制,包括:游戲維度的批量行為上報,行為上報的時效限制,細化到各個技術方案選型進行對比。

方案一

使用RocketMQ 或者Kafaka等消息隊列來存儲上報的消息,但是消費側需要考慮在業務進程中按照游戲維度進行聚合,其中技術細節涉及按照游戲維度進行拆分,在滿足消息時效性和批量性的前提下觸發上報。在這種方案下消息中間件扮演的角色本質上消息的中轉站,沒有解決任何業務場景中提及的游戲維度拆分、批量性和時效性。

方案二

在方案一的基礎上,尋求一種技術方案來解決游戲維度的消息分組、批量消費 、時效性。通過Redis的list結構來實現隊列(進一步要求實現定長隊列)來解決游戲維度的消息分組;通過Redis的list支持的Lrange來實現批量消費;通過業務側的多線程來解決時效問題,針對高頻的游戲使用單獨的線程池進行處理,上述兩個手段能夠保證消費速度大於生產速度。

方案對比

對比兩種方案後決定使用Redis的實現了一個偽消息中間件:

  1. 通過List對象實現定長隊列來保存游戲維度的行為消息(以游戲作為key的List對象來保存用戶行為);
  2. 通過List來保存所有的存在行為數據的游戲列表;
  3. 通過Set來進行去重判斷來保證2中的List對象的唯一性。

整體的技術方案如下圖所示:

圖片

生產過程

步驟一:游戲維度的某行為數據PUSH到游戲維度的隊列當中。

步驟二:判斷游戲是否在游戲的集合Set中,如果在就直接返回,如果不在進行步驟三。

步驟三:往游戲列表中PUSH游戲。

消費過程

步驟一:從游戲對象的列表中迴圈取出一款游戲。

步驟二:通過步驟一獲取的游戲對象去該游戲對象的行為數據隊列中批量獲取數據處理。

三、技術原理

在Redis的支持命令中,在List和Set的基礎命令,結合Lua腳本來實現整個技術方案。

消息數據層面,通過單獨的List迴圈維護待消費的游戲維度的數據,每個游戲維度使用定長的List來保存消息。

消息生產過程中,通過結合List的llen+lpop+rpush來實現游戲維度的定長隊列,保證隊列的長度可控。

消息消費過程中,通過結合List的lrange+ltrim來實現游戲維度的消息的批量消費。

在整個執行的複雜度層面,需要保證時間複雜度在0(N)常量維度,保證時間可控。

3.1 Lua 腳本

EVAL script numkeys key [key ...] arg [arg ...]
    時間複雜度:取決於腳本本身的執行的時間複雜度。
 
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
 
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.
This semantic is similar to the one of MULTI / EXEC.
From the point of view of all the other clients the effects of a script are either still not visible or already completed.

Redis採用相同的Lua解釋器去運行所有命令,我們可以保證,腳本的執行是原子性的。作用就類似於加了MULTI/EXEC。

  • Lua 腳本內多個命令以原子性的方式執行,保證了命令執行的線程安全。

  • Lua 腳本結合List命令實現定長隊列,實現批量消費。

  • Lua 腳本僅支持單個key的操作,不支持多key的操作。

3.2 List 對象

LLEN key
    計算List的長度
    時間複雜度:O(1)。
 
LPOP key [count]
    從List的左側移除元素
    時間複雜度:O(N),N為移除元素的個數。
 
RPUSH key element [element ...]
    從List的右側保存元素
    時間複雜度:O(N),N為保存元素的個數。
  • List的基礎命令包括計算List的長度,移除數據,添加數據,整體命令的複雜度都在O(N)的常量時間。
  • 整合上述三個命令,我們能保證實現固定長度的隊列,通過判斷隊列長度是否達到定長結合新增隊列元素和移除隊列元素來完成。
LRANGE key start end
    時間複雜度:O(S+N), S為偏移量start, N為指定區間內元素的數量。
  
    下標(index)參數 start 和 stop 都以 0 為底,也就是說,以 0 表示列表的第一個元素,以 1 表示列表的第二個元素,以此類推。
    你也可以使用負數下標,以 -1 表示列表的最後一個元素, -2 表示列表的倒數第二個元素,以此類推。
 
LTRIM key start stop
    時間複雜度:O(N) where N is the number of elements to be removed by the operation.
 
    修剪(trim)一個已存在的 list,這樣 list 就會只包含指定範圍的指定元素。

  • List的基礎命令包括批量返回數據和裁剪數據,整體命令的複雜度都在O(N)的常量時間。
  • 整合上述兩個命令,我們能夠批量消費數據並移除隊列數據,通過LRANGE批量返回數據並通過LTRIM保留剩餘數據。

3.3 Set 對象

SADD key member [member ...]
    往Set集合添加數據。
    時間複雜度:O(1)。
     
SISMEMBER key member
    判斷Set集合是否存在元素。
    時間複雜度:O(1)。

四、技術應用

4.1 生產消息

定義LUA腳本   
    CACHE_NPPA_EVENT_LUA =
        "local retVal = 0 " +
        "local key = KEYS[1] " +
        "local num = tonumber(ARGV[1]) " +
        "local val = ARGV[2] " +
        "local expire = tonumber(ARGV[3]) " +
        "if (redis.call('llen', key) < num) then redis.call('rpush', key, val) " +
        "else redis.call('lpop', key) redis.call('rpush', key, val) retVal = 1 end " +
        "redis.call('expire', key, expire) return retVal";
 
 
執行LUA腳本
    String data = JSON.toJSONString(nppaBehavior);
    Long retVal = (Long)jedisClusterTemplate.eval(CACHE_NPPA_EVENT_LUA, 1, NPPA_PREFIX + nppaBehavior.getGamePackage(), String.valueOf(MAX_GAME_EVENT_PER_GAME), data, String.valueOf(NPPA_TTL_MINUTE * 60));
 
執行效果
    實現固長隊列的數據存儲並設置過期時間
  • 通過整合llen+rpush+lpop三個命令實現定長隊列。
  • 通過lua腳本保證上述命令的原子性執行。

圖片

  • 整體的執行流程如上圖所示,核心理念通過lua腳本的原子性保證了隊列長度計算(llen)、隊列數據移除(lpop)、隊列數據保存(rpush)的原子性執行。

4.2 消費消息

定義LUA腳本
    QUERY_NPPA_EVENT_LUA =
        "local data = {} " +
        "local key = KEYS[1] " +
        "local num = tonumber(ARGV[1]) " +
        "data = redis.call('lrange', key, 0, num) redis.call('ltrim', key, num+1, -1) return data";
 
執行LUA腳本
    Integer batchSize = NppaConfigUtils.getInteger("nppa.report.batch.size", 1);
    Object result = jedisClusterTemplate.eval(QUERY_NPPA_EVENT_LUA, 1,NPPA_PREFIX + gamePackage, String.valueOf(batchSize));
 
執行效果
    取固定數量的對象,然後保留隊列的剩餘的消息對象。
  • 通過整合lrange+ltrim兩個命令實現消息的批量消費。
  • 通過lua腳本保證上述命令的原子性執行。

圖片

  • 整體的執行流程如上圖所示,核心理念通過lua腳本的原子性保證了數據獲取(Lrange)和數據裁剪(Ltrim)的原子性執行。
  • 整體的消費流程選擇pull模式,通過多線程迴圈輪詢可消費的隊列進行消費。與藉助於redis的pub/sub的通知機制實現消費流程的push模式相比,pull模式成本更低效果更佳。

4.3 註意事項

  • Redis集群模式下,執行Lua腳本建議傳單key,多key會報重定向錯誤。
  • 在不同的Redis版本下,Lua腳本針對null的返回值處理不同,參考官方文檔。
  • 消費者的消費過程中通過迴圈遍歷游戲列表,然後根據游戲去獲取對應的消息對象,但是不同的游戲對應的熱度不同,所以在消費端我們通過配置的方式為熱門游戲單獨開啟消費線程進行消費,相當於針對不同游戲配置不同優先順序的消費者。

五、線上效果

圖片

圖片

圖片

  • 生產和消費的QPS約為1w qps左右,整體上報QPS通過批量上報後會遠低於生產的消息生產和消費的QPS。
  • 整體數據的使用游戲包名作為key進行存儲,性能上不存在熱點的問題。

六、適用場景

在描述完方案的原理和實現細節之後,進一步對適用的業務場景進行下總結。整體方案是基於redis的基本數據結構構建一個偽消息隊列,用以解決消息的單個生產批量消費的場景,通過多key形式實現消息隊列的多Topic模式,重要的是能夠藉助於redis的原生能力在O(N)的時間複雜度完成批量消費。另外該方案也可以降級作為實現先進先出定長的日誌隊列。

七、總結

本文主要探索在特定業務場景下通過Redis的原生命令實現類MQ的功能,創新式的通過Lua腳本組合Redis的List的基礎命令,實現了消息的分組,消息的定長隊列,消息的批量消費功能;整體解決方案線上上環境落地並平穩運行,為特定場景提供了一種通用的解決方案。

分享 vivo 互聯網技術乾貨與沙龍活動,推薦最新行業動態與熱門會議。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在docker中拉取centos8鏡像,創建一個為centos8的容器,併在容器中編譯安裝apache,然後使用編製安裝完成的容器創建一個鏡像。測試是否能通過此鏡像部署一個web站點併成功訪問 ...
  • 1. TriCore與中斷的簡介 TriCore是德國英飛凌科技公司旗下的第一個為實時嵌入式系統而優化的統一的、32位的微控制器-DSP(Digital Signal Processing)處理器架構。TriCore的ISA(Instruction Set Architecture),即指令集體繫結 ...
  • 博德之門:黑暗聯盟2是一款原始畫風奇幻探索動作冒險游戲,商隊遇難,僅剩的守衛也已受傷,邪惡力量再次回到了傳說中的博德之門,玩家需要狡猾的戰士和大膽的法師從威脅所有人的黑魔法中奪回博德之門,快拿起你的武器參加戰鬥吧! 詳情:博德之門:黑暗聯盟2 for mac(迷宮探索游戲) 簡單介紹 在這個地牢爬行 ...
  • PreviewMarkdown Mac版是一款功能強大的Markdown編輯預覽軟體,為使用流行的 Markdown 標記格式創建的文檔提供 QuickLook 文件預覽和文件圖標縮略圖。 詳情:PreviewMarkdown for Mac(Markdown編輯預覽軟體) 簡單介紹 Preview ...
  • 定義: 刪除數據表就是將資料庫中已經存在的表從資料庫中刪除。註意,在刪除表的同時,表的定義和表中所有的數據均會被刪除。因此,在進行刪除操作前,最好對錶中的數據做一個備份,以免造成無法輓回的後果。本節將詳細講解資料庫表的刪除方法。 1 刪除一個或多個沒有被其他表關聯的數據表 如果一個數據表沒有和其它表 ...
  • Orange是提高工作便利性的 集成開發工具 可以極大方便資料庫管理和 用戶的數據訪問進程 Orange是 DB運營管理和開發解決方案 通過方便而又強大的功能 快速可視化編程前端 以便瀏覽數據分析和可視化 開發商介紹 Ware Valley成立於2001年,是一家全球性的軟體公司,也是南韓唯一一個為 ...
  • 更多技術交流、求職機會、試用福利,歡迎關註位元組跳動數據平臺微信公眾號,回覆【1】進入官方交流群 序言 埋點數據作為推薦、搜索、產品優化的基石,其數據質量的重要性不言而喻,而要保障埋點數據的質量,埋點驗證則首當其衝。工欲善其事必先利其器,要做好埋點驗證會面臨很多技術挑戰:易用性、準確性、實時性、穩定性 ...
  • 一、數字營銷是數字化轉型排頭兵 《⼗四五數字經濟發展規劃》中強調,要⼤⼒推進數字化轉型,形成數據驅動的智能決策能⼒,提升企業整體運營效率。 要做好數字化轉型,企業可從產、研、供、銷、⽤等多個環節入手,而 “銷” 恰好是第一關鍵要素,企業轉型往往從營銷場景入手,因此我們說數字化營銷是企業數字化轉型的排 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...