ThinkPHP 8多级缓存架构实战:让商城商品页飞起来

2026-06-17 0 506

前阵子接手了一个有点年头的商城项目,首页和商品详情页慢得让人抓狂。每次刷新都要查数据库、拼模板、渲染,首页还好说,详情页高峰期接口响应时间经常超过一秒。更难受的是,一到搞活动,Redis装不下的热数据加上略大的MySQL慢查询,服务直接开始掉头发。

硬着头皮上缓存刻不容缓。但简单往Redis里一塞又担心缓存更新不及时,全部页面静态化又缺乏足够的灵活性。最后敲定了一套TP8自带能力结合多级缓存的方案:动态数据用标签缓存,片段用查询缓存,整体再套一层页面静态缓存,并且利用模型事件自动失效。优化后同台机器的页面平均加载时间降到50ms以下,压力测试时照样稳如老狗。这里就把搭建流程和踩过的坑完整复盘出来。

环境准备与缓存驱动配置

首先确认你的ThinkPHP版本是8.x。项目根目录下的.env文件里加入Redis配置,默认的缓存驱动也切换到Redis:

CACHE_DRIVER = redis
REDIS_HOST = 127.0.0.1
REDIS_PORT = 6379
REDIS_PASSWORD = 
REDIS_SELECT = 0

接着在config/cache.php中确保stores里redis配置已关联到上述环境变量。如果还想使用文件作为二级缓存,可以保留file驱动。

简单验证一下:

use thinkfacadeCache;
Cache::set('test', 'Hello Cache', 60);
echo Cache::get('test');

成功输出即表示Redis缓存连接无碍。

第一层:模型查询缓存与标签

商品详情页通常会拉取商品基础信息、SKU列表、商品相册等。在模型中开启查询缓存,并打上标签方便批量失效:

namespace appcommonmodel;

use thinkModel;

class Goods extends Model
{
    protected $cacheTag = 'goods';  // 默认标签

    // 获取基础信息(自动缓存)
    public function getInfoById(int $id)
    {
        return $this->cache(true, 3600)->find($id);
    }

    // 获取相册列表
    public function getAlbums(int $goodsId): array
    {
        return $this->hasMany(GoodsAlbum::class, 'goods_id', 'id')
            ->where('goods_id', $goodsId)
            ->cache(true, 3600, 'goods_album')
            ->select()
            ->toArray();
    }
}

关键在于cache(true, 3600, 'goods_album'),它会把本次查询的结果按对象缓存起来。第三个参数是标签,所有带有相同标签的缓存可以被一次性清理。当商品数据更新时,我们在控制器或事件里操作:

use thinkfacadeCache;
// 更新商品后,清除该商品相关的所有缓存
Cache::tag('goods')->clear();
Cache::tag('goods_album')->clear();

由于清除操作仅针对相关标签,不会影响其他缓存,做到了精准失效。

第二层:控制器动态缓存 + 标签

查询缓存只缓存了模型数据,拼装视图的过程依然耗费CPU。可以将整个渲染结果也缓存起来。在控制器中封装一个方法:

namespace appindexcontroller;

use appcommonmodelGoods;
use thinkfacadeCache;
use thinkfacadeView;

class Product
{
    public function detail(int $id)
    {
        $cacheKey = 'product_detail_' . $id;
        $html = Cache::get($cacheKey);

        if (!$html) {
            // 模型获取数据
            $goodsModel = new Goods();
            $info = $goodsModel->getInfoById($id);
            if (!$info) {
                abort(404, '商品不存在');
            }
            $albums = $goodsModel->getAlbums($id);

            // 渲染模板
            $html = View::fetch('detail', [
                'goods' => $info->toArray(),
                'albums' => $albums
            ]);

            // 写入缓存,并指定标签
            Cache::tag(['goods_html', 'goods_' . $id])->set($cacheKey, $html, 7200);
        }

        return $html;
    }
}

这样每个商品的详情页HTML会被缓存两小时。当后台修改了商品信息,我们可以调用Cache::tag('goods_html')->clear();,或者精确清除单个商品缓存Cache::tag('goods_' . $id)->clear();。大部分请求直接命中缓存,无需连接数据库和渲染模板。

第三层:页面静态化 + 条件更新

