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)
您可以将客户端视为数据库连接:您与 Cloud Spanner 的所有交互都必须通过客户端进行。通常,您可以在应用启动时创建客户端,然后重复使用该客户端来读取、写入和执行事务。每个客户端均使用 Cloud Spanner 中的资源。
修改数据
您可以通过多种方式在 Spanner 数据库中插入、更新和删除数据。下面列出了可用的方法。
在本实验中,您将使用变更来修改 Spanner 中的数据。
Spanner 中的变更
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 应用
插入内容
- 在 cloudshell 中输入以下命令。
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 控制台中的 Monitoring,然后选择左侧菜单中的 Metrics Explorer。
- 在 Resource type 字段中,选择 Cloud Spanner Instance。
- 在指标字段中,选择“查询次数”和“应用”。
- 在分组依据字段中,选择数据库、优化工具版本和状态。
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
- 在 Cloud Editor 中打开 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 服务集成
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 连接到 Private CloudSQL
- 使用 Identity-Aware Proxy (IAP) 保护无服务器应用
- 使用 Cloud Scheduler 触发 Cloud Run 作业
- 安全地部署到 Cloud Run
- 保护 Cloud Run 入站流量
- 从 GKE Autopilot 连接到专用 AlloyDB
清理
为避免因本教程中使用的资源导致您的 Google Cloud 账号产生费用,请删除包含这些资源的项目,或者保留项目但删除各个资源。
删除项目
若要避免产生费用,最简单的方法是删除您为本教程创建的项目。