Redis小实战分享
看了很久博客园的博客,今天有点小冲动,写一个小小分享,欢迎吐槽,O(∩_∩)O哈哈~
- 背景:
微信活动小游戏开发,游戏中需要不断的恢复体力值,体力相关数据都存储在redis中。
- 需求:
1.当日首次登录,增加全部体力值;
2.周期性增加定额体力值;
- 实现方案1(全量更新):
出于用户量小、简单的考虑,对整个系统用户执行全量更新。
相关表:
体力
|
表名strength,前缀strength:uid:*,体力表,哈希存储 |
|||||||||
|
定时任务每5分钟(可配置,用户量大时间太短会导致任务叠加)扫描strgtimeline,当前时间和score(上一次恢复体力时间)对比,超过则更新时间,并且更新strength表。
|
表名strgupdate,字符串存储,体力更新表 |
||||||
|
以下是一个体力值增加方法:
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,有序集合存储,体力时间表 |
|||||||||
用户首次登录初始化 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,感觉还可以,类似这种业务处理的都可以这么做,提升效率。

浙公网安备 33010602011771号