## 适用范围与版本

  • 适用:内容站点/博客/资讯类服务的「热门文章」模块。
  • 基线版本: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 与互动信号,配合温和的时间衰减与小时分桶重算,可稳定产出高质量的热门榜单;参数需结合站点实际分布做回归与迭代。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部