当前位置: 首页 > news >正文

游戏中排行榜的后台实现

游戏中经常会有排行榜需求需要实现,例如常见的战力排行榜、积分排行榜等等。

排行榜一般会用到 Redis 来实现,原因是:

  1. Redis 基于内存操作,速度快
  2. Redis 提供了高效的有序集合 zset

例如创建一个名为 rank 的排行榜

# 为用户user1设置分数为1
> zadd 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 Map<Long, 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 Collection<UserRankInfo> 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名的用户  Set<Tuple> tuples = jedis.zrevrangeWithScores("rank", 0, 49);  putUserRankInfoMap(tuples);  LAST_SYNC_TIME = (int) (System.currentTimeMillis() / 1000);  }  }  private void putUserRankInfoMap(Set<Tuple> 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名的用户  Set<Tuple> tuples = jedis.zrevrangeWithScores("rank", 0, 49);  putUserRankInfoMap(tuples);  LAST_SYNC_TIME = (int) (System.currentTimeMillis() / 1000);  }  }
}

开发中,上面的例子还存在不少问题:

  1. 因为 redis 操作比较耗时,所以一般都会放在异步线程中进行操作
  2. 缓存数据的更新不是原子的,一旦多个用户同时请求,可能会导致数据重复更新多次
  3. 相同的分数的用户的排名会按照用户名来排序

针对于问题 3,因为用户在相同分数的情况下, redis 只支持根据用户名的字典排序,并不支持自定义排序。但是这对玩家来说是不可接受的。一个解决办法让相同分数的玩家按照达成时间的判断,最先抵达的玩家排名最高。

我们可以使用(真实分数 + 时间戳倒数)作为排名分数,真实分数作为整数部分,时间戳倒数作为小数部分。

public void setUserRankScore(long userID,int score){  try (Jedis jedis = new Jedis("127.0.0.1", 6379);) {  //因为毫秒时间戳最多有13位  double newScore=score+1000_000_000_000.0D/System.currentTimeMillis();  jedis.zadd("rank", newScore, String.valueOf(userID));  // 获取前50名的用户  Set<Tuple> tuples = jedis.zrevrangeWithScores("rank", 0, 49);  putUserRankInfoMap(tuples);  LAST_SYNC_TIME = (int) (System.currentTimeMillis() / 1000);  }  
}

参考:

  1. Redis sorted sets | Redis
  2. Redis实现排行榜及相同积分按时间排序 - 知乎
  3. Redis 浮点数累计实现-腾讯云开发者社区-腾讯云
http://www.lryc.cn/news/289664.html

相关文章:

  • 《动手学深度学习(PyTorch版)》笔记3.1
  • 【贪吃蛇:C语言实现】
  • 01.领域驱动设计:微服务设计为什么要选择DDD学习总结
  • 写静态页面——魅族导航_前端页面练习
  • Go 命令行解析 flag 包之快速上手
  • React16源码: React中commitAllHostEffects内部的commitDeletion的源码实现
  • [机器学习]简单线性回归——梯度下降法
  • 2024年搭建幻兽帕鲁服务器价格多少?如何自建Palworld?
  • 『OpenCV-Python|鼠标作画笔』
  • 关于如何利用ChatGPT提高编程效率的
  • Excel VBA ——从MySQL数据库中导出一个报表-笔记
  • 金融OCR领域实习日志(一)——OCR技术从0到1全面调研
  • ELK日志解决方案
  • 嵌入式学习-驱动
  • 系统架构17 - 软件工程(5)
  • 空气质量预测 | Python实现基于线性回归、Lasso回归、岭回归、决策树回归的空气质量预测模型
  • MYSQL数据库基本操作-DQL-基本查询
  • gdb 调试 - 在vscode图形化展示在远程的gdb debug过程
  • Android 13.0 SystemUI下拉状态栏定制二 锁屏页面横竖屏时钟都居中功能实现二
  • docker 部署xxl-job
  • Kafka(九)跨集群数据镜像
  • 第3讲 谈谈final、finally、 finalize有什么不同?
  • MC3172 串口模块
  • VUE3 加载自定义SVG文件
  • 【数据分析】numpy基础第五天
  • CSS 双色拼接按钮效果
  • T05垃圾收集算法与垃圾收集器ParNew CMS
  • 每日一道面试题:Java中序列化与反序列化
  • 论文阅读:Vary-toy论文阅读笔记
  • 【Linux】开始使用 vim 吧!!!