You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Elasticsearch单次查询获取多用户游戏排行榜排名?

游戏排行榜:如何用Elasticsearch单次查询获取多个用户的排名?

背景

需求为游戏结束后展示所有用户的排行榜,现有Elasticsearch数据映射结构如下:

{"mappings": {"properties": {"userId":{"type": "keyword"},"score":{"type": "double"},"timeStamp":{"type": "date"}}}}

用户每次游戏结束后会获得分数并向ES插入文档,目前可通过以下聚合查询获取指定用户某时段的总分:

GET integral_stream/_search
{"query": {"bool": {"filter": [{"term": {"userId": "1"}, "range": {"timestamp": {"gte": "2023-07-31T05:00:00.000Z","lte": "2023-08-07T04:59:59.999Z"}}]}},"size": 10, "aggs": {"users": {"terms": {"field": "userId"},"aggs": {"total_score": {"sum": {"field": "score"}}}}}}

当前问题

虽可通过统计总分大于等于某用户的桶数得到其排名,但如何通过单次查询获取多个用户的排名?若无法实现,是否需要调整数据结构甚至放弃使用ES构建排行榜?


单次查询获取多用户排名的实现方法

Elasticsearch没有直接返回多用户排名的原生聚合,但可以通过两种方式实现:

  • 脚本字段+多聚合统计
    先通过terms聚合算出所有用户的总分并按降序排序,再针对每个目标用户添加filter聚合,统计总分大于等于该用户的桶数量,这个数量就是用户的排名。注意:如果存在同分用户,需要额外处理并列排名的逻辑。不过这种方式在用户量较大时性能会明显下降,因为需要遍历所有用户桶。

  • 借助rank_feature字段(ES 7.10+)
    若可以定期预计算用户总分,可将总分存储为rank_feature类型,维护一个预聚合索引,映射示例如下:

    {"mappings": {"properties": {"userId": {"type": "keyword"}, "total_score": {"type": "rank_feature"}, "timeRange": {"type": "keyword"}}}}
    

    之后可以通过function_score结合rank_feature计算排名,或用script查询统计符合条件的用户数,这种方式比实时聚合更高效。

是否需要调整数据结构?

如果用户量较小(几万级别),现有结构可通过上述方法支撑;但用户量达到几十万甚至百万级时,实时聚合的性能会极差,建议调整数据结构:

  • 使用预聚合索引:创建专门的用户总分索引,每次用户得分更新时,通过ES的update API+脚本进行总分增量更新,而非插入新文档。查询时直接按总分排序,多用户排名可通过批量查询每个用户在排序后的位置,或结合terms聚合与脚本统计。
  • 避免实时聚合大数量级数据:ES的terms聚合处理百万级用户时,内存和CPU消耗极大,预聚合是更优方案。

是否要放弃ES?

不需要放弃ES,但要根据场景选择合适的用法:

  • 若排行榜实时性要求极高(秒级更新)且用户量巨大,可结合Redis的Sorted Set做实时排名,ES负责存储游戏得分明细数据;
  • 若实时性要求一般(分钟级更新),ES的预聚合索引完全可以满足需求,且ES的优势在于支持结合时间范围、其他维度做多维度排行榜统计。

内容的提问来源于stack exchange,提问作者tooth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 19:05:23