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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...