Redis小實戰分享

来源:http://www.cnblogs.com/moary/archive/2016/04/14/5393288.html
-Advertisement-
Play Games

看了很久博客園的博客,今天有點小衝動,寫一個小小分享,歡迎吐槽,O(∩_∩)O哈哈~ 背景: 微信活動小游戲開發,游戲中需要不斷的恢復體力值,體力相關數據都存儲在redis中。 需求: 1.當日首次登錄,增加全部體力值; 2.周期性增加定額體力值; 實現方案1(全量更新): 出於用戶量小、簡單的考慮 ...


看了很久博客園的博客,今天有點小衝動,寫一個小小分享,歡迎吐槽,O(∩_∩)O哈哈~

  • 背景:

微信活動小游戲開發,游戲中需要不斷的恢復體力值,體力相關數據都存儲在redis中。

  • 需求:

1.當日首次登錄,增加全部體力值;

2.周期性增加定額體力值;

  • 實現方案1(全量更新):

出於用戶量小、簡單的考慮,對整個系統用戶執行全量更新。

相關表:

 

 

體力

 

表名strength,首碼strength:uid:*,體力表,哈希存儲

列名

備註

surplus

當前體力餘量

 

total

體力總量

 

 

定時任務每5分鐘(可配置,用戶量大時間太短會導致任務疊加)掃描strgtimeline,當前時間和score(上一次恢復體力時間)對比,超過則更新時間,並且更新strength表。

 

表名strgupdate,字元串存儲,體力更新表

列名

備註

value

定時任務執行前,該值設置為1

定時任務執行完成,該值設置為0

定時任務啟動時,並檢查該值為0時,再執行恢復體力操作,防止任務疊加,否則結束本次定時任務

 

以下是一個體力值增加方法:

 1 /**
 2      * 添加體力值,返回true:標識添加成功
 3      * @param uid 用戶ID
 4      * @param nums 增加數量
 5      * @return
 6      */
 7     @Override
 8     public boolean incrStrength(Long uid, int nums) {
 9         boolean ret = false;
10         Jedis jedis = null;
11         // 用戶體力表
12         String strgKey = "strength:uid:" + uid;
13         try {
14             jedis = YlcqUtil.getRedis().getReadPool().getResource();
15             jedis.watch(strgKey);
16             Transaction trans = jedis.multi();
17             
18             //獲取當前體力
19             List<String> strgInfo = jedis.hmget(strgKey, "surplus", "total");
20             long surplus = Long.parseLong(strgInfo.get(0));
21             long total = Long.parseLong(strgInfo.get(1));
22             if(surplus == total){
23                 logger.info("體力值不能在增加!" + strgKey);
24             }else{
25                 surplus = surplus + nums;
26                 if(surplus > total){
27                     surplus = total;
28                 }
29                 //更新體力
30                 jedis.hset(strgKey, "surplus", surplus + "");
31                 List<Object> result = trans.exec();
32                 
33                 if (result == null || result.isEmpty()) {
34                     logger.debug("增加體力值失敗!增加前:" + strgInfo + ";增加:" + nums);
35                 } else {
36                     ret = true;
37                 }
38             }
39         } catch (Exception e) {
40             e.printStackTrace();
41         } finally {
42             YlcqUtil.getRedis().getReadPool().returnResource(jedis);
43         }
44         return ret;
45     }

