热榜与趋势计算:热度算法、时间衰减、实时排行与去重

系统拆解微型博客热榜与趋势计算:从注意力与新鲜度的博弈出发,给出热度公式的加权、衰减与归一化设计,详解指数衰减、半衰期与滑动窗口三类时间模型,落地 Redis ZSet 分片实时排行,并覆盖刷榜识别、内容聚簇去重、个性化分层、冷启动长尾、离线近线在线三层架构以及监控调参与灰度回放。

热榜是内容平台最贵的流量入口:它把「用户注意力」重新分配给「正在变热的内容」,既要快、又要公平、还要防刷。热度算法是工程与博弈的结合体——公式决定谁能上榜,衰减决定谁该下榜,去重决定榜单是否被同质内容淹没。本文从公式到架构讲透热榜:热度公式设计、时间衰减模型、Redis ZSet 实时排行、刷榜识别与内容聚簇、个性化分层、冷启动长尾、三层架构、监控调参与灰度回放。

前置:排序信号与质量分、实时链路与流处理、行为埋点与指标口径。

目录

1. 热榜的本质:注意力与新鲜度的博弈

热榜要同时满足三组互相拉扯的目标,理解它们才能设计出稳定的公式。

热榜的三组矛盾:
□ 时效性 vs 公平性:新内容要机会,老内容不能长期霸榜
□ 总量   vs 质量  :点赞总量大 ≠ 值得推荐(标题党、互赞)
□ 全局   vs 个性  :全站热榜曝光高但转化低,个性化转化高但无「共识感」

热榜的价值在于「共识」:它回答「此刻大家都在看什么」。因此它不能完全个性化,否则失去社交货币属性;但也不能完全全局,否则对垂类用户无意义。工程上的常见做法是「全局榜 + 垂类榜 + 个性化榜」三层并存。

榜单类型排序口径刷新频率主要用途
全站热榜全量互动加权1~5 分钟发现、破圈
垂类热榜分品类加权5~15 分钟兴趣深耕
关注热榜关系链加权实时社交在场感
长尾榜低曝光高互动1 小时冷启动扶持

2. 热度公式设计:加权、衰减与归一化

Hacker News 式公式是最经典的起点,它的核心思想是「用时间做分母,把总量拉回同一尺度」。

score = (w_like * likes + w_comment * comments + w_repost * reposts + w_view * views)
        / (age_hours + 2) ^ gravity

其中 gravity 常取 1.5 ~ 1.8

三类设计要点:

  • 互动权重差异化:评论 > 转发 > 点赞 > 浏览,因为成本越高越难刷。浏览量噪音大,通常取对数。
  • 对数压缩:log(1 + views) 抑制爆款对长尾的碾压,避免「一条视频吃掉整个榜」。
  • 归一化:按作者粉丝数或历史均值做基线归一,防止大 V 天然霸榜。
baseline_norm = raw_score / (author_avg_score_7d + epsilon)
final_score   = baseline_norm * quality_factor

quality_factor 由完读率、停留时长、举报率等负向信号构成。举报率超过阈值时应直接扣分甚至熔断,而不是线性衰减。

3. 时间衰减模型:指数、半衰期与滑动窗口

衰减决定了「热度的记忆长度」。三类模型各有适用场景。

模型公式记忆长度适用
指数衰减s * e^(-λt)平滑可调在线实时打分
半衰期s * 0.5^(t/h)直观可解释运营调参
滑动窗口窗口内求和硬截断榜单合规与可复现

指数衰减的工程实现通常离散化为「桶」:把时间切成 5 分钟一个桶,只对最近 N 个桶保留明细,桶外只留聚合值。这样打分时无需回扫全量事件。

bucket_score(now) = Σ  Σ  event_weight * 0.5^((now - bucket_ts)/half_life)
                  b∈buckets  e∈bucket_b

半衰期的选择要和内容消费节奏匹配:短视频 612 小时,图文 2448 小时,深度长文 72 小时以上。半衰期过短会导致榜单抖动(内容还没积累就掉榜),过长则变成「僵尸榜」。

