在 Firebase 中为 10 万用户提供实时复杂的 Geoquery

问题描述 投票:0回答:1

用例:

  • 50K 到 100K 卡车实时更新位置和内部引擎信息。
  • 在控制台中,每次在一定范围内的卡车更改位置或更改内部引擎信息时,更新界面至关重要。所以如果位置相同但引擎信息是新的,应该触发更新。

问题:

  • Geofire 仅按位置查询,因此我必须创建一个 Geoquery 并单独收听每辆卡车的信息,这在 Realtime DB 和 Firestore 中都是不切实际的广告昂贵。
  • Firestore 不清楚实时侦听器与 Geoqueries 的结合(据我所知,Firestore 中没有 Geoqueries 的 onSnapshot,即使这样对于这种情况也会非常昂贵)。

次优选择: [Web Javascript Firebase V9 模块化] 我会使用 Firestore,为给定范围(静态的,非实时的)创建一个 Geoquery,并从中获取所有文档。然后在控制台中,用户只能通过单击特定卡车(文档)来查看实时信息,因此我可以将 onSnapshot 附加到该文档。

这个用例是否根本不受支持,或者有没有办法以合理的成本对数据进行建模以适应它?

javascript firebase geolocation real-time geohashing
1个回答
0
投票

GeoFire 仅在实时数据库上得到完全支持。 GeoFire 的数据旨在与您自己的数据分开保存,并通过唯一的密钥/ID 链接到它。这最大限度地减少了执行查询所需的索引,并最大限度地减少了查询时传递的数据。

Cloud Firestore 有一个解决方法,但它不适合您的用例,因为它会获取所有附近的记录,然后在客户端过滤其余记录。

如果你很好奇,GeoFire 条目在上传时看起来类似于以下内容(为了便于阅读而格式化):

"path/to/geofire/": {
  "<key>/": {
    ".priority": string, // a geohash, hidden
    "g": string, // a geohash
    "l": {
      "0": number, // latitude, number
      "1": number, // longitude, number
    }
  }
}

从上面可以看出,这里没有任何用户提供的数据,只有一个“密钥”。此键可以具有任何含义,例如注册表中的机场代码、实时数据库位置下的推送 ID、Cloud Firestore 文档 ID 或一些 base64 编码的数据。

随着 Firestore 的推出,许多用户将 GeoFire 数据存储在实时数据库中,并使用存储在 GeoFire 中的密钥将其链接回 Cloud Firestore 文档。

在下面的例子中,一个

truckInfo
对象看起来像这样:

interface TruckInfo {
  key: string;
  location: Geopoint; // [number, number]
  distance: number;
  snapshot: DataSnapshot | undefined;
  cancelling?: number; // an ID from a setTimeout call
  errorCount: number; // number of errors trying to attach data listener
  hidden?: true; // indicates whether the vehicle is currently out of range
  listening?: boolean; // indicates whether the listener was attached successfully
}

要使下面的代码起作用,您必须定义两个回调方法:

const truckUpdatedCallback = (truckInfo, snapshot) => { // or you can use ({ key, location, snapshot }) => { ... }
  // TODO: handle new trucks, updated locations and/or updated truck info
  // truckInfo.hidden may be true!
}

const truckRemovedCallback => (truckInfo, snapshot) => {
  // TODO: remove completely (deleted/out-of-range)
}

这些回调随后由以下“引擎”调用:

const firebaseRef = ref(getDatabase(), "gf"), // RTDB location for GeoFire data
  trucksColRef = collection(getFirestore(), "trucks"), // Firestore location for truck-related data
  geoFireInstance = new geofire.GeoFire(firebaseRef),
  trackedTrucks = new Map(), // holds all the tracked trucks
  listenToTruck = (truckInfo) => { // attaches the Firestore listeners for the trucks
    if (truckInfo.cancelling !== void 0) {
      clearTimeout(truckInfo.cancelling);
      delete truckInfo.cancelling;
    }
   
    if (truckInfo.listening || truckInfo.errorCount >= 3)
      return; // do nothing.

    truckInfo.unsub = onSnapshot(
      truckInfo.docRef,
      (snapshot) => {
        truckInfo.listening = true;
        truckInfo.errorCount = 0;
        truckInfo.snapshot = snapshot;
        truckUpdatedCallback(truckInfo, snapshot); // fire callback
      },
      (err) => {
        truckInfo.listening = false;
        truckInfo.errorCount++;
        console.error("Failed to track truck #" + truckInfo.key, err);
      }
    )
  },
  cancelQuery = () => { // removes the listeners for all trucks and disables query
    // prevents all future updates
    geoQuery.cancel();
    trackedTrucks.forEach(({unsub}) => {
      unsub && unsub();
    });
  };

const geoQuery = geoFireInstance.query({ center, radius });

geoQuery.on("key_entered", function(key, location, distance) {
  let truckInfo = trackedTrucks.get(key);

  if (!truckInfo) {
    // new truck to track
    const docRef = document(truckColRef, key);
    truckInfo = { key, location, distance, docRef, errorCount: 0 };
    trackedTrucks.set(key, truckInfo);
  } else {
    // truck has re-entered watch area, update position
    Object.assign(truckInfo, { location, distance });
    delete truckInfo.hidden;
  }

  listenToTruck(truckInfo);
});

geoQuery.on("key_moved", function(key, location, distance) {
  const truckInfo = trackedTrucks.get(key);
  if (!truckInfo) return; // not being tracked?
  Object.assign(truckInfo, { location, distance });
  truckUpdatedCallback(truckInfo, snapshot); // fire callback
});

geoQuery.on("key_exited", function(key, location, distance) {
  const truckInfo = trackedTrucks.get(key);
  if (!truckInfo) return; // not being tracked?

  truckInfo.hidden = true;

  const unsub = truckInfo.unsub,
    cleanup = () => { // removes any listeners for this truck and removes it from tracking
      unsub && unsub();
      truckInfo.listening = false;
      trackedTrucks.delete(key);
      truckRemovedCallback(truckInfo, snapshot); // fire removed callback
    };

  if (location === null && distance === null) {
    // removed from database, immediately remove from view
    return cleanup();
  }

  // keep getting updates for at least 60s after going out of
  // range in case vehicle returns. Afterwards, remove from view
  truckInfo.cancelling = setTimeout(cleanup, 60000);

  // fire callback (to hide the truck)
  truckUpdatedCallback(truckInfo, snapshot);
});
© www.soinside.com 2019 - 2024. All rights reserved.