网站云服务器,低价备案域名,西安网站建设报价方案,硬件开发项目流程游戏中经常会有排行榜需求需要实现#xff0c;例如常见的战力排行榜、积分排行榜等等。
排行榜一般会用到 Redis 来实现#xff0c;原因是#xff1a;
Redis 基于内存操作#xff0c;速度快Redis 提供了高效的有序集合 zset 例如创建一个名为 rank 的排行榜
# 为用户use…游戏中经常会有排行榜需求需要实现例如常见的战力排行榜、积分排行榜等等。
排行榜一般会用到 Redis 来实现原因是
Redis 基于内存操作速度快Redis 提供了高效的有序集合 zset 例如创建一个名为 rank 的排行榜
# 为用户user1设置分数为1zadd rank 1 user1# 获取排行榜中全部用户的排名和分数分数顺序排序zrange rank 0 -1 withscores
1) user1
2) 1
3) user2
4) 2
5) user3
6) 3# 获取排行榜中全部用户的排名和分数分数倒序排序zrevrange rank 0 -1 withscores
1) user3
2) 3
3) user2
4) 2
5) user1
6) 1# 获取排行榜中排名前2的用户的排名和分数分数倒序排序zrevrange rank 0 1 withscores
1) user3
2) 3
3) user2
4) 2# 获取排行榜中用户user2的排名zrank rank user2
(integer) 1纵然 redis 的速度很快但是再加上网络请求的开销和单线程问题也比不上应用内直接内存的速度所以为了速度一般会在游戏内缓存排行榜。获取排行榜时优先从内存中获取并定时从 redis 同步数据到内存。
下面是一个简单的例子实现了获取排行榜信息和用户排名数据。
public class RankTest { Data AllArgsConstructor public static class UserRankInfo { private long userID; private int rank; private double score; } /** * 缓存的用户信息 */ private static final MapLong, UserRankInfo USER_RANK_INFO_MAP new ConcurrentHashMap(); /** * 上次同步时间 */ private static int LAST_SYNC_TIME 0; /** * 每隔多长时间从redis同步一次 */ private static final int SYNC_EVERY_SECOND 60 * 10; /** * 获取排行榜 */ public CollectionUserRankInfo getRankList() { if ((int) (System.currentTimeMillis() / 1000) LAST_SYNC_TIME SYNC_EVERY_SECOND) { syncUserRankInfoMap(); } return USER_RANK_INFO_MAP.values(); }private void syncUserRankInfoMap() { try (Jedis jedis new Jedis(127.0.0.1, 6379);) { // 获取前50名的用户 SetTuple tuples jedis.zrevrangeWithScores(rank, 0, 49); putUserRankInfoMap(tuples); LAST_SYNC_TIME (int) (System.currentTimeMillis() / 1000); } } private void putUserRankInfoMap(SetTuple tuples) { USER_RANK_INFO_MAP.clear(); int rank 0; for (Tuple tuple : tuples) { long userID Long.parseLong(tuple.getElement()); UserRankInfo info new UserRankInfo(userID, rank, tuple.getScore()); USER_RANK_INFO_MAP.put(userID, info); } } /** * 获取用户排名信息 */ public UserRankInfo getUserRankInfo(long userID) { if ((int) (System.currentTimeMillis() / 1000) LAST_SYNC_TIME SYNC_EVERY_SECOND) { syncUserRankInfoMap(); } return USER_RANK_INFO_MAP.get(userID); } /** * 设置用户分数 */ public void setUserRankScore(long userID,double score){ try (Jedis jedis new Jedis(127.0.0.1, 6379);) { jedis.zadd(rank, score, String.valueOf(userID)); // 获取前50名的用户 SetTuple tuples jedis.zrevrangeWithScores(rank, 0, 49); putUserRankInfoMap(tuples); LAST_SYNC_TIME (int) (System.currentTimeMillis() / 1000); } }
}开发中上面的例子还存在不少问题
因为 redis 操作比较耗时所以一般都会放在异步线程中进行操作缓存数据的更新不是原子的一旦多个用户同时请求可能会导致数据重复更新多次相同的分数的用户的排名会按照用户名来排序
针对于问题 3因为用户在相同分数的情况下 redis 只支持根据用户名的字典排序并不支持自定义排序。但是这对玩家来说是不可接受的。一个解决办法让相同分数的玩家按照达成时间的判断最先抵达的玩家排名最高。
我们可以使用真实分数 时间戳倒数作为排名分数真实分数作为整数部分时间戳倒数作为小数部分。
public void setUserRankScore(long userID,int score){ try (Jedis jedis new Jedis(127.0.0.1, 6379);) { //因为毫秒时间戳最多有13位 double newScorescore1000_000_000_000.0D/System.currentTimeMillis(); jedis.zadd(rank, newScore, String.valueOf(userID)); // 获取前50名的用户 SetTuple tuples jedis.zrevrangeWithScores(rank, 0, 49); putUserRankInfoMap(tuples); LAST_SYNC_TIME (int) (System.currentTimeMillis() / 1000); }
}参考
Redis sorted sets | RedisRedis实现排行榜及相同积分按时间排序 - 知乎Redis 浮点数累计实现-腾讯云开发者社区-腾讯云