首页 > 解决方案 > Firebase 功能:如何维护“应用程序全局”API 客户端?

问题描述

如何实现跨云函数实例函数调用共享的“应用程序范围”全局变量?我想创建一个真正的“全局”对象,在我所有函数的生命周期中只初始化一次。

语境:

我的应用程序的整个后端是 Firestore + Firebase Cloud Functions。也就是说,我混合使用了后台(Firestore)触发器和 HTTP 函数来实现后端逻辑。此外,我依靠第 3 方位置服务来不断收听来自传感器的位置更新。我只想要一个订阅这些更新的客户端实例。

问题是 Firebase/Google Cloud Functions 是无状态的,这意味着函数实例不共享内存/对象/状态。如果我调用 functionA、functionB、functionC,将至少创建 3 个locationService客户端实例,每个实例分别侦听 3rd 方服务,因此我们最终会重复调用位置 API 回调。

示例代码:

// index.js
const functions = require("firebase-functions");

exports.locationService = require('./location_service');

this.locationService.initClient();

// define callable/HTTP functions & Firestore triggers
...

// location_service.js
var tracker = require("third-party-tracker-js");

const self = (module.exports = {
    initClient: function () {
        tracker.initialize('apiKey')
        .then((client)=>{
            client.setCallback(async function(payload) {
                console.log("received location update: ", payload)
                // process the payload ...
                // with multiple function instances running at once, we receive as many callbacks for each location update
            })

            client.subscribeProject()
            .then((subscription)=>{
                subscription.subscribe()
                .then((subscribeMsg)=>{
                    console.log("subscribed to project with message: ", subscribeMsg); // success
                });
                // subscription.unsubscribe(); // ??? at what point should we unsubscribe?
            })
            .catch((err)=>{
                throw(err)
            })
        })
        .catch((err)=>{
            throw(err)
        })
    },

});

我意识到我正在尝试做的事情大致相当于在单进程环境中实现一个守护进程,而且看起来像 Firebase/Google Cloud Functions 这样的无服务器环境并不是为了支持这种需求而设计的,因为每个实例都作为自己的实例运行过程。但我很想听听任何相反的想法和可能的解决方法。

另一个想法...

受这篇相关的 SO 帖子关于无状态函数的官方 GCF 文档的启发,我考虑使用 Firestore 来持久化跟踪器值,从而允许我们有条件地初始化 API 客户端。大致是这样的:

// read value from db; only initialize the client if there's no valid subscription
let locSubscriberActive = await getSubscribeStatusFromDb();

if (!locSubscriberActive) {
   this.locationService.initClient();
}

// in `location_service.js`, do setSubscribeStatusToDb(); // set flag to true when we call subscribe(). reset when we get terminated

面临的问题:我在什么时候取消/重置该值?直观地说,我会在初始化客户端的函数实例被回收/杀死的那一刻这样做。但是,似乎无法知道Firebase Cloud Function 实例何时终止我到处搜索,但找不到有关如何检测此类事件的文档...

标签: node.jsfirebasegoogle-cloud-firestoregoogle-cloud-functionsserverless

解决方案


Cloud Functions 完全不支持您尝试执行的操作。重要的是要认识到,可能为每个部署的功能分配任意数量的服务器实例。这就是 Cloud Functions 如何以经济高效的方式向上和向下扩展以匹配函数上的负载。这些实例可能会因任何原因随时终止。您没有任何迹象表明实例何时终止。

此外,实例在空闲时无法执行任何计算。CPU 资源在函数终止后被限制,并在该实例上调用下一个函数时再次启动。当一个函数没有被主动调用时,你不能运行任何“守护进程”代码。我不知道你做了什么locationService,但在函数终止后它肯定什么都不做,不管它是如何终止的。

对于任何类型的长时间运行或类似守护程序的代码,Cloud Functions 都不是合适的产品。相反,您还应该考虑使用另一种产品,让您可以 24/7 不间断地运行代码。App Engine 和 Compute Engine 是可行的替代方案,您必须仔细考虑是否以及如何希望它们的服务器实例随负载扩展。


推荐阅读