1. 概览
在本实验中,您将把无服务器数据库(Spanner 和 Firestore)与在 Cloud Run 中运行的应用(Go 和 Node.js)集成在一起。Cymbal Eats 应用包含多个在 Cloud Run 上运行的服务。在以下步骤中,您将配置服务以使用 Cloud Spanner 关系型数据库和 Cloud Firestore(一种 NoSQL 文档数据库)。利用无服务器产品来处理数据层和应用运行时,您可以将所有基础设施管理任务剥离出来,全力专注于构建应用,而不必担心开销的问题。
2. 学习内容
在本实验中,您将学习如何完成以下操作:
- 集成 Spanner
- 启用 Spanner 托管服务
- 集成到代码中
- 部署连接到 Spanner 的代码
- 集成 Firestore
- 启用 Firestore 受管服务
- 集成到代码中
- 部署连接到 Firestore 的代码
3. 设置和要求
自定进度的环境设置
- 登录 Google Cloud 控制台,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个。



- 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串。您可以随时对其进行更新。
- 项目 ID 在所有 Google Cloud 项目中是唯一的,并且是不可变的(一经设置便无法更改)。Cloud 控制台会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(通常用
PROJECT_ID标识)。如果您不喜欢生成的 ID,可以再随机生成一个 ID。或者,您也可以尝试自己的项目 ID,看看是否可用。完成此步骤后便无法更改该 ID,并且此 ID 在项目期间会一直保留。 - 此外,还有第三个值,即部分 API 使用的项目编号,供您参考。如需详细了解所有这三个值,请参阅文档。
- 接下来,您需要在 Cloud 控制台中启用结算功能,以便使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有的话)。若要关闭资源以避免产生超出本教程范围的结算费用,您可以删除自己创建的资源或删除项目。Google Cloud 新用户符合参与 300 美元免费试用计划的条件。
设置环境
- 创建项目 ID 变量
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
export SPANNER_INSTANCE=inventory-instance
export SPANNER_DB=inventory-db
export REGION=us-east1
export SPANNER_CONNECTION_STRING=projects/$PROJECT_ID/instances/$SPANNER_INSTANCE/databases/$SPANNER_DB
- 启用 Spanner、Cloud Run、Cloud Build 和 Artifact Registry API
gcloud services enable \
compute.googleapis.com \
spanner.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
firestore.googleapis.com \
appengine.googleapis.com \
artifactregistry.googleapis.com
- 克隆存储库
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git
- 前往该目录
cd cymbal-eats/inventory-service/spanner
4. 创建和配置 Spanner 实例
Spanner 是商品目录服务后端关系型数据库。您将在以下步骤中创建 Spanner 实例、数据库和架构。
创建实例
- 创建 Cloud Spanner 实例
gcloud spanner instances create $SPANNER_INSTANCE --config=regional-${REGION} \
--description="Cymbal Menu Inventory" --nodes=1
输出示例
Creating instance...done.
- 验证 Spanner 实例是否已正确配置
gcloud spanner instances list
输出示例
NAME: inventory-instance DISPLAY_NAME: Cymbal Menu Inventory CONFIG: regional-us-east1 NODE_COUNT: 1 PROCESSING_UNITS: 100 STATE: READY
创建数据库和架构
创建新数据库,并使用 Google 标准 SQL 的数据定义语言 (DDL) 创建数据库架构。
- 创建 DDL 文件
echo "CREATE TABLE InventoryHistory (ItemRowID STRING (36) NOT NULL, ItemID INT64 NOT NULL, InventoryChange INT64, Timestamp TIMESTAMP) PRIMARY KEY(ItemRowID)" >> table.ddl
- 创建 Spanner 数据库
gcloud spanner databases create $SPANNER_DB \
--instance=$SPANNER_INSTANCE \
--ddl-file=table.ddl
输出示例
Creating database...done.
验证数据库状态和架构
- 查看数据库的状态
gcloud spanner databases describe $SPANNER_DB \
--instance=$SPANNER_INSTANCE
输出示例
createTime: '2022-04-22T15:11:33.559300Z' databaseDialect: GOOGLE_STANDARD_SQL earliestVersionTime: '2022-04-22T15:11:33.559300Z' encryptionInfo: - encryptionType: GOOGLE_DEFAULT_ENCRYPTION name: projects/cymbal-eats-7-348013/instances/menu-inventory/databases/menu-inventory state: READY versionRetentionPeriod: 1h
- 查看数据库的架构
gcloud spanner databases ddl describe $SPANNER_DB \
--instance=$SPANNER_INSTANCE
输出示例
CREATE TABLE InventoryHistory ( ItemRowID STRING(36) NOT NULL, ItemID INT64 NOT NULL, InventoryChange INT64, TimeStamp TIMESTAMP, ) PRIMARY KEY(ItemRowID);
5. 集成 Spanner
在本部分中,您将了解如何将 Spanner 集成到您的应用中。此外,SQL Spanner 还提供客户端库、JDBC 驱动程序、R2DBC 驱动程序、REST API 和 RPC API,让您可以将 Spanner 集成到任何应用中。
在下一部分中,您将使用 Go 客户端库在 Spanner 中安装、验证和修改数据。
安装客户端库
Cloud Spanner 客户端库会自动使用应用默认凭证 (ADC) 查找您的服务账号凭证,从而更轻松地与 Cloud Spanner 集成
设置身份验证
Google Cloud CLI 和 Google Cloud 客户端库会自动检测它们何时在 Google Cloud 上运行并使用当前 Cloud Run 修订版本的运行时服务账号。此策略称为应用默认凭证,支持跨多个环境移植代码。
不过,最好为其分配用户管理的服务账号,而不是默认服务账号,从而创建专用身份。
- 向服务账号授予 Spanner Database Admin 角色
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
--role="roles/spanner.databaseAdmin"
输出示例
Updated IAM policy for project [cymbal-eats-6422-3462]. [...]
使用客户端库
Spanner 客户端库可抽象化与 Spanner 集成的复杂性,并支持多种常用编程语言。
创建 Spanner 客户端
Spanner 客户端是一种用于读取 Cloud Spanner 数据库中的数据以及向该数据库写入数据的客户端。客户端可以安全地并发使用,但其 Close 方法除外。
以下代码段会创建一个 Spanner 客户端
main.go
var dataClient *spanner.Client ... dataClient, err = spanner.NewClient(ctx, databaseName)
您可以将 Client 视为数据库连接:您与 Cloud Spanner 的所有交互都必须通过 Client 进行。通常,您可以在应用启动时创建客户端,然后重复使用该客户端来读取、写入和执行事务。每个客户端均使用 Cloud Spanner 中的资源。
修改数据
您可以通过多种方式在 Spanner 数据库中插入、更新和删除数据。下面列出了可用的方法。
在此实验中,您将使用变更来修改 Spanner 中的数据。
Spanner 中的变更
Mutation 是用于变更操作的容器。Mutation 表示插入、更新和删除等一系列操作,Cloud Spanner 将这些操作以原子方式应用于 Cloud Spanner 数据库中不同的行和表。
main.go
m := []*spanner.Mutation{}
m = append(m, spanner.Insert(
"inventoryHistory",
inventoryHistoryColumns,
[]interface{}{uuid.New().String(), element.ItemID, element.InventoryChange, time.Now()}))
此代码段用于在商品目录历史记录表中插入新行。
部署和测试
现在,Spanner 已配置完毕,并且您已查看关键代码元素,接下来可以将应用部署到 Cloud Run。
将应用部署到 Cloud Run
Cloud Run 可以通过一个命令自动构建、推送和部署您的代码。在以下命令中,您将对 run 服务调用 deploy 命令,并传入正在运行的应用所使用的变量,例如您之前创建的 SPANNER_CONNECTION_STRING。
- 点击“打开终端”
- 将库存服务部署到 Cloud Run
gcloud run deploy inventory-service \
--source . \
--region $REGION \
--update-env-vars SPANNER_CONNECTION_STRING=$SPANNER_CONNECTION_STRING \
--allow-unauthenticated \
--project=$PROJECT_ID \
--quiet
输出示例
Service [inventory-service] revision [inventory-service-00001-sug] has been deployed and is serving 100 percent of traffic. Service URL: https://inventory-service-ilwytgcbca-uk.a.run.app
- 存储服务网址
INVENTORY_SERVICE_URL=$(gcloud run services describe inventory-service \
--platform managed \
--region $REGION \
--format=json | jq \
--raw-output ".status.url")
测试 Cloud Run 应用
插入内容
- 在 Cloud Shell 中,输入以下命令。
POST_URL=$INVENTORY_SERVICE_URL/updateInventoryItem
curl -i -X POST ${POST_URL} \
--header 'Content-Type: application/json' \
--data-raw '[
{
"itemID": 1,
"inventoryChange": 5
}
]'
输出示例
HTTP/2 200 access-control-allow-origin: * content-type: application/json x-cloud-trace-context: 10c32f0863d26521497dc26e86419f13;o=1 date: Fri, 22 Apr 2022 21:41:38 GMT server: Google Frontend content-length: 2 OK
查询商品
- 查询商品目录服务
GET_URL=$INVENTORY_SERVICE_URL/getAvailableInventory
curl -i ${GET_URL}
示例响应
HTTP/2 200
access-control-allow-origin: *
content-type: text/plain; charset=utf-8
x-cloud-trace-context: b94f921e4c2ae90210472c88eb05ace8;o=1
date: Fri, 22 Apr 2022 21:45:50 GMT
server: Google Frontend
content-length: 166
[{"ItemID":1,"Inventory":5}]
6. Spanner 概念
Cloud Spanner 使用声明性 SQL 语句来查询其数据库。SQL 语句会指明用户想要什么结果,但不会说明如何获取结果。
- 在终端中,输入以下命令来查询之前创建的记录。
gcloud spanner databases execute-sql $SPANNER_DB \
--instance=$SPANNER_INSTANCE \
--sql='SELECT * FROM InventoryHistory WHERE ItemID=1'
输出示例
ItemRowID: 1
ItemID: 1
InventoryChange: 3
Timestamp:
查询执行计划
查询执行计划是 Spanner 用于获取结果的一系列步骤。获取特定 SQL 语句的结果的方式可能有多种。您可以在控制台和客户端库中访问查询执行计划。如需了解 Spanner 如何处理 SQL 查询,请执行以下操作:
- 在控制台中,打开 Cloud Spanner 实例页面。
- 转到 Cloud Spanner 实例
- 点击 Cloud Spanner 实例的名称。在“数据库”部分中,选择要查询的数据库。
- 点击“查询”。
- 在查询编辑器中输入以下查询
SELECT * FROM InventoryHistory WHERE ItemID=1
- 点击“运行”
- 点击“说明”
Cloud 控制台将直观显示您查询的执行计划。

