Firebase 托管可以从 Cloud Functions 提供缓存数据吗?

Can Firebase Hosting Serve Cached Data from Cloud Functions?

假设我在 firestore 中有一个包含 100,000 条内容的数据库。每条内容每月更改一次的可能性不大。我的单页应用程序使用 firebase 托管,使用一个函数从 firestore 检索内容,将其呈现给 HTML,然后 return 呈现给浏览器。

这是对我的 firestore 配额的浪费,如果我经常为不是 动态 的内容执行此过程,这会开始增加很多钱。

如何将该内容保存为静态 .com/path/path/contentpage.html 文件,以便在请求确切路径和查询时提供服务,而不是每次都经过 firestore / functions 过程?

我的目标是提高速度,减少不必要的 firestore 请求,因为每次读取都需要花钱。

谢谢!

当您使用Firebase Hosting on top of Cloud Functions for Firebase, Hosting can act as an edge-cached layer on top of the responses from your HTTPS functions. You can read about that integration in the documentation. In particular, read the section managing cache behavior时:

The main tool you'll use to manage cache is the Cache-Control header. By setting it, you can communicate both to the browser and the CDN how long your content should be cached. In your function, you set Cache-Control like so:

res.set('Cache-Control', 'public, max-age=300, s-maxage=600');

除了设置 Cache-Control header 之外,您还可以利用 Cloud Functions 实例中全局变量设置的优势,请参阅 Cloud Functions Tips 他们提到“使用全局变量在以后的调用中重用objects”。

有了这个想法,我就可以使用 npm package treasury(是的,我确实开发了这个,但这与它恰好在 Cloud Functions 中使用这个用例这一事实无关——我也使用它在生产中,如果它让你感觉更好)。

只要变量 treasury 存在,它就会使用 Treasury 的 "Memory" 适配器来存储数据的示例,它与 Cloud Function 实例一起存在和消亡:

const functions = require('firebase-functions');
const tauist = require('tauist');
const Treasury = require('treasury');
const cors = require('cors')({
    origin: true
});

// note that memory adapter uses MS not S for ttl
const treasury = new Treasury({
    ttl: tauist.ms.thirtyMinutes
});

function getAnimalData({name}) {
    // replace this with your actual Promise code doing the "real work" that you want to cache
    return new Promise();
}

exports.animal = functions.https.onRequest((req, res) => {
    if (req.method !== 'GET') {
        return res.status(403).send('Forbidden');
    }

    // Enable CORS using the `cors` express middleware.
    return cors(req, res, () => {
        // Reading ticker symbol from URL query parameter.
        let name = (req.query.name || '').trim();
        console.log('animal name:', name);

        if (!name) {
            return res.status(400).send('Bad Request');
        }

        res.set('Cache-Control', `public, max-age=${tauist.s.thirtyMinutes}, s-maxage=${tauist.s.thirtyMinutes}`);
        treasury.invest(getAnimalData, {name})
            .then((data) => res.status(200).send(data))
            .catch((error) => {
                console.error('error caught:', error);
                res.status(500).send('Internal Server Error');
            });

    });
});

如果您需要缓存一些非常昂贵的结果或者可能来自另一个 API,您可以将 fireStore 用作 "provider"。我的操作从大约 30 秒减少到大约 1 秒。您将减少出站配额的使用

这是我在服务中创建方法的示例代码:

             ...
    import {db} from "../database";
    const tauist = require('tauist');

    ... 

    /**
     * @param name must be uniq
     * @param fallback
     * @param parameters
     * @param namespace
     * @param ttl milliseconds
     */
    static async getCache(name: string, fallback: (...a: any) => Promise<any>, parameters: any[], ttl: number = tauist.s.thirtyDays, namespace: string = 'api') {
        let response = {};
        const parms = parameters || [];
        const now = new Date();
        const collectionRef = db.collection(`cache-${namespace}-${parms.join('-')}`);
        const documentRef = collectionRef.doc(name);
        try {
            const cacheSnapshot = await documentRef.get();
            if (cacheSnapshot.exists) {
                const cache = cacheSnapshot.data();
                if (new Date(new Date((cache as Cache).created).getTime() + ttl) < now) {
                    throw new Error();
                } else {
                    response = (cache as Cache).data;
                }
                // Using cache
            } else {
                throw new Error();
            }
        } catch (e) {
            // Cache created
            response = await fallback(...parms);
            await documentRef.set({
                data: response,
                created: new Date(new Date().getTime() + ttl)
            })
        }

        return response;
    }

我如何使用它:

import {INTERNAL_SERVER_ERROR, OK, UNPROCESSABLE_ENTITY} from "http-status-codes";
...
Service.getCache('getHugeData',  AWSService.getHugeData, [name, simple])
                .then((data: any) => {
                    res.status(OK).json({
                        data
                    });
                })
                .catch((error: any) => {
                    console.log(error);
                    res.status(UNPROCESSABLE_ENTITY).json(error);
                });

这是 JAMstack 风格架构的一个很好的用例,您可以在其中预呈现页面,并在构建时加载所需的数据。您可以将预呈现的静态站点构建器视为另一种形式的缓存。在这种情况下,考虑到您期望每月更新一次,在 运行 时间服务器呈现您的页面根本没有多大意义。

当您的数据发生变化时,只需重建您的站点。 Gastby(在反应世界中)为此设置,并有许多不同的数据源插入构建器,包括 firestore 的插件。

Netlify 是一个静态站点主机,它有网络书来触发重建。您可以使用由事件触发的 firebase 云函数到各种 firestore collections/documents,在 inserts/updates/deletes.

上将 Netlify ping 到 运行 "build"

这不仅更便宜,而且比 运行 宁 运行 时间服务器更简单,并且提供最高的最终用户性能,因为静态页面加载速度最快。