PHP项目怎样实现资讯热门排序?

wen PHP项目 48

本文目录导读:

PHP项目怎样实现资讯热门排序?

  1. 核心思路:设计一个“热度分数”公式
  2. 方案一:数据库实时计算(简单,适合小流量)
  3. 方案二:预计算 + 定时任务(推荐用于中等流量)
  4. 方案三:分级 + Redis Zset 缓存(适合高并发、大型网站)
  5. 方案四:算法升级(应对刷分与长尾效应)
  6. 总结建议

在PHP项目中实现资讯热门排序,通常需要综合考量时间衰减用户互动(如阅读、点赞、评论、分享) 等多个维度,而不是单纯依赖某一项指标(比如只看阅读量)。

以下是几种从简单到复杂、从理论到代码实现的完整方案:

核心思路:设计一个“热度分数”公式

热门排序的本质是计算一个 Hot Score(热度分数),然后按此分数降序排列。

最经典的简化公式是借鉴 Hacker NewsReddit 的算法:

  • Hacker News 风格(侧重爆发力,防旧文霸榜): Score = (P - 1) / (T + 2)^G

    • P = 点赞/投票数(或其他互动值)
    • T = 发布时间距离现在的小时数
    • G = 重力因子(1.5~1.8),值越大,老文章衰减越快
  • Reddit 风格(兼顾热度和时效): Score = log10( max(|upvotes - downvotes|, 1) ) + (sign * seconds / 45000)

    核心思想:互动总量的对数 + 时间戳的贡献。

  • 我们常用的简单加权模型(推荐项目初期使用)Hot_Score = A * 阅读量 + B * 点赞数 + C * 评论数 + D * 分享数 + E * 收藏数 - F * 发布时间衰减

    • A, B, C, D, E 是权重(分享比点赞重要,权重高)。
    • F 是时间衰减系数,确保老文章的热度下降越快。

数据库实时计算(简单,适合小流量)

直接在 MySQL 查询时,用 SQL 计算热度分。

数据表设计 (articles) 需要包含文章的元数据和时间戳。

CREATE TABLE articles (
    id INT PRIMARY KEY AUTO_INCREMENT,VARCHAR(255),
    content TEXT,
    created_at DATETIME,
    view_count INT DEFAULT 0,
    like_count INT DEFAULT 0,
    comment_count INT DEFAULT 0,
    share_count INT DEFAULT 0,
    -- 可选:预计算的时间衰减因子字段
    weight DECIMAL(10,4) DEFAULT 0.0 
);

PHP 查询代码(实时计算)

