4

我正在开发一个 NodeJS Web 应用程序以通过 Admin SDK 从 Firestore DB 接收实时更新。

这是 Firestore 对象的初始化代码。它只执行一次,在部署应用程序时(在 AWS Elastic Beanstalk 上):

const admin = require('firebase-admin');

var serviceAccount = require('./../key.json');

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount)
});

var db = admin.firestore();

FUNC.firestore = db;

然后我在 websocket 通信中使用这个 firestore 对象向浏览器发送实时更新。这个想法是使用服务器作为浏览器和 Firestore 之间的代理。

socket.on('open', function (client) {
    var query = FUNC.firestore.collection("notifications").doc(client.user.id.toString()).collection("global");
    query.onSnapshot(querySnapshot => {
        querySnapshot.docChanges().forEach(change => {
            client.send({ id: change.doc.id, body: change.doc.data(), type: change.type });
        });
    }, err => {
        console.log(`Encountered error: ${err}`);
    });
});

socket.on('close', function (client) {
    var unsub = FUNC.firestore.collection("notifications").doc(client.user.id.toString()).collection("global").onSnapshot(() => {
    });
    unsub();
});

它在一段时间内运行良好,但几个小时后客户端停止接收onSnapshot()更新,一段时间后服务器记录错误:Encountered error: Error: 10 ABORTED: The operation was aborted.

怎么了?我应该在每个连接上初始化 firestore 吗?是否存在一些生命周期错误?

谢谢

编辑(一个非常糟糕的解决方案)

我尝试为每个登录的用户创建一个 firebase-admin 应用程序实例,并以这种方式更改了我的代码

const admin = require('firebase-admin');

var serviceAccount = require('./../key.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

FUNC.getFirestore = function (user) {
  try {
    user.firebase = admin.app(user.id.toString());
    return user.firebase.firestore();
  } catch(e) {
    //ignore
  }

  var app = admin.initializeApp({
    credential: admin.credential.cert(serviceAccount)
  }, user.id.toString());

  user.firebase = app;

  return user.firebase.firestore();
}

FUNC.removeFirebase = function (user) {
  if (user.firebase) {
    user.firebase.delete();
  }
}

然后是套接字侦听器:

    self.on('open', function (client) {
        var query = FUNC.getFirestore(client.user).collection("notifications").doc(client.user.id.toString()).collection("global");
        query.onSnapshot(querySnapshot => {
            querySnapshot.docChanges().reverse();
            querySnapshot.docChanges().forEach(change => {
                client.send({ id: change.doc.id, body: change.doc.data(), type: change.type });
            });
        }, err => {
            console.log(`Encountered error: ${err}`);
        });
    });

    self.on('close', function (client) {
        var unsub = FUNC.getFirestore(client.user).collection("notifications").doc(client.user.id.toString()).collection("global").onSnapshot(() => {
        });
        unsub();
        FUNC.removeFirebase(client.user);
});

因此,当客户端由于某种原因断开连接时,服务器删除了它的 firebase 应用程序,它可以工作,但我注意到服务器上有巨大的内存泄漏,我需要一些帮助

4

1 回答 1

4

更新的答案

经过多次研究,我明白这种方法是错误的。当然,旧的答案可能是一种解决方法,但不是问题的真正解决方案,因为 Firestore 的设计初衷不是为了执行以下操作:Firestore <--(Admin SDK)--> Server <--(WebSocket)--> Client.

为了创建最佳通信,我了解并应用了 Firestore 安全规则 ( https://firebase.google.com/docs/firestore/security/get-started ) 以及自定义令牌生成 ( https://firebase.google。 com/docs/auth/admin/create-custom-tokens)。所以正确的流程是:

Client login request --> Server + Admin SDK generate custom auth token and return to client

然后,实时通信将仅在 Client 和 Firestore 本身之间进行,因此:Client + Custom Auth Token <--(Firebase JS SDK)--> Firestore DB

如您所见,服务器不再参与实时通信,但客户端直接从 Firestore 接收更新。

旧答案

终于可以从我自己来回答了。首先,我尝试过的第二个解决方案非常糟糕,因为通过 Admin SDK 创建的每个新应用程序都存储在 RAM 中,对于 20/30 的用户,该应用程序达到超过 1GB 的 RAM,绝对不能接受。

所以第一个实现是更好的解决方案,无论如何我错了注册/取消注册 onSnapshot 监听器生命周期。每个onSnapshot()调用都返回一个不同的函数,即使在同一个引用上调用也是如此。因此,我没有在套接字关闭时关闭侦听器,而是打开了另一个侦听器。应该是这样的:

socket.on('open', function (client) {
    var query = FUNC.firestore.collection("notifications").doc(client.user.id.toString()).collection("global");
    client.user.firestoreUnsub = query.onSnapshot(querySnapshot => {
        querySnapshot.docChanges().forEach(change => {
            client.send({ id: change.doc.id, body: change.doc.data(), type: change.type });
        });
    }, err => {
        console.log(`Encountered error: ${err}`);
    });
});

socket.on('close', function (client) {
    client.user.firestoreUnsub();
});

将近 48 小时后,侦听器仍然可以正常工作,并且没有发生内存泄漏。

于 2019-02-17T18:02:24.060 回答