从 Cloud Run 连接到全代管式数据库

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. 设置和要求

自定进度的环境设置

  1. 登录 Google Cloud 控制台,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串。您可以随时对其进行更新。
  • 项目 ID 在所有 Google Cloud 项目中是唯一的,并且是不可变的(一经设置便无法更改)。Cloud 控制台会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(通常用 PROJECT_ID 标识)。如果您不喜欢生成的 ID,可以再随机生成一个 ID。或者,您也可以尝试自己的项目 ID,看看是否可用。完成此步骤后便无法更改该 ID,并且此 ID 在项目期间会一直保留。
  • 此外,还有第三个值,即部分 API 使用的项目编号,供您参考。如需详细了解所有这三个值,请参阅文档
  1. 接下来,您需要在 Cloud 控制台中启用结算功能,以便使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有的话)。若要关闭资源以避免产生超出本教程范围的结算费用,您可以删除自己创建的资源或删除项目。Google Cloud 新用户符合参与 300 美元免费试用计划的条件。

设置环境

  1. 创建项目 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
  1. 启用 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
  1. 克隆存储库
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git
  1. 导航到该目录
cd cymbal-eats/inventory-service/spanner

4. 创建和配置 Spanner 实例

Spanner 是房源/票源服务后端关系型数据库。您将按照以下步骤创建 Spanner 实例、数据库和架构。

创建实例

  1. 创建 Cloud Spanner 实例
gcloud spanner instances create $SPANNER_INSTANCE --config=regional-${REGION} \
--description="Cymbal Menu Inventory" --nodes=1

输出示例

Creating instance...done.   
  1. 验证 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) 创建数据库架构。

  1. 创建 DDL 文件
echo "CREATE TABLE InventoryHistory (ItemRowID STRING (36) NOT NULL, ItemID INT64 NOT NULL, InventoryChange INT64, Timestamp TIMESTAMP) PRIMARY KEY(ItemRowID)" >> table.ddl
  1. 创建 Spanner 数据库
gcloud spanner databases create $SPANNER_DB \
--instance=$SPANNER_INSTANCE \
--ddl-file=table.ddl

输出示例

Creating database...done.

验证数据库状态和架构

  1. 查看数据库的状态
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
  1. 查看数据库架构
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 APIRPC API,可让您将 Spanner 集成到任何应用中。

在下一部分中,您将使用 Go 客户端库在 Spanner 中安装、验证和修改数据。

安装客户端库

Cloud Spanner 客户端库可自动使用应用默认凭据 (ADC) 查找您的服务账号凭据,从而简化与 Cloud Spanner 的集成

设置身份验证

Google Cloud CLI 和 Google Cloud 客户端库会自动检测它们是否在 Google Cloud 上运行,并使用当前 Cloud Run 修订版本的运行时服务账号。此策略称为“应用默认凭据”,可实现跨多个环境的代码可移植性。

但是,创建专用身份的最佳方式是为其分配用户管理的服务账号,而不是默认服务账号。

  1. 向服务账号授予 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。

  1. 点击“打开终端”
  2. 将房源/票源服务部署到 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
  1. 存储服务网址
INVENTORY_SERVICE_URL=$(gcloud run services describe inventory-service \
  --platform managed \
  --region $REGION \
  --format=json | jq \
  --raw-output ".status.url")

测试 Cloud Run 应用

插入内容

  1. 在 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

查询商品

  1. 查询房源/票源服务
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 语句会指明用户想要的内容,而不说明将如何获得结果。

  1. 在终端中,输入以下命令来查询表中之前创建的记录。
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 查询,请执行以下操作:

  1. 在控制台中,打开 Cloud Spanner 实例页面。
  2. 转到 Cloud Spanner 实例
  3. 点击 Cloud Spanner 实例的名称。在数据库部分中,选择要查询的数据库。
  4. 点击“查询”。
  5. 在查询编辑器中输入以下查询
SELECT * FROM InventoryHistory WHERE ItemID=1
  1. 点击“运行”
  2. 点击“说明”

Cloud 控制台会直观显示您的查询的执行计划。

149f8bae468f8b34

查询优化器

Cloud Spanner 查询优化器会比较多个替代执行计划,并选择最高效的执行计划。随着时间的推移,查询优化器将不断改进,拓宽了查询执行计划中的选择,并提高为这些选择提供依据的估算的准确性,从而产生更加高效的查询执行计划。

Cloud Spanner 将优化器更新作为新的查询优化器版本发布。默认情况下,每个数据库在最新版本发布后的 30 天内开始使用最新版本的优化工具。