查询优化器
Cloud Spanner 查询优化器会比较替代执行计划,并选择最有效的计划。随着时间的推移,查询优化器会不断发展,扩大了查询执行计划中的选项集,并提高了传达这些选择的估算值的准确性,从而产生更高效的查询执行计划。
Cloud Spanner 将以新查询优化器版本的形式发布优化器更新。默认情况下,每个数据库在该版本发布后的 30 天内开始使用最新版本的优化器。
如需查看在 gcloud spanner 中运行查询时使用的版本,请将 --query-mode 标志设置为 PROFILE
- 输入以下命令以查看优化器版本
gcloud spanner databases execute-sql $SPANNER_DB --instance=$SPANNER_INSTANCE \
--query-mode=PROFILE --sql='SELECT * FROM InventoryHistory'
输出示例
TOTAL_ELAPSED_TIME: 6.18 msecs
CPU_TIME: 5.17 msecs
ROWS_RETURNED: 1
ROWS_SCANNED: 1
OPTIMIZER_VERSION: 3
RELATIONAL Distributed Union
(1 execution, 0.11 msecs total latency)
subquery_cluster_node: 1
|
+- RELATIONAL Distributed Union
| (1 execution, 0.09 msecs total latency)
| call_type: Local, subquery_cluster_node: 2
| |
| \- RELATIONAL Serialize Result
| (1 execution, 0.08 msecs total latency)
| |
| +- RELATIONAL Scan
| | (1 execution, 0.08 msecs total latency)
| | Full scan: true, scan_target: InventoryHistory, scan_type: TableScan
| | |
| | +- SCALAR Reference
| | | ItemRowID
| | |
| | +- SCALAR Reference
| | | ItemID
| | |
| | +- SCALAR Reference
| | | InventoryChange
| | |
| | \- SCALAR Reference
| | Timestamp
| |
| +- SCALAR Reference
| | $ItemRowID
| |
| +- SCALAR Reference
| | $ItemID
| |
| +- SCALAR Reference
| | $InventoryChange
| |
| \- SCALAR Reference
| $Timestamp
|
\- SCALAR Constant
true
ItemRowID: 1
ItemID: 1
InventoryChange: 3
Timestamp:
更新优化器版本
本实验发布时的最新版本为版本 4。接下来,您将更新 Spanner 表,以使用版本 4 的查询优化器。
- 更新优化器
gcloud spanner databases ddl update $SPANNER_DB \
--instance=$SPANNER_INSTANCE \
--ddl='ALTER DATABASE InventoryHistory
SET OPTIONS (optimizer_version = 4)'
输出示例
Schema updating...done.
- 输入以下命令以查看优化器版本更新
gcloud spanner databases execute-sql $SPANNER_DB --instance=$SPANNER_INSTANCE \
--query-mode=PROFILE --sql='SELECT * FROM InventoryHistory'
输出示例
TOTAL_ELAPSED_TIME: 8.57 msecs CPU_TIME: 8.54 msecs ROWS_RETURNED: 1 ROWS_SCANNED: 1 OPTIMIZER_VERSION: 4 [...]
在 Metrics Explorer 中直观呈现查询优化器版本
您可以使用 Cloud 控制台中的 Metrics Explorer 直观呈现数据库实例的查询计数。您可以查看每个数据库中使用的优化器版本。
- 在 Cloud 控制台中前往“监控”,然后在左侧菜单中选择 Metrics Explorer。
- 在资源类型字段中,选择“Cloud Spanner 实例”。
- 在指标字段中,选择“查询次数”,然后点击“应用”。
- 在分组依据字段中,选择 database、optimizer_version 和 status。