定時任務業務代碼:

  1 /**
  2      * 周期性恢復-執行任務
  3      */
  4     public void cycleRecovery() {
  5         logger.info("開始執行周期恢復體力任務!");
  6         String updateKey = "strgupdate";
  7         // 更新標識
  8         String strgupdate = this.redisService.get(updateKey);
  9         if (null != strgupdate && !TASK_STATUS_FINISHED.equals(strgupdate)) {
 10             logger.info("恢復體力任務正在執行,本次任務終止(當前是周期性恢復任務)!");
 11             return;
 12         }
 13         try {
 14             this.redisService.set(updateKey, TASK_STATUS_RUNNING);
 15             doCycleTask();
 16         } catch (Exception e) {
 17             e.printStackTrace();
 18             logger.info("周期恢復體力任務執行異常!" + e.getMessage());
 19         } finally {
 20             this.redisService.set(updateKey, TASK_STATUS_FINISHED);
 21         }
 22         logger.info("周期恢復體力任務執行完成!");
 23     }
 24 
 25     /**
 26      * 周期性恢復-首次登錄
 27      */
 28     public void firstLoginRecovery() {
 29         logger.info("開始執行首次登錄恢復體力任務!");
 30         String updateKey = "strgupdate";
 31         // 更新標識
 32         String strgupdate = this.redisService.get(updateKey);
 33         if (null != strgupdate && !TASK_STATUS_FINISHED.equals(strgupdate)) {
 34             logger.info("恢復體力任務正在執行,本次任務終止(當前是首次登錄恢復任務)!");
 35             return;
 36         }
 37         try {
 38             this.redisService.set(updateKey, TASK_STATUS_RUNNING);
 39             doFirstLoginTask();
 40         } catch (Exception e) {
 41             e.printStackTrace();
 42             logger.info("首次登錄恢復體力任務執行異常!" + e.getMessage());
 43         } finally {
 44             this.redisService.set(updateKey, TASK_STATUS_FINISHED);
 45         }
 46         logger.info("首次登錄恢復體力任務執行完成!");
 47     }
 48 
 49     /**
 50      * 當天首次登錄恢復體力
 51      */
 52     private void doFirstLoginTask() {
 53         String strgkey = "strength:uid:*";
 54         Set<String> allstrgs = this.redisService.hkeys(strgkey);
 55         if (null == allstrgs || 0 == allstrgs.size()) {
 56             logger.info("沒有需要恢復體力的用戶(首次登錄任務)!");
 57             return;
 58         }
 59         Iterator<String> it = allstrgs.iterator();
 60         while (it.hasNext()) {
 61             String strength = it.next();
 62             List<String> strgInfo = this.redisService.hmget(strength, "surplus", "total");
 63             if (strgInfo.get(0).equals(strgInfo.get(1))) {
 64                 logger.info("體力值不需要恢復(首次登錄任務)!" + strength);
 65                 continue;
 66             }
 67             // 更新體力
 68             Long setRlt = this.redisService.hset(strength, "surplus", strgInfo.get(1));
 69             if (null == setRlt || 0 == setRlt) {
 70                 logger.info("更新用戶體力表失敗(首次登錄任務)!" + strength);
 71             }
 72         }
 73     }
 74 
 75     /**
 76      * 周期性恢復體力
 77      */
 78     private void doCycleTask() {
 79         String strgkey = "strength:uid:*";
 80         Set<String> allstrgs = this.redisService.hkeys(strgkey);
 81         if (null == allstrgs || 0 == allstrgs.size()) {
 82             logger.info("沒有需要恢復體力的用戶(周期恢復任務)!");
 83             return;
 84         }
 85         Iterator<String> it = allstrgs.iterator();
 86         while (it.hasNext()) {
 87             String strength = it.next();
 88             List<String> strgInfo = this.redisService.hmget(strength, "surplus", "total");
 89             if (strgInfo.get(0).equals(strgInfo.get(1))) {
 90                 logger.info("體力值不需要恢復(周期恢復任務)!" + strength);
 91                 continue;
 92             }
 93             long surplus = Long.parseLong(strgInfo.get(0));
 94             long total = Long.parseLong(strgInfo.get(1));
 95             surplus = surplus + ActivityCommonService.STRENGTH_PLUS;
 96             if (surplus > total) {
 97                 surplus = total;
 98             }
 99             // 更新體力
100             Long setRlt = this.redisService.hset(strength, "surplus", surplus + "");
101             if (null == setRlt || 0 == setRlt) {
102                 logger.info("更新用戶體力表失敗(周期恢復任務)!" + strength);
103             }
104         }
105     }
  • 弊端分析:

每次任務都會把不需要恢復體力的用戶也都恢復一遍,而且這個還是周期性的任務,所以需要優化。

  • 實現方案2(按需更新):

增加一個表,記錄更新時間。

 

用戶體力恢復的時候,同步維護一個體力時間有序集合表。

表名strgtimeline,有序集合存儲,體力時間表

列名

備註

value

uid用戶ID

 

score

用戶恢復體力的時間戳

只是在定時任務恢復體力時才更新,其他方式增加體力值時不更新

 

用戶首次登錄初始化

jedis.zadd("strgtimeline", nowSecs, uid);

 

 

優化後代碼如下:

 1 public boolean incrStrength(Long uid, int nums) {
 2         boolean ret = false;
 3         Jedis jedis = null;
 4         // 用戶體力表
 5         String strgKey = "strength:uid:" + uid;
 6         //體力時間表
 7         String strgtimeline = "strgtimeline";
 8         long nowSecs = TimeUtil.toSecs(System.currentTimeMillis());
 9         try {
10             jedis = YlcqUtil.getRedis().getReadPool().getResource();
11             jedis.watch(strgKey);
12             Transaction trans = jedis.multi();
13             
14             //獲取當前體力
15             List<String> strgInfo = jedis.hmget(strgKey, "surplus", "total");
16             long surplus = Long.parseLong(strgInfo.get(0));
17             long total = Long.parseLong(strgInfo.get(1));
18             if(surplus == total){
19                 logger.info("體力值不能在增加!" + strgKey);
20             }else{
21                 surplus = surplus + nums;
22                 if(surplus > total){
23                     surplus = total;
24                 }
25                 //更新體力
26                 jedis.hset(strgKey, "surplus", surplus + "");
27                 
28                 //插入體力時間
29                 jedis.zadd(strgtimeline, nowSecs, uid.toString());    
30                 List<Object> result = trans.exec();
31                 
32                 if (result == null || result.isEmpty()) {
33                     logger.debug("增加體力值失敗!增加前:" + strgInfo + ";增加:" + nums);
34                 } else {
35                     ret = true;
36                 }
37             }
38         } catch (Exception e) {
39             e.printStackTrace();
40         } finally {
41             YlcqUtil.getRedis().getReadPool().returnResource(jedis);
42         }
43         return ret;
44     }

