在 Nuxt.js 中使用 Redis 缓存页面

5,188 阅读3分钟

情况说明

众所周知,在 SSR 服务中,渲染阶段是非常消耗渲染服务器资源的。

比如网站的首页,会经常被访问到,如果每一次请求渲染服务器都要执行一遍渲染操作,不仅速度上要慢很多,而且在高并发状态下会出现性能瓶颈。

这时,如果我们在页面第一次渲染完成后,就将这次的渲染结果缓存下来。当再次请求时直接把渲染好的渲染结果返回,就省了渲染、查询等一系列的操作。无疑对程序的性能和可用性上都是一次大的提升。

那么在 Nuxt.js 中,我们该怎么实现这个页面级的缓存呢?

Module

模块是 Nuxt.js 扩展,可以扩展其核心功能并添加无限的集成。

在使用 Nuxt 开发应用程序时,您很快就会发现框架的核心功能还不够。 Nuxt 可以使用配置选项和插件进行扩展,但是在多个项目中维护这些自定义是繁琐、重复和耗时的。 另一方面,开箱即用支持每个项目的需求将使 Nuxt 非常复杂且难以使用。 这就是 Nuxt 提供更高阶模块系统的原因,可以轻松扩展核心。 模块只是在引导 Nuxt 时按顺序调用的函数。 框架在加载之前等待每个模块完成。 如此,模块几乎可以自定义 Nuxt 的任何地方。 我们可以使用功能强大的 Hookable Nuxt.js 系统来完成特定事件的任务。

官网链接:zh.nuxtjs.org/guide/modul…

简而言之,我们可以通过配置一个 Module,来扩展 Nuxt。通过 Nuxt 的 Hook,在渲染时、渲染前改变默认的行为。

Nuxt render 阶段的 Hook: nuxtjs.org/api/interna…

我们可以在 render:before 这个 hook 的 callback 里有 renderer 这个渲染器,那么我们可以试着 重写本次的渲染器

重写渲染器

this.nuxt.hook("render:before", (renderer, options) => {

        // 首先拿到原来的渲染方法renderRoute
        const renderRoute = renderer.renderRoute.bind(renderer);

        // 重写renderer.renderRoute
        renderer.renderRoute = async function(route, context) {

            // isCacheFriendly 用来判断当前的route是否和moduleOptions.matches中配置的一致
            // moduleOptions是nuxt module配置中传过来的参数
            // 没有匹配的情况下,返回原来的renderRoute
            if (!isCacheFriendly(route, moduleOptions.matches)) {
                return renderRoute(route, context);
            }

            // 返回值
            let value;

            try {
                // getAsync 是 取Redis的值
                value = await getAsync(route);
            } catch (err) {
                // 如果出错了,返回原来的renderRoute
                console.log(err);
                return renderRoute(route, context);
            }

            if (!value) {
                // 没有找到值,返回原来的renderRoute
                return renderRoute(route, context);
            } else {
                // 标记命中, 写在context中,在后续hook中可以取到
                context.req.hitCache = true;

                // 从cache中获取,封装成Promise
                console.log("return from cache");

                return new Promise(function(resolve, reject) {
                    // 返回取到的值
                    resolve(deserialize(value));
                });
            }
        };
    });

那么在哪里存这个页面的值呢?

存渲染完成的页面

可以在 render:route 这个 hook 中来做处理。

// add cache hit header
    this.nuxt.hook("render:route", (url, result, context) => {
        // 如果hitCache了,那么就标记一个响应头。
        // 如果没有,那么就缓存一下。
        if (context.req.hitCache) {
            context.res.setHeader("x-page-cache", "hit");
        } else if (isCacheFriendly(url, moduleOptions.matches)) {
            client.set(url, serialize(result), "EX", moduleOptions.expireTime ? moduleOptions.expireTime : 1000);
        }
    });

完整代码

完整代码请见: github.com/tangzhongzh… 使用方法请见 GITHUB README

更多:完整代码还加入了,版本管理,登录状态判断等。甚至还可以将渲染好的页面放到 cdn 中。