Skip to content

排行榜

Rank 模块(Zeze.Game.Rank)为游戏提供高性能的分布式排行榜系统。它通过 ConcurrentLevel 并发分区实现多服务器并行更新,使用多路归并算法聚合查询结果,支持多种时间维度和自定义排行榜。

ConcurrentLevel 是排行榜最关键的配置参数(默认 128)。排行榜数据按照 hash(roleId) % ConcurrentLevel 分散到多个分区中存储和更新。分区数量决定了最大并发度,修改此参数会导致旧数据全部失效并需要重建。

rank.setFuncConcurrentLevel(rankType -> 256);
  • RankSize(默认 100):最终排行榜展示的条目数量。
  • ComputeCount(默认 RankSize * computeFactor,factor 默认 2.5):中间计算数据保留的条目数量,大于 RankSize 以保证合并后结果的准确性。

每个排行榜实例由 BConcurrentKey 唯一标识,包含以下字段:

字段含义
rankType排行榜类型标识
concurrentIndex并发分区索引(内部计算)
timeType时间维度
year年份
offset时间偏移(天/周/季)或自定义 ID

Rank 内置了以下时间类型(BConcurrentKey.TimeType*):

  • Total:全量排行榜,不区分时间。
  • Day:日排行,按年内第几天分。
  • Week:周排行,按年内第几周分。
  • Season:季排行,按春夏秋冬分。
  • Year:年排行,按年份分。
  • Customize:自定义排行榜,使用 customizeId 标识。
// 创建日排行榜 key
BConcurrentKey key = Rank.newRankKey(rankType, BConcurrentKey.TimeTypeDay);
// 创建自定义排行榜 key
BConcurrentKey key = Rank.newRankKey(rankType, customId);
Rank rank = Rank.create(app);
rank.setFuncRankSize(rankType -> 200);
rank.setFuncConcurrentLevel(rankType -> 256);
rank.setFuncRankCacheTimeout(rankType -> 10 * 60 * 1000); // 10分钟缓存
@RedirectHash(ConcurrentLevelSource = "getConcurrentLevel(keyHint.getRankType())")
public RedirectFuture<Long> updateRank(int hash, BConcurrentKey keyHint, long roleId, Bean value);

updateRank 使用 @RedirectHash 注解将请求路由到正确的并发分区。执行流程:

  1. 先移除该 roleId 在当前分区的旧记录。
  2. 使用 compactor 比较器在有序列表中找到合适位置插入。
  3. 如果列表超过 ComputeCount,移除末尾多余记录。

默认比较器 LongOnlyCompactorBValueLong 的值降序排列。可以通过 setCompactor 自定义排序逻辑。

public RedirectFuture<Long> removeRank(int hash, BConcurrentKey keyHint, long roleId);

从指定分区中移除某个角色的排行记录。

RankTotal total = rank.getRankTotal(keyHint); // 返回 RankSize 条
RankTotal total = rank.getRankTotal(keyHint, 500); // 返回 500 条

查询流程:

  1. 检查缓存是否有效(rankCacheTimeout,默认 5 分钟)。
  2. 缓存失效则调用 getRankDirect 重新构建。
  3. getRankDirect 遍历所有并发分区,使用多路归并算法合并排序。
long position = rank.getRankPosition(keyHint, roleId); // 精确排名
long position = rank.getRankPositionWithGuess(keyHint, roleId, score, totalUser); // 估算排名

getRankPosition 返回精确排名(在榜内时)或 -1(未入榜)。getRankPositionWithGuess 在未入榜时根据分数和总人数估算大致排名。

rank.deleteRank(keyHint); // 删除所有并发分区的数据
rank.mergeRank(keyHintFrom, keyHintTo); // 将 from 合并到 to

直接合并两个排行榜的所有分区数据。合并后保留 ComputeCount 条记录。

排行榜的值类型通过 BeanFactory 管理。自定义值类型需要在启动时注册:

Rank.register(MyCustomRankValue.class);

注册的 Bean 类型会持久化其类名,系统在反序列化时通过 beanFactory 自动还原。

Rank 模块利用 Zeze 的 Redirect 机制实现分布式:

  • updateRankremoveRank 使用 @RedirectHash 根据 roleId 的哈希值将请求路由到目标服务器。
  • 查询操作(getRankTotal)在任意服务器上执行,直接读取本地数据库并合并所有分区数据。
  • 排行榜数据通过 Zeze 的缓存一致性协议在集群间同步。
  • 更新操作在 Zeze 事务内执行,乐观锁保证并发安全。
  • 每个并发分区是一个独立的记录,不同分区的更新互不冲突。
  • 查询结果有缓存机制,避免频繁的全量合并计算。
  • 适当增大 ComputeFactor 可以提高合并后的结果精度,但会占用更多存储空间。