1. 项目背景与核心需求
天机学堂作为一个在线学习平台,其排行榜功能的设计直接关系到用户的学习动力和平台活跃度。实时/历史排行榜看似简单,但背后涉及数据采集、处理、存储和展示的全链路技术实现。在实际开发中,我们既要保证榜单的实时性(毫秒级更新),又要兼顾历史数据的快速查询,这对系统架构提出了双重挑战。
我参与过多个教育类项目的排行榜开发,发现90%的同类产品都存在以下痛点:
- 实时榜单在高并发下响应延迟
- 历史数据查询效率低下
- 榜单更新导致数据库压力激增
- 作弊行为影响榜单公平性
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 整体方案选型
采用"流批一体"架构解决实时与历史需求:
code复制实时层:Kafka -> Flink -> Redis
批处理层:HBase -> Spark -> MySQL
选择理由:
- Flink的Exactly-Once语义保证数据准确性
- Redis的SortedSet天然适合排行榜场景
- HBase的海量数据存储能力满足历史需求
- MySQL的关系型特性方便业务查询
2.2 数据结构设计
Redis中的SortedSet存储方案:
python复制# Key设计
realtime_rank:{courseId}:{date}
# 示例值
ZADD realtime_rank:101:20230801 95 "user123"
HBase表结构设计:
| 列族 | 列限定符 | 说明 |
|---|---|---|
| info | userId | 用户标识 |
| info | score | 最新得分 |
| stat | day_rank | 日排名 |
| stat | week_rank | 周排名 |
3. 实时处理实现
3.1 数据采集端优化
采用分段批量上报策略降低压力:
java复制// 客户端上报伪代码
public void reportScore(String userId, int score) {
localCache.add(new ScoreEvent(userId, score));
if(localCache.size() >= 20 || System.currentTimeMillis() - lastReport > 5000) {
kafkaProducer.send(batchEvents);
localCache.clear();
}
}
3.2 Flink处理逻辑
关键处理流程:
java复制DataStream<ScoreEvent> stream = env
.addSource(kafkaSource)
.keyBy(event -> event.getCourseId())
.process(new RankingCalculator());
// 自定义处理函数
class RankingCalculator extends KeyedProcessFunction<String, ScoreEvent, RankItem> {
private transient ValueState<Double> currentScore;
@Override
public void processElement(ScoreEvent event, Context ctx, Collector<RankItem> out) {
double newScore = calculateWeightedScore(event);
currentScore.update(newScore);
out.collect(new RankItem(event.getUserId(), newScore));
}
private double calculateWeightedScore(ScoreEvent event) {
// 包含时间衰减因子和难度系数
return event.getScore() * Math.pow(0.9, (System.currentTimeMillis() - event.getTimestamp())/3600000.0);
}
}
4. 历史数据处理
4.1 离线计算策略
采用分层计算架构:
- 原始层:存储HBase中的全量数据
- 中间层:Spark每日生成聚合结果
- 服务层:MySQL存储最终展示数据
Spark计算示例:
scala复制val dailyRank = spark.read.hbaseTable("score_detail")
.filter($"date" === targetDate)
.groupBy($"courseId", $"userId")
.agg(max($"score").as("maxScore"))
.withColumn("rank", rank().over(Window.partitionBy($"courseId").orderBy($"maxScore".desc)))
4.2 数据冷热分离
设计三级存储策略:
- 热数据(7天内):Redis
- 温数据(30天内):MySQL
- 冷数据(30天+):HBase+OSS
5. 性能优化实践
5.1 Redis调优经验
- 内存优化:
bash复制# 调整zset的ziplist配置
zset-max-ziplist-entries 512
zset-max-ziplist-value 64
- 集群分片策略:
python复制def get_redis_conn(course_id):
slot = course_id % 16
if slot < 8:
return redis_cluster1
else:
return redis_cluster2
5.2 防作弊机制
实现多维度的异常检测:
- 速度检测:10分钟内得分增长不超过1000
- 模式识别:使用孤立森林算法检测异常行为
- 设备指纹:识别多账号作弊
python复制def check_cheat(user_id, new_score):
history = get_24h_history(user_id)
if new_score - history['max'] > 500:
trigger_review(user_id)
if len(get_active_devices(user_id)) > 3:
block_account(user_id)
6. 踩坑实录
-
ZSET卡顿问题
当单个课程用户量超过50万时,ZRANGE操作出现明显延迟。解决方案:- 采用分段查询:先ZRANK获取位置,再分段获取周边用户
- 增加二级缓存:缓存TOP1000的榜单
-
Flink状态膨胀
长期运行的KeyedProcessFunction导致状态过大。优化方案:- 定期清理非活跃用户状态
- 设置TTL自动过期
-
Spark数据倾斜
热门课程的计算耗时异常。处理方法:scala复制.repartition(100, $"courseId") // 显式重分区 .sql("/*+ SKEWJOIN(course_skew) */") // 倾斜join提示
7. 监控与运维
7.1 指标体系建设
核心监控指标:
- 实时延迟:P99 < 500ms
- 计算准确率:99.99%
- 数据完整性:每日0点校验
Grafana监控面板配置示例:
json复制{
"panels": [{
"title": "实时处理延迟",
"targets": [{
"expr": "histogram_quantile(0.99, sum(rate(flink_latency_bucket[1m])) by (le))"
}]
}]
}
7.2 灾备方案
设计双活架构保证高可用:
- 实时链路:Kafka镜像集群+Flink Checkpoint
- 数据存储:Redis主从+HBase副本
- 定期演练:每月模拟单机房故障
8. 效果验证
上线后关键指标对比:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 实时更新延迟 | 1200ms | 230ms |
| 历史查询耗时 | 8s | 1.2s |
| 高峰QPS | 1500 | 8500 |
| 存储成本 | 100% | 65% |
用户行为数据变化:
- 每日榜单查看次数提升340%
- 课程完课率提高22%
- 作弊账号识别准确率达98.7%
在实际运行中,我们发现排行榜的展示策略需要动态调整。比如编程类课程更适合展示代码质量评分,而语言学习类课程则应该突出连续学习天数。这需要建立课程特征与展示策略的映射关系表,通过配置中心实时生效。
