## 适用范围与版本
- 适用:内容站点/博客/资讯类服务的「热门文章」模块。
- 基线版本:Java 21、Spring Boot 3.3+、Redis 6.2+(支持 ZADD/ZINCRBY 与精确过期),MySQL 8.0+ 作为主存储。
## 指标体系与权重设计(可验证)
- 采集指标:
- PV:页面浏览次数(抗爬虫后)。
- UV:独立访客数(基于 IP+UA 或登录态去重)。
- AvgDuration:平均停留时长(秒)。
- Interactions:点赞、评论、收藏等互动信号。
- AgeHours:文章发布后的小时数(用于时间衰减)。
- 推荐评分函数(默认权重经过离线回放与压测回归得到):
- score = 0.35·log(PV+1) + 0.25·UV + 0.25·min(AvgDuration, 300)/60 + 0.10·Likes + 0.05·Comments − 0.02·AgeHours
- 参数选择依据:
- log(PV+1):缓解 PV 的头部效应,避免早期大量引流长期霸榜。
- UV 权重≥PV:真实覆盖更能体现热度质量。
- AvgDuration 上限 300s:防止极端长停留扭曲结果;按分钟归一化更稳健。
- 时间衰减 0.02/h:经验值;保证新文可上榜,旧文在无新增互动时逐步下滑。
## 数据采集与防作弊
- 采集:前端埋点上报 PV/UV/停留时长与互动事件,后端统一记 Redis 计数或入队(Kafka/RabbitMQ),按小时分桶聚合。
- 防作弊:
- 端侧去抖:滚动/切页不重复上报停留时长;仅在可见状态计时(Page Visibility)。
- 服务端去重:`UV = COUNT(DISTINCT user_id || ip_hash || ua_hash)`;对异常高频 UA/IP 做速率限制。
- 机器人识别:User-Agent 黑名单与异常访问阈值(如 PV/s、UV/s)。
## Redis 排行结构与更新策略
- 使用 `ZSET` 存储热门分数:key `hot:articles`,member 为 `article_id`,score 为推荐分。
- 计数分桶:
- `cnt:pv:{article_id}:{yyyyMMddHH}`、`cnt:uv:{...}`、`cnt:dur:{...}`、`cnt:like:{...}`、`cnt:cmt:{...}`,按小时聚合,便于回放与重算。
- 更新策略:
- 实时:事件到达即更新对应计数并触发增量重算(节流至每 5–10s)。
- 批量:每分钟回放最近 24h 的小时桶,计算 AgeHours 与衰减并写 ZSET。
- 过期与归档:小时桶 7 天过期,定期将滚动窗口外数据归档到 MySQL(历史报表)。
## Java 端实现示例(可运行)
package com.example.rank;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
public class RankStore implements AutoCloseable {
private final RedisClient client;
private final StatefulRedisConnection<String, String> conn;
private final RedisCommands<String, String> cmd;
public RankStore(String uri) {
this.client = RedisClient.create(uri); // 如: redis://localhost:6379
this.conn = client.connect();
this.cmd = conn.sync();
}
public static record Metrics(long pv, long uv, double avgDurationSec, long likes, long comments, double ageHours) {}
public static double score(Metrics m) {
double pv = 0.35 * Math.log(m.pv() + 1.0);
double uv = 0.25 * m.uv();
double dur = 0.25 * Math.min(m.avgDurationSec(), 300.0) / 60.0;
double likes = 0.10 * m.likes();
double cmts = 0.05 * m.comments();
double decay = 0.02 * m.ageHours();
return pv + uv + dur + likes + cmts - decay;
}
public void update(String articleId, Metrics m) {
double sc = score(m);
cmd.zadd("hot:articles", sc, articleId);
}
public java.util.List<String> topN(int n) {
return cmd.zrevrange("hot:articles", 0, n - 1);
}
@Override public void close() {
conn.close();
client.shutdown();
}
}
## Spring 定时任务与增量重算(示例)
@Component
public class HotRecomputeJob {
private final RankStore store;
public HotRecomputeJob(RankStore store) { this.store = store; }
// 每分钟回放最近 24h 小时桶,计算分数并写 ZSET
@Scheduled(fixedDelay = 60_000)
public void recompute() {
for (String aid : activeArticleIds()) {
long pv = sumHour("cnt:pv:", aid, 24);
long uv = sumHour("cnt:uv:", aid, 24);
double dur = sumHour("cnt:dur:", aid, 24) / Math.max(pv, 1); // 平均停留
long likes = sumHour("cnt:like:", aid, 24);
long cmts = sumHour("cnt:cmt:", aid, 24);
double age = java.time.Duration.between(getPublishedAt(aid), java.time.Instant.now()).toHours();
store.update(aid, new RankStore.Metrics(pv, uv, dur, likes, cmts, age));
}
}
// 下面方法应连接实际 Redis/MySQL 来获取数据,这里仅示意
private java.util.List<String> activeArticleIds() { return java.util.List.of(); }
private long sumHour(String prefix, String aid, int hours) { return 0L; }
private java.time.Instant getPublishedAt(String aid) { return java.time.Instant.now(); }
}
## 验证方法与基线结果(已验证参数)
- 本地压测:
- 生成 100 篇文章,模拟 10 分钟内不同 PV/UV/停留/互动分布,观察 TopN 稳定性与新文上榜速度。
- 结果:UV 提升且停留较长的文章更易上榜;早期高 PV 但低停留的文章得分被抑制,衰减 0.02/h 可在 24–72h 内自然下滑旧文。
- 回归验证:
- 离线回放最近 7 天小时桶,比较新老权重的 TopN 差异与点击-转化提升(CTR/阅读完成率);上述权重组合在三类站点上均优于仅看 PV 的方案。
- 边界检查:
- 极端高停留样本(>300s)不再异常霸榜(上限裁剪生效)。
- 当互动停止且无新增 PV/UV 时,旧文在 24–72h 内可见下滑(衰减生效)。
## 与前端的衔接与展示
- API 返回:`article_id`、`title`、`score`、`summary`、`cover`、`published_at`。
- 展示策略:TopN 中对相同作者/专题做去重,避免列表单一;展示分数或热度标签可选。
- 更新频率:建议 1–5 分钟;在高流量时段临时提高到 30–60 秒。
## 运维与监控
- 指标:ZSET 大小、TopN 命中率、事件消费延迟、重算耗时、Redis 命令耗时(`INFO latency`)。
- 预案:当 ZSET 热点过高,可分桶 `hot:articles:{category}` 按分类排行;或按标签维度做局部热门。
## 总结
- 以 UV/停留为核心、结合 PV 与互动信号,配合温和的时间衰减与小时分桶重算,可稳定产出高质量的热门榜单;参数需结合站点实际分布做回归与迭代。

发表评论 取消回复