7. 创建和配置 Firestore 数据库
Firestore 是一个 NoSQL 文档数据库,能够自动扩缩、具备出色的性能,并且易于进行应用开发。虽然 Firestore 界面具有许多与传统数据库相同的功能,但作为 NoSQL 数据库,它与传统数据库在描述数据对象间关系的方式方面有所不同。
以下任务将引导您创建一个由 Firestore 提供支持的订购服务 Cloud Run 应用。在开始处理订单之前,订购服务会调用上一部分中创建的库存服务来查询 Spanner 数据库。此服务将确保有足够的库存,并且可以完成订单。

8. Firestore 概念
数据模型
Firestore 数据库由集合和文档组成。

文档
每个文档都包含一组键值对。Firestore 经过优化,可用于存储小型文档的大型集合。

集合
您必须将所有文档存储在集合中。文档可以包含子集合和嵌套对象,包括基本字段(如字符串)或复杂对象(如列表)。

创建 Firestore 数据库
- 创建 Firestore 数据库
gcloud firestore databases create --location=$REGION
输出示例
Success! Selected Google Cloud Firestore Native database for cymbal-eats-6422-3462
9. 将 Firestore 集成到您的应用中
在本部分中,您将更新服务账号、添加 Firestore 访问服务账号、查看并部署 Firestore 安全规则,以及查看 Firestore 中的数据是如何修改的。
设置身份验证
- 向服务账号授予 Datastore 用户角色
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
--role="roles/datastore.user"
输出示例
Updated IAM policy for project [cymbal-eats-6422-3462].
Firestore 安全规则
安全规则以简单明了的格式提供访问权限控制和数据验证。
- 前往 order-service/starter-code 目录
cd ~/cymbal-eats/order-service
- 在云编辑器中打开 firestore.rules 文件
cat firestore.rules
firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents { ⇐ All database
match /{document=**} { ⇐ All documents
allow read: if true; ⇐ Allow reads
}
match /{document=**} {
allow write: if false; ⇐ Deny writes
}
}
}
警告:最佳实践是限制对 Firestore 存储空间的访问权限。在本实验中,允许所有读取操作。不建议在生产环境中采用此配置。
启用 Firestore 受管服务
- 点击“打开终端”
- 使用当前项目 ID 创建 .firebaserc 文件。部署目标的设置存储在项目目录下的 .firebaserc 文件中。
firebaserc.tmpl
sed "s/PROJECT_ID/$PROJECT_ID/g" firebaserc.tmpl > .firebaserc
- 下载 Firebase 二进制文件
curl -sL https://firebase.tools | upgrade=true bash
输出示例
-- Checking for existing firebase-tools on PATH... Your machine already has firebase-tools@10.7.0 installed. Nothing to do. -- All done!
- 部署 Firestore 规则。
firebase deploy
输出示例
=== Deploying to 'cymbal-eats-6422-3462'... i deploying firestore i cloud.firestore: checking firestore.rules for compilation errors... ✔ cloud.firestore: rules file firestore.rules compiled successfully i firestore: uploading rules firestore.rules... ✔ firestore: released rules firestore.rules to cloud.firestore ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/cymbal-eats-6422-3462/overview
修改数据
集合和文档是在 Firestore 中隐式创建的。只需将数据分配给集合中的文档即可。如果集合或文档不存在,Firestore 会创建一个。
向 Firestore 添加数据
您可以通过几种方式将数据写入 Cloud Firestore:
- 设置集合中的文档的数据,明确指定文档标识符。
- 向集合添加新文档。在这种情况下,Cloud Firestore 会自动生成文档标识符。
- 创建一个使用自动生成的标识符的空文档,并在以后向该文档分配数据。
下一部分将指导您使用 set 方法创建文档。
设置文档
使用 set() 方法创建文档。使用 set() 方法时,您必须为要创建的文档指定 ID。
请查看以下代码段。
index.js
const orderDoc = db.doc(`orders/123`);
await orderDoc.set({
orderNumber: 123,
name: Anne,
address: 555 Bright Street,
city: Mountain View,
state: CA,
zip: 94043,
orderItems: [id: 1],
status: 'New'
});
此代码将创建一个指定用户生成的文档 ID 123 的文档。如需让 Firestore 代表您生成 ID,请使用 add() 或 create() 方法。
更新文档
借助更新方法 update(),您可以更新某些文档字段,而无需覆盖整个文档。
在下面的代码段中,代码会更新订单 123
index.js
const orderDoc = db.doc(`orders/123`); await orderDoc.update(name: "Anna");
删除文档
在 Firestore 中,您可以删除集合、文档或文档中的特定字段。如需删除文档,请使用 delete() 方法。
以下代码段会删除订单 123。
index.js
const orderDoc = db.doc(`orders/123`); await orderDoc.delete();
10. 部署和测试
在本部分中,您将应用部署到 Cloud Run,并测试创建、更新和删除方法。
将应用部署到 Cloud Run
- 将网址存储在变量 INVENTORY_SERVICE_网址 中,以便与 Inventory Service 集成
INVENTORY_SERVICE_URL=$(gcloud run services describe inventory-service \
--region=$REGION \
--format=json | jq \
--raw-output ".status.url")
- 部署订单服务
gcloud run deploy order-service \
--source . \
--platform managed \
--region $REGION \
--allow-unauthenticated \
--project=$PROJECT_ID \
--set-env-vars=INVENTORY_SERVICE_URL=$INVENTORY_SERVICE_URL \
--quiet
输出示例
[...] Done. Service [order-service] revision [order-service-00001-qot] has been deployed and is serving 100 percent of traffic. Service URL: https://order-service-3jbm3exegq-uk.a.run.app
测试 Cloud Run 应用
创建文档
- 将订单服务应用的网址存储到变量中以供测试
ORDER_SERVICE_URL=$(gcloud run services describe order-service \
--platform managed \
--region $REGION \
--format=json | jq \
--raw-output ".status.url")
- 构建订单请求并将新订单发布到 Firestore 数据库
curl --request POST $ORDER_SERVICE_URL/order \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Jane Doe",
"email": "Jane.Doe-cymbaleats@gmail.com",
"address": "123 Maple",
"city": "Buffalo",
"state": "NY",
"zip": "12346",
"orderItems": [
{
"id": 1
}
]
}'
输出示例
{"orderNumber":46429}
保存订单号以备后用
export ORDER_NUMBER=<value_from_output>
查看结果
在 Firestore 中查看结果
- 前往 Firestore 控制台
- 点击“数据”

