热榜是内容平台最贵的流量入口:它把「用户注意力」重新分配给「正在变热的内容」,既要快、又要公平、还要防刷。热度算法是工程与博弈的结合体——公式决定谁能上榜,衰减决定谁该下榜,去重决定榜单是否被同质内容淹没。本文从公式到架构讲透热榜:热度公式设计、时间衰减模型、Redis ZSet 实时排行、刷榜识别与内容聚簇、个性化分层、冷启动长尾、三层架构、监控调参与灰度回放。
目录
- 1. 热榜的本质:注意力与新鲜度的博弈
- 2. 热度公式设计:加权、衰减与归一化
- 3. 时间衰减模型:指数、半衰期与滑动窗口
- 4. 实时排行实现:Redis ZSet 与分片
- 5. 反作弊与去重:刷榜识别与内容聚簇
- 6. 个性化热榜:分层与多样性约束
- 7. 冷启动与长尾:新内容的机会分配
- 8. 工程架构:离线、近线与在线三层
- 9. 监控与调参:指标、回放与灰度
- 10. 速查表与一句话记忆
- 延伸阅读
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 |
| 为什么用对数 | 抑制爆款碾压长尾,浏览量噪音大 |
| 衰减怎么选 | 短视频 6 |
| 实时排行用什么 | Redis ZSet + 候选池 + 快照缓存 |
| 怎么防刷榜 | 账号/行为/关系/设备四层信号 + 图识别 |
| 去重怎么做 | 特征 + 相似度 + 在线聚类 + 榜单配额 |
| 个性化怎么插值 | α 全局分 + (1-α) 兴趣分,α 动态 |
| 新内容怎么扶持 | UCB 置信度加成 + 独立冷启池 |
| 架构怎么分层 | 离线慢变量 / 近线分数 / 在线编排 |
| 调参怎么验证 | 离线回放 + 影子模式 + 小流量灰度 |
一句话记忆:热榜 = 加权互动分 / 时间衰减(指数或半衰期)+ 对数压缩 + 基线归一(防大 V 霸榜)+ Redis ZSet 候选池与快照(低延迟一致读)+ 四层反作弊与内容聚簇(防刷防刷屏)+ α 个性化插值 + MMR 打散(多样性)+ UCB 冷启加成(长尾机会)+ 离线近线在线三层 + 回放影子灰度(可验证调参)——把注意力公平、快速、真实地再分配。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。