如果还想进一步降低服务器压力,可以将纯HTML静态文件写入public/static目录,由Nginx直接读取。TP8可以配合ob_startfile_put_contents

// 生成静态文件
$staticPath = app()->getRootPath() . 'public/static/goods/' . $id . '.html';
if (!is_dir(dirname($staticPath))) {
    mkdir(dirname($staticPath), 0755, true);
}
file_put_contents($staticPath, $html);

// 访问时先判断静态文件是否存在
if (file_exists($staticPath)) {
    return file_get_contents($staticPath);
}

可以结合TP8的事件系统,在商品更新后自动删除对应的静态文件:

namespace appcommonevent;

use thinkfacadeFilesystem;

class GoodsUpdate
{
    public function handle($event)
    {
        $goodsId = $event->goodsId;
        $staticPath = app()->getRootPath() . 'public/static/goods/' . $goodsId . '.html';
        if (file_exists($staticPath)) {
            unlink($staticPath);
        }
        // 同时清除动态缓存
        Cache::tag(['goods_html', 'goods_' . $goodsId])->clear();
    }
}

这样,当后台管理员编辑商品时,静态页面被删除,下一次用户访问会自动重建最新的HTML并再次缓存。

多级缓存组合投产

把上面三层串起来,请求到达时依次检查:静态文件 → Redis动态HTML缓存 → 查询缓存并拼装HTML。整个执行流如下:

public function detail(int $id)
{
    // 1. 静态文件检查
    $staticFile = app()->getRootPath() . 'public/static/goods/' . $id . '.html';
    if (file_exists($staticFile)) {
        return file_get_contents($staticFile);
    }

    // 2. Redis动态HTML缓存
    $cacheKey = 'product_detail_' . $id;
    $html = Cache::get($cacheKey);
    if ($html) {
        return $html;
    }

    // 3. 数据库查询并渲染
    $goodsModel = new Goods();
    $info = $goodsModel->getInfoById($id);
    // ...渲染模板得到$html...

    // 4. 存储到Redis,可选的静态文件写入
    Cache::tag(['goods_html', 'goods_' . $id])->set($cacheKey, $html, 7200);
    file_put_contents($staticFile, $html);
    return $html;
}

这个流程最大化了性能,同时通过标签保证数据一致性。此外,如果在高并发下担心缓存雪崩,可以在缓存过期时间基础上增加随机偏移:

$expire = 7200 + rand(0, 600); // 7200~7800秒之间

高级技巧:利用opcache加速缓存读取

如果你的静态文件数量可控,将它们用PHP的opcache缓存入内存也是一种极速方案。可以把生成的HTML用include方式加载:

// 将HTML静态文件保存为PHP文件(带.php扩展),内容就是
$phpCacheFile = runtime_path() . 'goods/' . $id . '.php';
file_put_contents($phpCacheFile, '<?php return ' . var_export($html, true) . ';');

// 读取
$html = include $phpCacheFile;

这种方式利用了OPcache对PHP文件的缓存,读取速度甚至比Redis更快,适合热数据极其集中的场景。

缓存键命名规范与维护

项目缓存键一多,管理就容易混乱。建议遵循以下规则:

  • 使用冒号分隔命名空间,如project:module:function:id
  • 标签用复数名称,如goodsgoods_album
  • 在开发文档中罗列所有缓存键和标签,便于团队维护。
  • 通过TP8的Cache::tag()方法统一清理,禁止直接使用clear()刷全库。

总结

ThinkPHP 8本身的缓存组件设计得很到位,只需稍加组合就能构建出适合中小型项目的多级缓存迭代。从模型查询缓存到标签化动态HTML,再到可选的静态页面,每一步都能无缝衔接。在实际的商城改造中,我们几乎没修改业务逻辑,只是在模型和控制器里合理加了几行缓存代码,访问速度就提升了十倍有余。

如果你的项目也在为数据库查询和页面渲染耗时困扰,不妨从最热的接口开始,一层层加上缓存。先做模型查询缓存,再补控制器缓存,最后叠上静态文件。相信等你感受到那种“刷新页面秒开”的快感,会觉得折腾这些配置完全值得。

ThinkPHP 8多级缓存架构实战:让商城商品页飞起来
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 thinkphp ThinkPHP 8多级缓存架构实战:让商城商品页飞起来 https://www.taomawang.com/server/thinkphp/2162.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务