Redis小实战分享

看了很久博客园的博客,今天有点小冲动,写一个小小分享,欢迎吐槽,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,感觉还可以,类似这种业务处理的都可以这么做,提升效率。

 

 

posted @ 2016-04-14 23:06  Moary  阅读(607)  评论(0)    收藏  举报