俳在 Java 中,Optional是用于优雅处理null的容器类,其核心目标是,避免。以下是Optional。P

掳谢冀秩构建实时排行榜时,我们需要满足以下关键需求:

高并发:支持每秒数万次更新操作

低延迟:毫秒级响应时间

动态排序:实时更新玩家排名

可扩展性:支持百万级用户规模

Redis Sorted Set(ZSET)完美满足这些需求,它具有以下特性:

唯一性:成员(member)不可重复

有序性:按分数(score)自动排序

高性能:所有操作时间复杂度 ≤ O(log N)

灵活性:支持分数范围查询、排名查询等

Redis Sorted Set 核心命令详解

1. 添加/更新元素

ZADD leaderboard 1500 "player1" # 添加元素

ZINCRBY leaderboard 500 "player1" # 增加分数

2. 查询操作

ZSCORE leaderboard "player1" # 获取分数

ZREVRANK leaderboard "player1" # 获取排名(降序)

ZCARD leaderboard # 获取总人数

3. 范围查询

# 获取前100名

ZREVRANGE leaderboard 0 99 WITHSCORES

# 查询分数段 [2500, 3500]

ZRANGEBYSCORE leaderboard 2500 3500 WITHSCORES

4. 删除操作

ZREM leaderboard "player1" # 删除玩家

ZREMRANGEBYSCORE leaderboard -inf 1000 # 删除低分玩家

Java 实现实时排行榜

环境准备

redis.clients

jedis

4.4.3

排行榜服务核心类

import redis.clients.jedis.Jedis;

import redis.clients.jedis.Tuple;

import java.util.Set;

public class LeaderboardService {

private final Jedis jedis;

private final String leaderboardKey;

public LeaderboardService(String host, int port, String leaderboardKey) {

this.jedis = new Jedis(host, port);

this.leaderboardKey = leaderboardKey;

}

// 更新玩家分数

public double updateScore(String playerId, double delta) {

return jedis.zincrby(leaderboardKey, delta, playerId);

}

// 获取玩家排名(从1开始)

public Long getPlayerRank(String playerId) {

Long rank = jedis.zrevrank(leaderboardKey, playerId);

return rank != null ? rank + 1 : null;

}

// 获取玩家分数

public Double getPlayerScore(String playerId) {

return jedis.zscore(leaderboardKey, playerId);

}

// 获取前N名玩家

public Set getTopPlayers(int limit) {

return jedis.zrevrangeWithScores(leaderboardKey, 0, limit - 1);

}

// 获取玩家周围排名(前后各range名)

public Set getAroundPlayer(String playerId, int range) {

Long rank = getPlayerRank(playerId);

if (rank == null) return null;

long start = Math.max(0, rank - 1 - range);

long end = rank - 1 + range;

return jedis.zrevrangeWithScores(leaderboardKey, start, end);

}

// 添加玩家(初始分数)

public void addPlayer(String playerId, double initialScore) {

jedis.zadd(leaderboardKey, initialScore, playerId);

}

}

处理同分排名问题

Redis 默认按字典序排序同分玩家,我们可以通过组合分数实现精确排序:

public class ScoreComposer {

private static final double MAX_TIMESTAMP = 1e15; // 支持到公元3000年

public static double composeScore(double realScore, long timestamp) {

// 组合分数 = 原始分数 + (1 - timestamp/10^15)

return realScore + (1 - timestamp / MAX_TIMESTAMP);

}

public static double extractRealScore(double composedScore) {

return Math.floor(composedScore);

}

}

// 使用示例

long timestamp = System.currentTimeMillis();

double realScore = 1500;

double composedScore = ScoreComposer.composeScore(realScore, timestamp);

leaderboardService.addPlayer("player1", composedScore);

分页查询实现

public Set getRankingPage(int page, int pageSize) {

long start = (long) (page - 1) * pageSize;

long end = start + pageSize - 1;

return jedis.zrevrangeWithScores(leaderboardKey, start, end);

}

高级特性与性能优化

1. 冷热数据分离

// 赛季结束时的数据归档

