看了很久博客园的博客,今天有点小冲动,写一个小小分享,欢迎吐槽,O(∩_∩)O哈哈~
微信活动小游戏开发,游戏中需要不断的恢复体力值,体力相关数据都存储在redis中。
1.当日首次登录,增加全部体力值;
2.周期性增加定额体力值;
出于用户量小、简单的考虑,对整个系统用户执行全量更新。
相关表:
体力
表名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 }
每次任务都会把不需要恢复体力的用户也都恢复一遍,而且这个还是周期性的任务,所以需要优化。
增加一个表,记录更新时间。
用户体力恢复的时候,同步维护一个体力时间有序集合表。
表名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,感觉还可以,类似这种业务处理的都可以这么做,提升效率。
原标题:Redis小实战分享
关键词:Redis