<?php
define('HOUR_WEIGHT', 0.5); // 每过一小时,热度衰减的系数
define('LIKE_WEIGHT', 2.0);
define('COMMENT_WEIGHT', 3.0);
define('VIEW_WEIGHT', 0.1);
define('SHARE_WEIGHT', 5.0);
function getHotNews($db, $limit = 20) {
    // 使用 TIMESTAMPDIFF 计算小时差,实现时间衰减
    $sql = "
        SELECT id, title, 
               ( 
                   (view_count * :view_w) +
                   (like_count * :like_w) +
                   (comment_count * :comment_w) +
                   (share_count * :share_w) 
               ) - 
               (TIMESTAMPDIFF(HOUR, created_at, NOW()) * :hour_w) 
               AS hot_score
        FROM articles
        WHERE status = 1
        ORDER BY hot_score DESC
        LIMIT :limit
    ";
    $stmt = $db->prepare($sql);
    $stmt->bindValue(':view_w', VIEW_WEIGHT, PDO::PARAM_STR);
    $stmt->bindValue(':like_w', LIKE_WEIGHT, PDO::PARAM_STR);
    $stmt->bindValue(':comment_w', COMMENT_WEIGHT, PDO::PARAM_STR);
    $stmt->bindValue(':share_w', SHARE_WEIGHT, PDO::PARAM_STR);
    $stmt->bindValue(':hour_w', HOUR_WEIGHT, PDO::PARAM_STR);
    $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 调用示例
// $hotNews = getHotNews($pdo);
?>

缺点

  • 每次查询都要全表扫描计算 TIMESTAMPDIFF数据量大(>10万)时性能极差
  • 无法处理复杂的时间衰减曲线(如指数衰减)。

预计算 + 定时任务(推荐用于中等流量)

原理:使用 Cron 或 Laravel Task Scheduling,每隔 5~10 分钟计算一次所有文章的热度分,并直接写入数据库的单独字段,查询时只需按该字段排序。

数据表增加字段

ALTER TABLE articles ADD COLUMN hot_score DECIMAL(14,4) DEFAULT 0.0 INDEX;

定时任务脚本 (cron_hot_score.php)

<?php
// 伪代码逻辑
function recalculateAllHotScores($db) {
    $sql = "UPDATE articles 
            SET hot_score = 
                (view_count * 0.1 + 
                 like_count * 2.0 + 
                 comment_count * 3.0 + 
                 share_count * 5.0) 
                - 
                (TIMESTAMPDIFF(HOUR, created_at, NOW()) * 0.5)
            WHERE status = 1";
    // 如果数据量大,建议分批更新,避免锁表
    // 或者使用 MySQL 事件调度器
    $db->exec($sql);
}
// 设置定时任务:每5分钟执行一次
// crontab: */5 * * * * /usr/bin/php /var/www/project/cron_hot_score.php

查询

SELECT id, title, hot_score FROM articles WHERE status = 1 ORDER BY hot_score DESC LIMIT 20;

这个查询因为用的是 INDEX,排序会非常快,即便有百万数据。

优点

  • 查询极快,排序利用索引。
  • 逻辑清晰,易于调整权重。

分级 + Redis Zset 缓存(适合高并发、大型网站)

原理:将“计算”和“存储”完全交给内存数据库 Redis,用于处理高频查询,MySQL 只作为持久化存储。

存储结构

  • 使用 Redis 的 Sorted Set(有序集合)
  • Key: hot_news
  • Member: 文章ID (article:123)
  • Score: 热度分数(浮点数)

PHP 逻辑(用户互动时触发)

<?php
// 当用户阅读文章时,增加热度
function incrementView($redis, $articleId, $weight) {
    $score = $redis->zIncrBy('hot_news', $weight, "article:$articleId");
    return $score;
}
// 当用户点赞/评论时,增加更多
function likeArticle($redis, $articleId) {
    $redis->zIncrBy('hot_news', 2.0, "article:$articleId"); // 点赞权重高
}
// 新文章发布时,先加入,并给予基础热度
function publishArticle($redis, $articleId) {
    $redis->zAdd('hot_news', time(), "article:$articleId");
}

时间衰减处理
Redis 本身不做复杂衰减,我们可以借助定时任务,或者查询时结合文章发布时间

方案 A(推荐):在获取排序时,同步计算衰减(需要 Redis + MySQL 联动)

function getHotNews($redis, $db) {
    // 1. 从 Redis Zset 获取前50个文章ID
    $articleIds = $redis->zRevRange('hot_news', 0, 49);
    // 2. 从 MySQL 批量获取这些文章的时间和其他信息
    $ids = implode(',', array_map(fn($id) => str_replace('article:', '', $id), $articleIds));
    $sql = "SELECT id, created_at FROM articles WHERE id IN ($ids)";
    // ... 执行查询获取文章的发布时间
    // 3. 在 PHP 中进行二次排序,应用时间衰减(分数 = Redis分数 - 小时数 * 0.5)
    // 使用 usort 根据新分数排序
    usort($articles, function($a, $b) {
        // 计算衰减后的分数
        $scoreA = $redisScore - (time() - strtotime($a['created_at'])) / 3600 * 0.5;
        $scoreB = $redisScore - (time() - strtotime($b['created_at'])) / 3600 * 0.5;
        return $scoreB <=> $scoreA;
    });
    return $articles;
}

方案 B(更简单):直接用 Redis 的 Key 时间戳
如果文章发布时间已知,可以直接用 (当前时间戳 - 创建时间戳) / 3600 作为衰减,在上面的 PHP 排序中完成。


算法升级(应对刷分与长尾效应)

如果你的项目有“刷分”风险,或者希望“优秀的老文章”偶尔也能被推荐,可以考虑:

  1. 对数函数:为了降低刷阅读量的影响,可以用 log10(view_count + 1) 来代替原始阅读量,1000次阅读和10000次阅读的差距被缩小。
  2. 威尔逊区间 (Wilson Lower Bound):防止“点赞极少但满星”的文章排到第一,常用于评分排序(如知乎、豆瓣)。
  3. 牛顿冷却定律:更拟真的时间衰减,1 / (1 + e^(-\alpha * (t - t0)))

总结建议

阶段 推荐方案 原因
小项目/轻量级 方案一(数据库实时计算) 简单,无需额外组件,数据量<10万可用。
中等规模项目 方案二(定时任务 + 预存字段) 性能好,容易维护,MySQL 足以应对。
高并发/大型网站 方案三(Redis Zset) 查询性能极优,可以实时更新热度,但需要维护 Redis。
算法要求高/防刷 方案二/三 + 对数/时间衰减优化 引入 log10 和指数衰减。

最后的关键建议不要过度设计,先从最简单的加权公式开始,上线后观察数据,再根据用户反馈和业务需求(今天发布的新文” vs “一周前的优质文”的占比),逐步调整权重和衰减曲线。

抱歉,评论功能暂时关闭!