在體力值變更時,記錄一條時間戳值,作為下次更新的時重要依據。

定時任務也做簡單調整,使用redis的

zremrangeByScore方法,確保每次只更新最近兩個周期內的數據,也就是熱數據。

  1 /**
  2      * 周期性恢復-執行任務
  3      */
  4     public void cycleRecovery() {
  5         logger.info("開始執行周期恢復體力任務!");
  6         //更新標識
  7         String strgupdate = this.redisService.get("strgupdate");
  8         if(null != strgupdate && !TASK_STATUS_FINISHED.equals(strgupdate)){
  9             logger.info("恢復體力任務正在執行,本次任務終止(當前是周期性恢復任務)!");
 10             return;
 11         }
 12         try {
 13             this.redisService.set("strgupdate", TASK_STATUS_RUNNING);
 14             doCycleTask();
 15         } catch (Exception e) {
 16             e.printStackTrace();
 17             logger.info("周期恢復體力任務執行異常!" + e.getMessage());
 18         } finally {
 19             this.redisService.set("strgupdate", TASK_STATUS_FINISHED);
 20         }
 21         logger.info("周期恢復體力任務執行完成!");
 22     }
 23     
 24     /**
 25      * 周期性恢復-首次登錄
 26      */
 27     public void firstLoginRecovery() {
 28         logger.info("開始執行首次登錄恢復體力任務!");
 29         //更新標識
 30         String strgupdate = this.redisService.get("strgupdate");
 31         if(null != strgupdate && !TASK_STATUS_FINISHED.equals(strgupdate)){
 32             logger.info("恢復體力任務正在執行,本次任務終止(當前是首次登錄恢復任務)!");
 33             return;
 34         }
 35         try {
 36             this.redisService.set("strgupdate", TASK_STATUS_RUNNING);
 37             doFirstLoginTask();
 38         } catch (Exception e) {
 39             e.printStackTrace();
 40             logger.info("首次登錄恢復體力任務執行異常!" + e.getMessage());
 41         } finally {
 42             this.redisService.set("strgupdate", TASK_STATUS_FINISHED);
 43         }
 44         logger.info("首次登錄恢復體力任務執行完成!");
 45     }
 46     
 47     /**
 48      * 首次登錄恢復體力
 49      */
 50     private void doFirstLoginTask() {
 51         String strgkey = "strength:uid:*";
 52         Set<String> allstrgs = this.redisService.hkeys(strgkey);
 53         if(null == allstrgs || 0 == allstrgs.size()){
 54             logger.info("沒有需要恢復體力的用戶(首次登錄任務)!");
 55             return;
 56         }
 57         String strgtimeline = "strgtimeline";
 58         long nowSecs = TimeUtil.toSecs(System.currentTimeMillis());
 59         Iterator<String> it = allstrgs.iterator();
 60         while(it.hasNext()){
 61             String strength = it.next();
 62             List<String> strgInfo = this.redisService.hmget(strength, "surplus", "total");
 63             if(strgInfo.get(0).equals(strgInfo.get(1))){
 64                 logger.info("體力值不需要恢復(首次登錄任務)!" + strength);
 65                 continue;
 66             }
 67             //更新體力
 68             Long setRlt = this.redisService.hset(strength, "surplus", strgInfo.get(1));
 69             if(null == setRlt || 0 == setRlt){
 70                 logger.info("更新用戶體力表失敗(首次登錄任務)!" + strength);
 71             }else{
 72                 //插入體力時間
 73                 String uid = strength.split(":")[2];
 74                 this.redisService.zadd(strgtimeline, nowSecs, uid);    
 75             }
 76         }
 77     }
 78     
 79     /**
 80      * 周期性恢復體力
 81      */
 82     private void doCycleTask() {
 83         //體力時間表
 84         String strgtimeline = "strgtimeline";
 85 
 86         //刪除早於兩倍體力恢復周期之前是的數據,防止多次無用的恢復
 87         long nowSecs = TimeUtil.toSecs(System.currentTimeMillis());
 88         long minSecs = nowSecs - ActivityCommonService.STRENGTH_PLUS_CYCLE_SECS * 2;
 89         this.redisService.zremrangeByScore(strgtimeline, 0, minSecs - 1);
 90         
 91         //獲取體力時間表
 92         Set<Tuple> strgupdates = this.redisService.zrangeByScoreWithScores(strgtimeline, minSecs, nowSecs);
 93         if(null == strgupdates || 0 == strgupdates.size()){
 94             logger.info("沒有需要恢復體力的用戶(周期恢復任務)!");
 95             return;
 96         }
 97         Iterator<Tuple> it = strgupdates.iterator();
 98         while(it.hasNext()){
 99             Tuple tuple = it.next();
100             double lastRecoveryTime = tuple.getScore();
101             if(nowSecs - lastRecoveryTime >= ActivityCommonService.STRENGTH_PLUS_CYCLE_SECS){
102                 String uid = tuple.getElement();
103                 String strength = "trength:uid:" + uid;
104                 List<String> strgInfo = this.redisService.hmget(strength, "surplus", "total");
105                 long surplus = Long.parseLong(strgInfo.get(0));
106                 long total = Long.parseLong(strgInfo.get(1));
107                 if(surplus == total){
108                     logger.info("體力值不需要恢復(周期恢復任務)!" + strength);
109                     continue;
110                 }
111                 surplus = surplus + ActivityCommonService.STRENGTH_PLUS;
112                 if(surplus > total){
113                     surplus = total;
114                 }
115                 //更新體力
116                 Long setRlt = this.redisService.hset(strength, "surplus", surplus + "");
117                 if(null == setRlt || 0 == setRlt){
118                     logger.info("更新用戶體力表失敗(周期恢復任務)!" + strength);
119                 }else{
120                     //插入體力時間
121                     this.redisService.zadd(strgtimeline, nowSecs, uid);                    
122                 }
123             }
124         }
125     }