更新文档
提交的订单未包含数量。
- 更新记录并添加数量键值对
curl --location -g --request PATCH $ORDER_SERVICE_URL/order/${ORDER_NUMBER} \
--header 'Content-Type: application/json' \
--data-raw '{
"orderItems": [
{
"id": 1,
"quantity": 1
}
]
}'
输出示例
{"status":"success"}
查看结果
在 Firestore 中查看结果
- 前往 Firestore 控制台
- 点击“数据”

删除文档
- 从 Firestore 订单集合中删除商品 46429
curl --location -g --request DELETE $ORDER_SERVICE_URL/order/${ORDER_NUMBER}
查看结果
- 前往 Firestore 控制台
- 点击“数据”

11. 恭喜!
恭喜,您已完成本实验!
后续步骤:
探索其他 Cymbal Eats Codelab:
- 使用 Eventarc 触发 Cloud Workflows
- 触发 Cloud Storage 中的事件处理
- 从 Cloud Run 连接到专用 CloudSQL
- 使用 Identity Aware Proxy (IAP) 保护无服务器应用
- 使用 Cloud Scheduler 触发 Cloud Run 作业
- 安全地部署到 Cloud Run
- 保护 Cloud Run 入站流量的安全
- 从 GKE Autopilot 连接到专用 AlloyDB
清理
为避免因本教程中使用的资源导致您的 Google Cloud 账号产生费用,请删除包含这些资源的项目,或者保留项目但删除各个资源。
删除项目
若要避免产生费用,最简单的方法是删除您为本教程创建的项目。