游戏设计之-排行榜处理
前言
我相信大部分人,乃至公司和团队在设计排行榜都考虑的是redis,zadd操作,不需要排序,维护获取,操作都极其简单;
无一例外我也是;
在项目中运营了大量的模板,来处理各个木块的排行榜信息;
统一的会在晚上又一些结算处理;就牵涉一次性拉取,过滤,发放奖励,甚至还有删除操作;
同时为了节约运营陈本,定期还需要对redis里面无效数据进行删除,收缩操作;
目前redis已经达到6.55g,这只是其中一个平台运营部署;
运营过项目的都知道云redis的费用是非常高昂的;所以就需要定期做一些无用数据的删除操作;
可是问题来了,随着运营时间的推移,每天早上总能看到如此的异常推送;
就问你揪心不?仔细一查才发现还是数据库相关的操作导致的卡主了;
于是下定决心对排行榜等相关功能进行调整;
削峰
1. 首先就是对数据库的一些删除操作,集中早上5-6点去处理他;这个时候在线玩家最少;处理一些垃圾数据最为合理;基本卡了也不会对玩家的感觉特别强烈反馈;
于是把原来处理的数据移除的代码调整为定时器创建异步任务在凌晨5-6点去处理;
2. 第二步就是为了避免玩家集体拉取数据,并对数据进行操作;考虑重构排行榜代码需求,
排行榜自定义实现
为了模拟排行榜实现的,
首先应该考虑,排行榜数据,既方便查找修改,又方便排序;
那么肯定需要考虑的是
一个map,用于存取和玩家相关积分数据;
一个list,用于排序积分;
首先我们来实现一个积分类
1 package org.wxd.lang.rank; 2 3 import lombok.Getter; 4 import lombok.Setter; 5 import lombok.experimental.Accessors; 6 import org.wxd.io.ObjectFactory; 7 import org.wxd.timer.TimeUtil; 8 9 import java.util.Comparator; 10 import java.util.Objects; 11 12 /** 13 * 排行 14 * 15 * @author: Troy.Chen(無心道, 15388152619) 16 * @version: 2022-12-08 21:27 17 **/ 18 @Getter 19 @Setter 20 @Accessors(chain = true) 21 public class RankScore implements Comparable<RankScore> { 22 23 /** 正序 */ 24 public static final Comparator<RankScore> Sort = (o1, o2) -> { 25 if (o1.score != o2.score) { 26 return Double.compare(o1.score, o2.score); 27 } 28 29 if (o1.scoreTime != o2.scoreTime) { 30 /**时间取值要倒叙*/ 31 return Long.compare(o2.scoreTime, o1.scoreTime); 32 } 33 34 return Long.compare(o1.uid, o2.uid); 35 }; 36 37 /** 倒叙 */ 38 public static final Comparator<RankScore> BreSort = (o1, o2) -> { 39 if (o1.score != o2.score) { 40 return Double.compare(o1.score, o2.score); 41 } 42 43 if (o1.scoreTime != o2.scoreTime) { 44 /**时间取值要倒叙*/ 45 return Long.compare(o2.scoreTime, o1.scoreTime); 46 } 47 48 return Long.compare(o1.uid, o2.uid); 49 }; 50 51 private long uid; 52 private double score; 53 private long scoreTime; 54 55 public RankScore setScore(double score) { 56 this.score = score; 57 this.scoreTime = TimeUtil.currentTimeMillis(); 58 return this; 59 } 60 61 @Override public int compareTo(RankScore o2) { 62 return Sort.compare(this, o2); 63 } 64 65 public int scoreIntValue() { 66 return (int) score; 67 } 68 69 public long scoreLongValue() { 70 return (long) score; 71 } 72 73 @Override public boolean equals(Object o) { 74 if (this == o) return true; 75 if (o == null || getClass() != o.getClass()) return false; 76 RankScore rankScore = (RankScore) o; 77 return uid == rankScore.uid; 78 } 79 80 @Override public int hashCode() { 81 return Objects.hash(uid); 82 } 83 84 @Override public String toString() { 85 return ObjectFactory.stringBuilder(sb -> { 86 sb.append(this.getClass().getSimpleName()).append("{"); 87 sb.append("uid=").append(uid); 88 sb.append(", score=").append(score); 89 sb.append('}'); 90 }); 91 } 92 }
接下来我实现代码
1 package test; 2 3 import org.junit.Test; 4 import org.wxd.lang.RandomUtils; 5 import org.wxd.lang.rank.RankScore; 6 import org.wxd.system.MarkTimer; 7 8 import java.util.ArrayList; 9 import java.util.HashMap; 10 import java.util.List; 11 12 /** 13 * @author: Troy.Chen(無心道, 15388152619) 14 * @version: 2022-12-08 20:48 15 **/ 16 public class RankPackTest { 17 18 public class RankPack { 19 HashMap<Long, RankScore> rankMap = new HashMap<>(); 20 List<Long> rankList = new ArrayList<>(); 21 22 public void addScore(long uid, double score) { 23 /*忽律并发问题 可以自行改为 ConcurrentHashMap*/ 24 rankMap.computeIfAbsent(uid, l -> { 25 RankScore rankScore = new RankScore().setUid(uid).setScore(score); 26 /*能到这里初始化,那么list里面必然也没有数据*/ 27 rankList.add(uid); 28 return rankScore; 29 }); 30 } 31 32 public void sort() { 33 /*忽律并发问题*/ 34 rankList.sort((o1, o2) -> { 35 RankScore r1 = rankMap.get(o1); 36 RankScore r2 = rankMap.get(o2); 37 return r1.compareTo(r2); 38 }); 39 } 40 41 public void breSort() { 42 /*忽律并发问题*/ 43 rankList.sort((o1, o2) -> { 44 RankScore r1 = rankMap.get(o1); 45 RankScore r2 = rankMap.get(o2); 46 return r2.compareTo(r1); 47 }); 48 } 49 50 } 51 52 RankPack rankPack = new RankPack(); 53 54 @Test 55 public void test() { 56 init(); 57 sort(); 58 sort(); 59 sort(); 60 sort2(); 61 sort2(); 62 sort2(); 63 } 64 65 public int randomScore() { 66 return RandomUtils.random(100, 10000); 67 } 68 69 public void init() { 70 MarkTimer build = MarkTimer.build(); 71 int speed = 1; 72 for (int i = 0; i < 300; i++) { 73 rankPack.addScore(speed, randomScore()); 74 speed++; 75 } 76 rankPack.breSort(); 77 float v = build.execTime(); 78 System.out.println(rankPack.getClass().getSimpleName() + " - 数量:" + rankPack.rankMap.size() + ", 全部排序耗时:" + v); 79 show(); 80 } 81 82 public void sort() { 83 MarkTimer build = MarkTimer.build(); 84 rankPack.rankMap.values().forEach(rankScore -> rankScore.setScore(randomScore())); 85 rankPack.breSort(); 86 float v = build.execTime(); 87 System.out.println(rankPack.getClass().getSimpleName() + " - 数量:" + rankPack.rankMap.size() + ", 全部排序耗时:" + v); 88 show(); 89 } 90 91 public void sort2() { 92 MarkTimer build = MarkTimer.build(); 93 RankScore randomItem = (RankScore) RandomUtils.randomItem(rankPack.rankMap.values()); 94 randomItem.setScore(randomScore()); 95 rankPack.breSort(); 96 float v = build.execTime(); 97 show(); 98 int index = rankPack.rankList.indexOf(randomItem.getUid()); 99 System.out.println(v + " - " + randomItem + " - 当前排名:" + (index + 1)); 100 } 101 102 public void show() { 103 // AtomicInteger atomicInteger = new AtomicInteger(); 104 // for (int i = 0; i < 10; i++) { 105 // Long uid = rankPack.rankList.get(i); 106 // RankScore rankScore = rankPack.rankMap.get(uid); 107 // System.out.println(rankScore.toString() + " - 排名:" + (i + 1)); 108 // } 109 } 110 111 }
运行一下看看效果
视乎性能还可以,等等,我们是不是忽律一个问题,现在排行榜只有300个对象哦,加到3万试试
很明显看得出来,全排序情况下直接翻了30倍左右;单个项修改后排序直接翻了差不多40倍;
到这里或许有很多已经满足需求了,;
可是我的需求远不止如此,这很明显时间消耗太久了,不知道有么有更为适合的方案来处理性能;
突然想到一个问题,或许了解树结构的同学知道,树装结构数据,在插入数据的时候就已经排序了,并且根据hash索引,性能非常高;
由此我们想到,
那么我们排序很耗时;我能不能不做这个排序操作,通过索引数据结构来达到目的呢?
改造树装结构
我们把刚才hashmap改为treemap
吧list改为treeset试试效果
测试一下,感觉确实是快了很多呢
我们来打印一下数据看看情况
哦豁,为什么数据不对;
不是我们预期的效果,
然后研究了treeset数据结构就知道,它是排序的,但是他是add的时候排序的,一旦add就不再变更了;
那我们能不能尝试修改的时候先移除,再修改,然后在add呢?
我们来改造一下addScore方法块;
排序正常了
可以看出运行结构,整体差距不算特别大;
如果考虑并发性能问题;可以把
TreeMap 换成 ConcurrentSkipListMap
TreeSet 换成 ConcurrentSkipListSet
java自带的跳表结构,添加删除查询,都会非常高效;
最后粘贴一下最新的全部测试代码
1 package test; 2 3 import org.junit.Test; 4 import org.wxd.lang.RandomUtils; 5 import org.wxd.lang.rank.RankScore; 6 import org.wxd.system.MarkTimer; 7 8 import java.util.concurrent.ConcurrentSkipListMap; 9 import java.util.concurrent.ConcurrentSkipListSet; 10 11 /** 12 * @author: Troy.Chen(無心道, 15388152619) 13 * @version: 2022-12-08 20:48 14 **/ 15 public class RankPackTest { 16 17 public class RankPack { 18 ConcurrentSkipListMap<Long, RankScore> rankMap = new ConcurrentSkipListMap<>(); 19 ConcurrentSkipListSet<RankScore> rankList = new ConcurrentSkipListSet<>(); 20 21 public void addScore(long uid, double score) { 22 /*忽律并发问题 可以自行改为 ConcurrentHashMap*/ 23 RankScore score1 = rankMap.computeIfAbsent(uid, l -> { 24 RankScore rankScore = new RankScore().setUid(uid); 25 return rankScore; 26 }); 27 rankList.remove(score1); 28 score1.setScore(score); 29 rankList.add(score1); 30 } 31 32 // public void sort() { 33 // /*忽律并发问题*/ 34 // rankList.sort((o1, o2) -> { 35 // RankScore r1 = rankMap.get(o1); 36 // RankScore r2 = rankMap.get(o2); 37 // return r1.compareTo(r2); 38 // }); 39 // } 40 // 41 // public void breSort() { 42 // /*忽律并发问题*/ 43 // rankList.sort((o1, o2) -> { 44 // RankScore r1 = rankMap.get(o1); 45 // RankScore r2 = rankMap.get(o2); 46 // return r2.compareTo(r1); 47 // }); 48 // } 49 50 } 51 52 RankPack rankPack = new RankPack(); 53 54 @Test 55 public void test() { 56 sort(); 57 sort(); 58 sort(); 59 sort(); 60 sort2(); 61 sort2(); 62 sort2(); 63 sort2(); 64 } 65 66 public int randomScore() { 67 return RandomUtils.random(100, 10000); 68 } 69 70 public void sort() { 71 MarkTimer build = MarkTimer.build(); 72 int speed = 1; 73 for (int i = 0; i < 30000; i++) { 74 rankPack.addScore(speed, randomScore()); 75 speed++; 76 } 77 // rankPack.breSort(); 78 float v = build.execTime(); 79 System.out.println(rankPack.getClass().getSimpleName() + " - 数量:" + rankPack.rankList.size() + ", 全部排序耗时:" + v); 80 show(); 81 } 82 83 public void sort2() { 84 MarkTimer build = MarkTimer.build(); 85 RankScore randomItem = (RankScore) RandomUtils.randomItem(rankPack.rankMap.values()); 86 rankPack.addScore(randomItem.getUid(), randomScore()); 87 // rankPack.breSort(); 88 float v = build.execTime(); 89 show(); 90 int index = 0; 91 for (RankScore rankScore : rankPack.rankList) { 92 if (rankScore.getUid() == randomItem.getUid()) break; 93 index++; 94 } 95 System.out.println(v + " - " + randomItem + " - 当前排名:" + (index + 1)); 96 } 97 98 public void show() { 99 int i = 0; 100 for (RankScore rankScore : rankPack.rankList) { 101 System.out.println(rankScore.toString() + " - 排名:" + (i + 1)); 102 i++; 103 if (i >= 10) break; 104 } 105 } 106 107 }
大家如果有兴趣可以自己测试哦;
好了这里提供集中思路来处理排行榜相关的数据;以及排名功能;
不知道园子里的朋友们,还有没有更好的思路;
听说大家喜欢看,机会是留给有耐心的人
跪求保留标示符 /** * @author: Troy.Chen(失足程序员, 15388152619) * @version: 2021-07-20 10:55 **/ C#版本代码 vs2010及以上工具可以 java 开发工具是netbeans 和 idea 版本,只有项目导入如果出现异常,请根据自己的工具调整 提供免费仓储。 最新的代码地址:↓↓↓ https://gitee.com/wuxindao 觉得我还可以,打赏一下吧,你的肯定是我努力的最大动力