要查看在 gcloud spanner 中运行查询时使用的版本,请将 –query-mode 标志设置为 PROFILE

  1. 输入以下命令以查看优化器版本
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 用于查询优化器。

  1. 更新优化器
gcloud spanner databases ddl update $SPANNER_DB \
--instance=$SPANNER_INSTANCE \
--ddl='ALTER DATABASE InventoryHistory
SET OPTIONS (optimizer_version = 4)'

输出示例

Schema updating...done. 
  1. 输入以下命令以查看优化器版本更新
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 来直观呈现数据库实例的查询次数。您可以看到每个数据库中正在使用的优化工具版本。

  1. 前往 Cloud 控制台中的 Monitoring,然后选择左侧菜单中的 Metrics Explorer
  2. Resource type 字段中,选择 Cloud Spanner Instance。
  3. 指标字段中,选择“查询次数”和“应用”。
  4. 分组依据字段中,选择数据库、优化工具版本和状态。

581b859c25790b21

7. 创建和配置 Firestore 数据库

Firestore 是一个 NoSQL 文档数据库,能够自动扩缩、具备出色的性能,并且易于进行应用开发。虽然 Firestore 接口的许多功能与传统数据库相同,但 NoSQL 数据库在描述数据对象之间的关系方面有所不同。

以下任务将引导您创建由 Firestore 支持的排序服务 Cloud Run 应用。订购服务将调用上一部分中创建的房源/票源服务来查询 Spanner 数据库,然后再开始下单。这项服务可确保有足够的广告资源来保证订单的货物能够履行。

6843abaf4263e112

8. Firestore 概念

数据模型

Firestore 数据库由集合和文档组成。

b60acd63d4793a6c.png

文档

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

5571cb2f261d2dbe.png

集合

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

5811378cb721e5ec

创建 Firestore 数据库

  1. 创建 Firestore 数据库
gcloud firestore databases create --location=$REGION

输出示例

Success! Selected Google Cloud Firestore Native database for cymbal-eats-6422-3462

9. 将 Firestore 集成到您的应用中

在本部分中,您将更新服务账号、添加 Firestore 访问服务账号、查看和部署 Firestore 安全规则,以及查看数据在 Firestore 中的修改方式。

设置身份验证

  1. 向服务账号授予 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 安全规则

安全规则提供具有表现力和简单明了的访问控制和数据验证格式。

  1. 前往 order-service/starter-code 目录
cd ~/cymbal-eats/order-service
  1. 在 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 代管式服务

  1. 点击“打开终端”
  2. 使用当前项目 ID 创建 .firebaserc 文件。部署目标的设置存储在项目目录下的 .firebaserc 文件中。

firebaserc.tmpl

sed "s/PROJECT_ID/$PROJECT_ID/g" firebaserc.tmpl > .firebaserc
  1. 下载 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!
  1. 部署 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

  1. 将网址存储在变量 INVENTORY_SERVICE_网址 中,以便与 Inventory 服务集成
INVENTORY_SERVICE_URL=$(gcloud run services describe inventory-service \
 --region=$REGION \
 --format=json | jq \
 --raw-output ".status.url")
  1. 部署订单服务
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 应用

创建文档

  1. 将订单服务应用的网址存储到变量中以进行测试
ORDER_SERVICE_URL=$(gcloud run services describe order-service \
  --platform managed \
  --region $REGION \
  --format=json | jq \
  --raw-output ".status.url")
  1. 构建订单请求并将新订单发布到 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 中查看结果

  1. 前往 Firestore 控制台
  2. 点击“数据”

465ceca6198b2b88

更新文档

提交的订单未包含数量。

  1. 更新记录并添加数量键值对
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 中查看结果

  1. 前往 Firestore 控制台
  2. 点击“数据”

cfcf78d200e15b84.png

删除文档

  1. 从 Firestore 订单集合中删除项目 46429
curl --location -g --request DELETE $ORDER_SERVICE_URL/order/${ORDER_NUMBER}

查看结果

  1. 前往 Firestore 控制台
  2. 点击“数据”

73e14d69211d1539

11. 恭喜!

恭喜,您已完成本实验!

后续步骤:

探索其他 Cymbal Eats Codelab:

清理

为避免因本教程中使用的资源导致您的 Google Cloud 账号产生费用,请删除包含这些资源的项目,或者保留项目但删除各个资源。

删除项目

若要避免产生费用,最简单的方法是删除您为本教程创建的项目。