滑动窗口模型的可复现性最好:给定窗口边界,任何人重算都能得到同一结果,因此适合做「日榜」「周榜」这类需要审计与申诉的场景。它的问题是边界效应——窗口切换瞬间分数会阶跃,需要在切换时做平滑或双窗口过渡。

双窗口过渡:
score = (1 - p) * window_prev + p * window_curr
p 在切换后的 M 分钟内从 0 线性升到 1

三类模型可以组合:在线用指数衰减保证平滑,结算用滑动窗口保证可复现,运营看板用半衰期保证可解释。

4. 实时排行实现:Redis ZSet 与分片

Redis 的 ZSet 是热榜的天然数据结构:ZADD 写分,ZREVRANGE 取 Top N,ZREVRANK 查排名,复杂度都是 O(log N)。

ZADD trending:global 1732.55 "note:10086"
ZINCRBY trending:global 3.0 "note:10086"
ZREVRANGE trending:global 0 49 WITHSCORES

生产环境要注意三点:

  • 分片:单 ZSet 超过千万成员会阻塞,按内容 ID 哈希或按垂类分片。
  • 写放大:每次互动都 ZINCRBY 会打爆 Redis,应先在应用层聚合(如 1 秒窗口合并同内容多次互动)再写。
  • 冷热分离:榜单只保留候选池(如 Top 5000),长尾内容进离线存储,避免 ZSet 无限膨胀。
写路径:
互动事件 → Kafka → 窗口聚合(1s) → 分数重算 → ZADD 候选池 → 榜单快照

读路径:
ZREVRANGE 候选池 → 反作弊过滤 → 多样性打散 → 榜单缓存(CDN/本地) → 客户端

榜单读多写少,最终结果应生成不可变快照并缓存,客户端只读快照,保证同一时刻所有用户看到的榜单一致。

5. 反作弊与去重:刷榜识别与内容聚簇

刷榜是热榜最大的敌人,识别手段分四层。

层次信号处置
账号新号、无头像、集中注册降权或不计分
行为秒赞、无停留、机械间隔行为不计分
关系互赞团伙、闭合子图图算法识别并扣分
设备同设备多账号、模拟器设备维度去重

关系图谱识别互赞团伙可用「密集子图」思路:在「用户-内容」二分图上,若某群用户互动高度重叠,则该群体贡献的分数应整体折减。

内容去重同样关键——同一事件会被大量账号复述,若不去重,热榜会被单一话题占满。做法是内容聚簇:

聚簇流程:
1. 抽取特征:SimHash / 关键词 / embedding
2. 相似度计算:汉明距离或余弦相似度
3. 聚类:在线增量聚类,新内容归入最近簇
4. 榜单约束:同一簇最多占 Top N 的 K 个位置

聚簇粒度要随热度动态调整:话题越热,允许的同簇位次越多(因为确实是大事),但要设上限避免刷屏。

6. 个性化热榜:分层与多样性约束

纯全局热榜对垂类用户价值有限,个性化热榜在「全局共识」与「个人兴趣」之间插值。

final = α * global_score + (1 - α) * personal_affinity
α 由用户活跃度与兴趣集中度动态决定

α 的调法:新用户或兴趣分散的用户 α 偏大(多看共识内容),老用户或兴趣明确的用户 α 偏小(多看兴趣内容)。

多样性约束用打散算法实现,常用 MMR(最大边际相关):

MMR 迭代选榜:
while 榜单未满:
    对每个候选 c:
        mmr(c) = λ * score(c) - (1-λ) * max_sim(c, 已选集合)
    选 mmr 最大者入榜

约束维度包括:作者去重(同一作者最多 1~2 条)、品类配额(垂类最低占比)、时效混合(新老内容搭配)。这些约束在候选池生成后、快照生成前统一施加。

7. 冷启动与长尾:新内容的机会分配

热榜若只按总量排序,新内容永远上不去,平台会失去活力。冷启动扶持的核心是置信度加成的探索机制。

ucb_score = raw_score + c * sqrt(ln(N) / n_exposure)

这是 UCB 的思想:曝光次数 n_exposure 越少,不确定性越大,加成越高,从而给新内容「试错曝光」。曝光后若互动率低于基线,加成迅速衰减,避免资源浪费。

