十万级游戏用户分类/全局排行榜缓存排序性能优化问询
游戏排行榜系统性能优化方案(10万+用户场景)
现有方案的核心问题
当前方案的性能瓶颈完全来自全量拉取+全量排序的模式:每10分钟拉取10万+玩家的25类数据,对每个分类做全量排序,再遍历所有玩家计算全局得分后又做一次全量排序。这种O(M*NlogN)(M为分类数)的操作在10万级数据下必然导致40-60秒的高耗时,直接占满CPU和内存。
针对性优化措施
1. 用增量更新替代全量定时刷新
- 放弃每10分钟全量同步逻辑,改为实时增量同步:通过监听MySQL的binlog(比如Canal工具)捕捉玩家得分更新事件,仅更新缓存中对应玩家的分类得分,而非全量重建缓存。
- 分类缓存改用有序集合:把
HashMap<Category, ArrayList<User>>换成ConcurrentSkipListSet(线程安全)或FastUtil的Object2ObjectRBTreeMap,自定义比较器按分类得分降序排列。新增/更新玩家时直接插入到有序集合对应位置,时间复杂度O(logN),彻底避免全量排序开销。 - 全局得分增量计算:当玩家某分类得分变化时,仅重新计算该玩家的全局得分(基于分类排名×权重),然后从全局有序集合中移除旧数据、插入更新后的数据,避免全量遍历计算。
2. 优化全局得分计算逻辑
- 提前维护分类排名:在分类有序集合中,玩家的排名可直接通过其在集合中的索引获取(或在User对象中维护
categoryRank字段,更新分类得分时同步更新),无需每次遍历计算。 - 预存分类权重:把25个分类的权重存入本地配置或分布式配置中心,计算全局得分时直接读取,避免重复查询或硬编码。
3. 让数据库分担部分压力
- 只同步增量数据:给MySQL表加
update_time字段,每次仅拉取上次同步后有更新的玩家数据,大幅减少数据传输和内存加载量。 - 初始数据预排序:首次加载缓存时,让数据库通过
ORDER BY score DESC返回已排序的分类数据,直接存入有序集合,避免内存中做第一次全量排序。
4. 替换低效的排序实现
如果某些场景必须做全量排序,别用Java原生List#sort():
- 改用FastUtil库的排序方法,它针对基本类型和自定义对象做了大量优化,排序速度比原生JDK快2-3倍。
- 避免重复排序:如果缓存是实时维护的有序集合,仅在批量导入历史数据时做一次性排序,日常更新无需全量排序。
5. 缓存结构优化
- 全局缓存用“有序集合+快速索引”组合:用
ConcurrentSkipListSet维护全局排序,同时用HashMap<UUID, User>做玩家快速查找。更新时先通过UUID找到玩家,再从有序集合中移除、更新得分后重新插入,保证操作高效性。 - 精简数据:User对象只存必要字段(UUID、各分类得分、全局得分),剔除无关数据,减少内存占用。
6. 异步分批次处理(针对必须全量刷新的场景)
如果业务上必须保留定时全量刷新逻辑:
- 分批次处理:每次处理5个分类,每个分类分10000条数据为一批,异步执行,避免CPU被长时间独占。
- 线程池控并发:配置核心线程数为CPU核心数的1-2倍,避免内存溢出和过载。
内容的提问来源于stack exchange,提问作者Nate
相关产品推荐
相关产品推荐