public void archiveSeason(String newSeasonKey) {

// 1. 持久化当前赛季数据到数据库

Set allPlayers = jedis.zrevrangeWithScores(leaderboardKey, 0, -1);

saveToDatabase(allPlayers);

// 2. 创建新赛季排行榜

jedis.del(leaderboardKey);

initializeNewSeason(newSeasonKey);

// 3. 更新当前赛季key

this.leaderboardKey = newSeasonKey;

}

private void saveToDatabase(Set players) {

// 实现数据库批量写入逻辑

// 可以使用JDBC批处理或MyBatis

}

2. 分布式排行榜

// 分片策略

public String getShardKey(String playerId, int totalShards) {

int shard = Math.abs(playerId.hashCode()) % totalShards;

return "leaderboard:shard:" + shard;

}

// 全局排名查询(示例)

public Long getGlobalRank(String playerId, int totalShards) {

String shardKey = getShardKey(playerId, totalShards);

Long rankInShard = jedis.zrevrank(shardKey, playerId);

if (rankInShard == null) return null;

// 计算全局排名(需要合并所有分片)

long globalRank = rankInShard;

for (int i = 0; i < shard; i++) {

String key = "leaderboard:shard:" + i;

globalRank += jedis.zcard(key);

}

return globalRank + 1;

}

3. 性能优化策略

// 使用管道批处理

public void batchUpdateScores(Map updates) {

Pipeline pipeline = jedis.pipelined();

for (Map.Entry entry : updates.entrySet()) {

pipeline.zincrby(leaderboardKey, entry.getValue(), entry.getKey());

}

pipeline.sync();

}

// 添加本地缓存

private final Cache rankCache =

CacheBuilder.newBuilder()

.expireAfterWrite(1, TimeUnit.SECONDS) // 1秒过期

.maximumSize(10000)

.build();

public Long getCachedPlayerRank(String playerId) {

try {

return rankCache.get(playerId, () -> getPlayerRank(playerId));

} catch (ExecutionException e) {

return getPlayerRank(playerId);

}

}

实战:游戏排行榜系统架构

游戏客户端

API Gateway

排行榜服务

Redis Cluster

持久化存储

管理后台

监控系统

核心组件

API Gateway:处理客户端请求,负载均衡

排行榜服务:无状态服务,水平扩展

Redis Cluster:分片部署,主从复制

持久化存储:MySQL 或 PostgreSQL

监控系统:Prometheus + Grafana

Java 服务部署方案

容器化:Docker + Kubernetes

配置管理:Spring Cloud Config

服务发现:Consul 或 Zookeeper

流量控制:Sentinel 或 Hystrix

性能测试数据

操作类型 10万元素耗时 100万元素耗时

更新分数 0.8 ms 1.2 ms

获取单个排名 0.3 ms 0.4 ms

获取前100名 1.2 ms 1.5 ms

分页查询(100条) 1.5 ms 1.8 ms

测试环境:AWS c6g.4xlarge, Redis 7.0, Java 17

最佳实践与注意事项

键设计规范

// 使用业务前缀和版本号

String key = "lb:v1:season5:global";

内存优化

// 定期清理低分玩家

jedis.zremrangeByScore(leaderboardKey, 0, 1000);

集群部署

// 使用JedisCluster

Set nodes = new HashSet<>();

nodes.add(new HostAndPort("redis1", 6379));

nodes.add(new HostAndPort("redis2", 6379));

JedisCluster cluster = new JedisCluster(nodes);

异常处理

try {

return jedis.zrevrank(leaderboardKey, playerId);

} catch (JedisConnectionException e) {

// 重试逻辑或降级处理

log.error("Redis连接异常", e);

return getRankFromCache(playerId);

}

总结

通过 Redis Sorted Set 和 Java 的强大组合,我们可以构建出高性能的实时排行榜系统:

核心优势:

毫秒级更新和查询

线性扩展能力

高可用架构

关键实现:

使用 ZADD/ZINCRBY 更新分数

使用 ZREVRANGE 获取排行榜

组合分数解决同分排名问题

分片策略支持海量用户

适用场景:

游戏积分榜

电商热销榜

社交平台影响力排名

赛事实时排名

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值