阶段曝光策略退出条件
冷启小流量试探 + UCB 加成达到最小样本量
成长按互动率放大曝光进入候选池 Top
成熟纯分数竞争衰减出榜
长尾长尾榜单独扶持按周复盘

冷启动池要与主榜隔离:新内容先在独立池验证,达标后再进主榜候选,防止未验证内容污染主榜体验。

8. 工程架构:离线、近线与在线三层

热榜是典型的 Lambda 架构场景,三层各司其职。

离线层(T+1 / 小时级):
  全量日志 → 数仓 → 重算历史分数 → 产出基线、作者权重、作弊名单

近线层(分钟级):
  Kafka → Flink 窗口聚合 → 桶化分数 → 写入候选池

在线层(秒级 / 请求级):
  候选池读取 → 过滤 → 打散 → 快照 → 缓存 → 下发

关键设计:

  • 离线产出「慢变量」:作者权重、作弊名单、内容质量分,这类信号计算重、变化慢,放离线最划算。
  • 近线产出「分数」:实时互动聚合成分数,写入候选池。
  • 在线只做「读与编排」:读候选池、施加过滤与打散、生成快照,保证低延迟。
数据一致性:在线分数与离线重算结果定期对账,
差异超过阈值触发告警,避免两套分数长期漂移。

9. 监控与调参:指标、回放与灰度

热榜调参不能凭感觉,要靠指标闭环。

指标含义健康区间示例
榜单更新延迟事件到上榜耗时P99 < 60s
榜单覆盖率被曝光过的内容占比> 60%
新内容占比Top50 中 24h 内新内容20% ~ 40%
同作者重复率Top50 同作者占比< 8%
作弊拦截率被扣分事件占比观察趋势

**回放(replay)**是调参的利器:把历史某天的完整事件流重新灌入打分逻辑,对比不同参数下的榜单差异,评估「新参数会让谁上榜、谁掉榜」。这比线上灰度更快、更安全。

灰度发布流程:

1. 离线回放:验证参数合理性与极端情况
2. 影子模式:新老公式并行计算,只记录不下发
3. 小流量灰度:1% → 5% → 20%,观察互动率与举报率
4. 全量:保留开关,异常可秒级回滚

工程要点:热榜的本质是「注意力再分配」,公式决定分配规则,衰减决定记忆长度,去重决定公平性,反作弊决定真实性。工程上必须把「慢变量(作者权重/作弊名单)离线算、快分数(实时互动)近线算、编排(过滤/打散/快照)在线做」三层拆开,并用回放 + 影子 + 灰度把每次调参都变成可验证的实验,而不是一次冒险。

10. 速查表与一句话记忆

问题一句话答案
热度公式怎么写加权互动 / (时间 + 常数)^gravity
为什么用对数抑制爆款碾压长尾,浏览量噪音大
衰减怎么选短视频 612h、图文 2448h、长文 72h+
实时排行用什么Redis ZSet + 候选池 + 快照缓存
怎么防刷榜账号/行为/关系/设备四层信号 + 图识别
去重怎么做特征 + 相似度 + 在线聚类 + 榜单配额
个性化怎么插值α 全局分 + (1-α) 兴趣分,α 动态
新内容怎么扶持UCB 置信度加成 + 独立冷启池
架构怎么分层离线慢变量 / 近线分数 / 在线编排
调参怎么验证离线回放 + 影子模式 + 小流量灰度

一句话记忆:热榜 = 加权互动分 / 时间衰减(指数或半衰期)+ 对数压缩 + 基线归一(防大 V 霸榜)+ Redis ZSet 候选池与快照(低延迟一致读)+ 四层反作弊与内容聚簇(防刷防刷屏)+ α 个性化插值 + MMR 打散(多样性)+ UCB 冷启加成(长尾机会)+ 离线近线在线三层 + 回放影子灰度(可验证调参)——把注意力公平、快速、真实地再分配。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「miniblog」更多文章

  1. 微型博客的可观测性与 SRE 实践:SLO、告警、容量与故障演练
  2. 国际化与全球化运营架构:文案、时区、多区域部署与合规
  3. API 设计与 GraphQL/BFF 聚合层:Schema 设计、N+1、聚合与缓存