许多应用会在首次网页加载时向所有用户分发相同的内容。例如,新闻网站可能显示最新报道,电子商务网站可能显示畅销商品。
如果此类内容从 Cloud Firestore 分发,就意味着在加载应用时,每个用户会发出一个新查询,但得到的是同样的结果。由于这些结果不会在用户之间缓存,因此应用的速度会比预期慢,并且消耗的成本也会比预期高。
解决方案:内容包
借助 Cloud Firestore 内容包,您可以使用 Firebase Admin SDK 根据后端上的常见查询结果汇总数据内容包,并在 CDN 上分发这些缓存的预计算 BLOB。这能让您的用户享受快得多的首次加载体验,并减少 Cloud Firestore 查询成本。
在本指南中,我们将使用 Cloud Functions 生成内容包并使用 Firebase Hosting 进行动态缓存和分发打包的内容。如需详细了解内容包,请参阅相关指南。
首先,创建一个简单的公开 HTTP 函数,以查询 50 个最新“报道”并将结果作为内容包进行分发。
Node.js
exports.createBundle = functions.https.onRequest(async (request, response) => { // Query the 50 latest stories const latestStories = await db.collection('stories') .orderBy('timestamp', 'desc') .limit(50) .get(); // Build the bundle from the query results const bundleBuffer = db.bundle('latest-stories') .add('latest-stories-query', latestStories) .build(); // Cache the response for up to 5 minutes; // see https://firebase.google.com/docs/hosting/manage-cache response.set('Cache-Control', 'public, max-age=300, s-maxage=600'); response.end(bundleBuffer); });
Java
package com.example; import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.firestore.Firestore; import com.google.cloud.firestore.FirestoreBundle; import com.google.cloud.firestore.Query.Direction; import com.google.cloud.firestore.QuerySnapshot; import com.google.cloud.functions.HttpFunction; import com.google.cloud.functions.HttpRequest; import com.google.cloud.functions.HttpResponse; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.cloud.FirestoreClient; import java.io.BufferedWriter; import java.io.IOException; public class ExampleFunction implements HttpFunction { public static FirebaseApp initializeFirebase() throws IOException { if (FirebaseApp.getApps().isEmpty()) { FirebaseOptions options = FirebaseOptions.builder() .setCredentials(GoogleCredentials.getApplicationDefault()) .setProjectId("YOUR-PROJECT-ID") .build(); FirebaseApp.initializeApp(options); } return FirebaseApp.getInstance(); } @Override public void service(HttpRequest request, HttpResponse response) throws Exception { // Get a Firestore instance FirebaseApp app = initializeFirebase(); Firestore db = FirestoreClient.getFirestore(app); // Query the 50 latest stories QuerySnapshot latestStories = db.collection("stories") .orderBy("timestamp", Direction.DESCENDING) .limit(50) .get() .get(); // Build the bundle from the query results FirestoreBundle bundle = db.bundleBuilder("latest-stores") .add("latest-stories-query", latestStories) .build(); // Cache the response for up to 5 minutes // see https://firebase.google.com/docs/hosting/manage-cache response.appendHeader("Cache-Control", "public, max-age=300, s-maxage=600"); // Write the bundle to the HTTP response BufferedWriter writer = response.getWriter(); writer.write(new String(bundle.toByteBuffer().array())); } }
接下来,通过修改 firebase.json
来配置 Firebase Hosting,以分发和缓存此 Cloud Functions 函数。利用此配置,Firebase Hosting CDN 将根据 Cloud Functions 函数设定的缓存设置分发打包的内容。当缓存过期时,它会再次触发该函数来刷新内容。
firebase.json
{
"hosting": {
// ...
"rewrites": [{
"source": "/createBundle",
"function": "createBundle"
}]
},
// ...
}
最后,在 Web 应用中,从 CDN 提取打包的内容并将其加载到 Firestore SDK 中。
// If you are using module bundlers.
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/firestore/bundle" // This line enables bundle loading as a side effect.
async function fetchFromBundle() {
// Fetch the bundle from Firebase Hosting, if the CDN cache is hit the 'X-Cache'
// response header will be set to 'HIT'
const resp = await fetch('/createBundle');
// Load the bundle contents into the Firestore SDK
await db.loadBundle(resp.body);
// Query the results from the cache
// Note: omitting "source: cache" will query the Firestore backend.
const query = await db.namedQuery('latest-stories-query');
const storiesSnap = await query.get({ source: 'cache' });
// Use the results
// ...
}
预估节省额
假设有一个新闻网站每天有 10 万名用户浏览,并且每位用户的首次加载都会得到相同的 50 篇热门报道。如果没有任何缓存,则每天从 Cloud Firestore 读取文档的次数将为 50 x 100,000 = 5,000,000 次。
现在,假设该网站采用上述技术,并缓存了这 50 个结果(长达 5 分钟)。这样一来,查询结果就只需要每小时加载 12 次,而不是为每个用户加载一次。无论有多少用户访问该网站,对 Cloud Firestore 的查询量将保持不变。此页面将每天读取 12 x 24 x 50 = 14,400 次文档,而不是 5,000,000 次。节省的 Cloud Firestore 费用可以轻松抵消 Firebase Hosting 和 Cloud Functions 函数额外带来的小笔费用。
虽然开发者可以节省成本,但最大的受益者还是用户。从 Firebase Hosting CDN(而不是直接从 Cloud Firestore)加载这 50 个文档可以轻松将页面内容加载时间缩短 100-200 毫秒,甚至更长时间。很多研究都表明,网页加载速度越快,用户越满意。