O(∩_∩)O~。

也是初試redis,感覺還可以,類似這種業務處理的都可以這麼做,提升效率。

 

 


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

-Advertisement-
Play Games
更多相關文章
  • 癥狀:前一天在MySQL中刪除了幾個不用的資料庫後登陸MySQL出現以下錯誤: mysql -u root -p passwd ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysql ...
  • 轉載: http://www.cnblogs.com/stephen-liu74/archive/2012/03/09/2328757.html http://www.runoob.com/sqlite/sqlite-tutorial.html ...
  • 資料庫系統的 三級模式結構 資料庫系統是由外模式,模式和內模式三級構成 1.模式(schema) 模式也稱邏輯模式,是資料庫中全體數據的邏輯結構和特征的描述,是所有用戶的公共數據視圖。 2.外模式(external schema) 外模式也稱子模式(subschema)或用戶模式,它是資料庫用戶(包 ...
  • SQLite的SQL語法 SQLite的SQL語法 SQLite庫可以解析大部分標準SQL語言。但它也省去了一些特性並且加入了一些自己的新特性。這篇文檔就是試圖描述那些SQLite支持/不支持的SQL語法的。查看關鍵字列表。 如下語法表格中,純文本用藍色粗體顯示。非終極符號為斜體紅色。作為語法一部分 ...
  • 官網上面的例子是在phoenix-4.6.0-HBase-1.1-client.jar完成的,這個jar包含了phoenix4.6連接hbase1.1.2所有的依賴,真是包羅萬象(裡面竟然還包括了servlet)。如下圖: 如果把這個jar放入到工程中,必然與現有工程諸多jar產生衝突。 經查找資料... ...
  • 寫在前面的話:之前做的一個項目,資料庫及系統整體構架設計完成之後,和弟兄們經過 一段時間的編碼,系統如期上線,剛開始運行一切良好,後來隨著數據量的急劇膨脹,慢慢出現了很多莫名其妙的問題,經過調試,修改了資料庫中幾個存儲過程的 一些問題。有意思的是,有一個存儲過程里,為了實現一個小的功能,寫了好多好多 ...
  • 介紹 本篇文章主要介紹在插入數據到表中遇到鍵重覆避免插入重覆值的處理方法,主要涉及到IGNORE,ON DUPLICATE KEY UPDATE,REPLACE;接下來就分別看看這三種方式的處理辦法。 IGNORE 使用ignore當插入的值遇到主鍵(PRIMARY KEY)或者唯一鍵(UNIQUE ...
  • 今天我們就來看一下資料庫的各種命令,以下命令全部是從CMD命令視窗下的命令行輸入指令,首先如果如果輸入mysql,系統提示“mysql不是內部命 令或外部命令。那麼這其實是環境變數沒有設置好的原因,例如我的mysql的安裝目錄是C:\news\mySql,則在環境變數中的系統變數PATH路 徑中輸入 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...