## 适用范围与版本

  • 适用:内容站点/博客/资讯类服务的「热门文章」模块。
  • 基线版本:Go 1.21+,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/停留时长与互动事件,统一写入 Kafka/队列或直接记 Redis 计数。
  • 防作弊:
  • 端侧去抖:滚动/切页不重复上报停留时长;只在可见状态下计时(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(历史报表)。

## Go 端实现示例(可运行)

package rank

import (
    "context"
    "math"
    "time"
    
    "github.com/redis/go-redis/v9"
)

type Metrics struct {
    PV          int64
    UV          int64
    AvgDuration float64 // 单位:秒
    Likes       int64
    Comments    int64
    AgeHours    float64
}

func Score(m Metrics) float64 {
    pv := 0.35 * math.Log(float64(m.PV)+1)
    uv := 0.25 * float64(m.UV)
    dur := 0.25 * math.Min(m.AvgDuration, 300) / 60.0
    likes := 0.10 * float64(m.Likes)
    cmts := 0.05 * float64(m.Comments)
    decay := 0.02 * m.AgeHours
    return pv + uv + dur + likes + cmts - decay
}

type Store struct {
    rdb *redis.Client
}

func NewStore(addr string) *Store {
    return &Store{rdb: redis.NewClient(&redis.Options{Addr: addr})}
}

func (s *Store) Update(ctx context.Context, articleID string, m Metrics) error {
    sc := Score(m)
    return s.rdb.ZAdd(ctx, "hot:articles", redis.Z{Score: sc, Member: articleID}).Err()
}

func (s *Store) TopN(ctx context.Context, n int64) ([]string, error) {
    return s.rdb.ZRevRange(ctx, "hot:articles", 0, n-1).Result()
}

// 计算 AgeHours 示例
func AgeHours(publishedAt time.Time, now time.Time) float64 {
    return now.Sub(publishedAt).Hours()
}

## 定时任务与增量重算(伪代码)

// 每分钟回放最近 24h 小时桶,计算分数并写 ZSET
for _, aid := range activeArticleIDs() {
    pv := sumHour("cnt:pv:", aid, 24)
    uv := sumHour("cnt:uv:", aid, 24)
    dur := sumHour("cnt:dur:", aid, 24) / max(float64(pv), 1) // 平均停留
    likes := sumHour("cnt:like:", aid, 24)
    cmts := sumHour("cnt:cmt:", aid, 24)
    age := AgeHours(getPublishedAt(aid), time.Now())
    _ = store.Update(ctx, aid, Metrics{PV: pv, UV: uv, AvgDuration: dur, Likes: likes, Comments: cmts, AgeHours: age})
}

## 验证方法与基线结果

  • 本地压测:
  • 生成 100 篇文章,模拟 10 分钟内不同 PV/UV/停留/互动分布,观察 TopN 稳定性与新文上榜速度。
  • 期望:在 UV 提升且停留较长的文章更易上榜;早期高 PV 但低停留的文章得分会被抑制。
  • 回归验证:
  • 离线回放最近 7 天小时桶,比较新老权重的 TopN 差异与点击-转化提升(CTR/阅读完成率)。
  • 边界检查:
  • 极端高停留样本(>300s)不应导致异常霸榜(上限裁剪生效)。
  • 无新增互动的旧文在 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 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部