第 3 单元:从 Google Cloud NDB 迁移到 Cloud Datastore

1. 概览

这一系列的 Codelab(自定进度的动手教程)旨在帮助 Google App Engine(标准环境)开发者通过一系列迁移来指导他们的应用现代化。最重要的步骤是摆脱原始的运行时捆绑服务,因为下一代运行时更加灵活,为用户提供了更多的服务选项。移至新一代运行时,您可以更轻松地与 Google Cloud 产品集成,使用更多支持的服务,并支持当前的语言版本。

本可选教程向开发者展示了如何从 Cloud NDB 迁移到 Cloud Datastore,以作为与 Datastore 服务通信的客户端库。偏好使用 NDB 的开发者可以继续使用它,因为 NDB 与 Python 3 兼容,因此此迁移是可选的。此迁移仅适用于希望构建一致的代码库和共享库的用户,这些用户已在使用 Cloud Datastore 的其他应用。“背景”部分对此进行了说明。

您将了解如何

  • 使用 Cloud NDB(如果您不熟悉该库)
  • 从 Cloud NDB 迁移到 Cloud Datastore
  • 进一步将应用迁移到 Python 3

所需条件

  • 具有有效 GCP 结算账号的 Google Cloud Platform 项目
  • 基本 Python 技能
  • 基本 Linux 命令的实践知识
  • 具备开发和部署 App Engine 应用的基础知识
  • 有效的模块 2 App Engine 2.x3.x 应用。

调查问卷

如何使用此 Codelab?

仅通读 阅读并完成练习

2. 背景

虽然 Cloud NDB 是面向长期使用 App Engine 的开发者的出色 Datastore 解决方案,并且有助于过渡到 Python 3,但 App Engine 开发者并非只能通过这种方式访问 Datastore。当 App Engine 的 Datastore 在 2013 年成为独立产品时,Google 创建了新的 Cloud Datastore 客户端库,以便所有用户都可以使用 Datastore。

Python 3 App Engine 和 App Engine 开发者应使用 Cloud Datastore(而非 Cloud NDB)。建议 Python 2 App Engine 开发者从 ndb 迁移到 Cloud NDB,并从那里移植到 Python 3,但他们也可以选择进一步迁移到 Cloud Datastore。对于已经有代码使用 Cloud Datastore(例如刚才提到的代码)并希望在所有应用中创建共享库的开发者来说,这是一个合理的决定。代码重用和代码一致性都是最佳实践,两者都有助于降低总体维护成本,如下所示:

从 Cloud NDB 迁移到 Cloud Datastore

  • 让开发者能够专注于单个代码库,以实现 Datastore 访问
  • 避免维护一些使用 Cloud NDB 的代码,另一些使用 Cloud Datastore 的代码
  • 提高代码库的一致性,并提升代码的可重用性
  • 支持使用通用/共享库,有助于降低总体维护成本

此迁移具有以下主要步骤:

  1. 设置/准备工作
  2. 将 Cloud NDB 替换为 Cloud Datastore 客户端库
  3. 更新应用

3. 设置/准备工作

在开始学习本教程的主要部分之前,让我们设置项目、获取代码,然后部署基准应用,以便我们知道我们从工作代码开始。

1.设置项目

如果您已完成模块 2 Codelab,我们建议您重复使用同一项目(和代码)。或者,您可以创建一个全新的项目或重复使用另一个现有项目。确保该项目具有有效的结算账号,并且已启用 App Engine(应用)。

2. 获取基准示例应用

前提条件之一是拥有有效的工作模块 2 示例应用。如果您已完成该教程,请使用您的解决方案。您可以立即完成此步骤(上面的链接),也可以选择跳过此步骤,然后复制模块 2 代码库(下面的链接)。

无论您是使用自己的代码还是我们的代码,我们都能在模块 2 中。本模块 3 Codelab 将会逐步引导您完成每个步骤,完成之后,它应该类似于 FINISH 点的代码。本教程有 Python 2 和 3 版本,因此请抓取下面的正确代码库。

Python 2

Python 2 模块 2 启动文件(您或我们的文件)的目录应如下所示:

$ ls
README.md               appengine_config.py     requirements.txt
app.yaml                main.py                 templates

学完模块 2 教程后,您还将有一个包含 Flask 及其依赖项的 lib 文件夹。如果您没有 lib 文件夹,请使用 pip install -t lib -r requirements.txt 命令创建该文件夹,以便我们可以在下一步中部署此基准应用。如果您同时安装了 Python 2 和 3,建议您使用 pip2 而不是 pip,以免与 Python 3 混淆。

Python 3

Python 3 模块 2 启动文件(您或我们的文件)的目录应如下所示:

$ ls
README.md               main.py                 templates
app.yaml                requirements.txt

对于 Python 3,libappengine_config.py 均不适用。

3. (重新)部署模块 2 应用

您现在需要执行的剩余预处理步骤:

  1. 熟悉 gcloud 命令行工具(必要的话)
  2. (重新)将模块 1 代码部署到 App Engine(必要的话)

当您成功执行这些步骤并确认操作有效后,我们将在本教程中进行本教程,从配置文件开始。

4. 将 Cloud NDB 替换为 Cloud Datastore 客户端库

唯一的配置更改是 requirements.txt 文件中的一个微小的软件包交换。

1. 更新requirements.txt

完成模块 2 后,您的 requirements.txt 文件应如下所示:

  • 之前(Python 2 和 3):
Flask==1.1.2
google-cloud-ndb==1.7.1

更新 requirements.txt,将 Cloud NDB 库 (google-cloud-ndb) 替换为最新版本的 Cloud Datastore 库 (google-cloud-datastore),同时保留 Flask 的条目,但请注意,与 Python 2 兼容的 Cloud Datastore 的最终版本为 1.15.3:

  • 之后(Python 2):
Flask==1.1.2
google-cloud-datastore==1.15.3
  • 之后(Python 3):
Flask==1.1.2
google-cloud-datastore==2.1.0

请注意,该代码库的维护频率高于本教程,因此 requirements.txt 文件可能会反映较新的版本。我们建议使用每个库的最新版本,但如果这些版本无法正常运行,您可以回滚到旧版本。上述版本号是此 Codelab 上次更新时的最新版本号。

2. 其他配置文件

其他配置文件(即 app.yamlappengine_config.py)应与上一个迁移步骤中的保持不变:

  • app.yaml 应(仍然)引用第三方捆绑软件包 grpciosetuptools
  • appengine_config.py 应(仍然)将 pkg_resourcesgoogle.appengine.ext.vendor 指向 lib 中的第三方资源。

现在,我们来了解应用文件。

5. 更新应用文件

template/index.html 没有变化,但 main.py 有一些更新。

1. 导入

导入部分的起始代码应如下所示:

  • 之前:
from flask import Flask, render_template, request
from google.cloud import ndb

google.cloud.ndb 导入替换为 Cloud Datastore 的导入:google.cloud.datastore。由于 Datastore 客户端库不支持在实体中自动创建时间戳字段,因此还需导入标准库 datetime 模块以手动创建一个时间戳字段。按照惯例,标准库导入应位于第三方软件包导入之上。完成这些更改后,它应如下所示:

  • 之后:
from datetime import datetime
from flask import Flask, render_template, request
from google.cloud import datastore

2. 初始化和数据模型

初始化 Flask 后,模块 2 示例应用创建的 NDB 数据模型类及其字段如下所示:

  • 之前:
app = Flask(__name__)
ds_client = ndb.Client()

class Visit(ndb.Model):
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

Cloud Datastore 库没有此类,因此请删除 Visit 类声明。您仍然需要一个客户端来与 Datastore 通信,因此请将 ndb.Client() 更改为 datastore.Client()。Datastore 库更加“灵活”,允许您创建实体,而无需像 NDB 那样“预先声明”其结构。更新后,main.py 的这一部分应如下所示:

  • 之后:
app = Flask(__name__)
ds_client = datastore.Client()

3. Datastore 访问权限

迁移到 Cloud Datastore 需要更改您(在用户层面)创建、存储和查询 Datastore 实体的具体方式。对于您的应用,此迁移的难度取决于 Datastore 代码的复杂程度。在我们的示例应用中,我们尝试尽可能简化更新过程。以下是我们的起始代码:

  • 之前:
def store_visit(remote_addr, user_agent):
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    with ds_client.context():
        return (v.to_dict() for v in Visit.query().order(
                -Visit.timestamp).fetch_page(limit)[0])

借助 Cloud Datastore,您可以创建一个通用实体,并使用“键”来标识实体中的分组对象。使用键值对的 JSON 对象 (Python dict) 创建数据记录,然后使用预期 put() 将其写入 Datastore。使用 Datastore 进行查询类似,但更直接。您可以在此处查看等效的 Datastore 代码有何不同:

  • 之后:
def store_visit(remote_addr, user_agent):
    entity = datastore.Entity(key=ds_client.key('Visit'))
    entity.update({
        'timestamp': datetime.now(),
        'visitor': '{}: {}'.format(remote_addr, user_agent),
    })
    ds_client.put(entity)

def fetch_visits(limit):
    query = ds_client.query(kind='Visit')
    query.order = ['-timestamp']
    return query.fetch(limit=limit)

如上所示,更新 store_visit()fetch_visits() 的函数正文,使其签名与之前版本保持一致。主处理程序 root() 没有任何变化。完成这些更改后,您的应用现在已配置为使用 Cloud Datastore,可以进行测试了。

6. 总结/清理

部署应用

使用 gcloud app deploy 重新部署应用,并确认应用可正常运行。您的代码现在应与模块 3 代码库文件夹中的内容相匹配:

如果您跳过本系列,但未执行任何上述 Codelab,则应用本身不会更改;它会记录对主网页 (/) 的所有访问,在您访问过该网站后,将如下所示:

visitme 应用

恭喜您完成本模块 3 的 Codelab。现在,您已经知道可以使用 Cloud NDB Cloud Datastore 客户端库来访问 Datastore。迁移到后者后,您现在可以获得共享库、通用代码和代码重用的好处,从而提高一致性并降低维护成本。

可选:清理

何不准备好清理,以避免在进入下一个迁移 Codelab 时继续计费?作为现有开发者,您可能已经了解 App Engine 的价格信息

可选:停用应用

如果您尚未准备好学习下一个教程,请停用应用,以免产生费用。准备好继续学习下一个 Codelab 后,您可以重新启用该功能。应用被停用后,不会产生任何流量产生费用,但如果超出免费配额,您可能还需要为 Datastore 使用量付费,因此请删除足够的数据,使使用量低于该上限。

另一方面,如果您不打算继续进行迁移,并希望完全删除所有内容,可以关闭项目

后续步骤

您可以从此处开始探索以下迁移模块:

  • 模块 3 奖励:继续阅读奖励部分,了解如何移植到 Python 3 和下一代 App Engine 运行时。
  • 模块 7:App Engine 推送任务队列(如果您使用 [推送] 任务队列,此为必需参数)
    • 向模块 1 应用添加了 App Engine taskqueue 推送任务
    • 为用户在第 8 模块中迁移到 Cloud Tasks 做准备
  • 模块 4使用 Docker 迁移到 Cloud Run
    • 使用 Docker 将应用容器化,以便在 Cloud Run 上运行
    • 让您继续使用 Python 2
  • 模块 5:使用 Cloud Buildpack 迁移到 Cloud Run
    • 使用 Cloud Buildpacks 将应用容器化,以便在 Cloud Run 上运行
    • 无需了解 Docker、容器或 Dockerfiles
    • 要求您已将应用迁移到 Python 3
  • 模块 6:迁移到 Cloud Firestore
    • 迁移到 Cloud Firestore 以访问 Firebase 功能
    • 虽然 Cloud Firestore 支持 Python 2,但此 Codelab 仅提供 Python 3 版本。

7. 奖励:迁移到 Python 3

为了使用最新的 App Engine 运行时和功能,我们建议您迁移到 Python 3。在我们的示例应用中,Datastore 是我们使用的唯一内置服务,并且由于我们已从 ndb 迁移到 Cloud NDB,因此现在可以移植到 App Engine 的 Python 3 运行时。

概览

虽然移植到 Python 3 不在 Google Cloud 教程的范围内,但本 Codelab 的这一部分可让开发者了解 Python 3 App Engine 运行时的不同之处。新一代运行时的一项出色功能是简化了对第三方软件包的访问:无需在 app.yaml 中指定内置软件包,也无需复制或上传内置库;只需在 requirements.txt 中列出这些库,系统就会隐式安装它们。

由于我们的示例非常基本 Cloud Datastore 与 Python 2-3 兼容,因此无需将任何应用代码显式移植到 3.x:该应用可在 2.x 和 3.x 上未经修改地运行,这意味着在这种情况下,唯一需要更改的是配置:

  1. 简化了 app.yaml 以引用 Python 3,并移除了对捆绑的第三方库的引用。
  2. 删除 appengine_config.pylib 文件夹,因为它们不再必要。

main.pytemplates/index.html 应用文件保持不变。

更新requirements.txt

支持 Python 2 的 Cloud Datastore 的最终版本为 1.15.3。将 requirements.txt 更新为 Python 3 的最新版本(现在可能已更新)。编写本教程时,最新版本为 2.1.0,因此请修改该行,使其如下所示(或修改为最新版本):

google-cloud-datastore==2.1.0

简化 app.yaml

之前:

此示例应用的唯一实际更改是大幅缩短 app.yaml。提醒一下,以下是我们在第 3 模块结束时 app.yaml 中的内容

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: grpcio
  version: 1.0.0
- name: setuptools
  version: 36.6.0

之后:

在 Python 3 中,threadsafeapi_versionlibraries 指令均已弃用;所有应用都被假定为线程安全,并且 api_version 不在 Python 3 中使用。App Engine 服务上不再预安装内置的第三方软件包,因此 libraries 也已被弃用。如需详细了解这些变更,请参阅有关 app.yaml 变更的文档。因此,您应从 app.yaml 中删除这三个版本,并更新为受支持的 Python 3 版本(见下文)。

可选:使用 handlers 指令

此外,用于将流量定向到 App Engine 应用的 handlers 指令也已弃用。由于下一代运行时要求 Web 框架管理应用路由,因此所有“处理程序脚本”都必须更改为“auto”。结合上述更改,您将得到以下 app.yaml

runtime: python38

handlers:
- url: /.*
  script: auto

如需详细了解 script: auto,请参阅 app.yaml 参考页面

移除 handlers 指令

由于 handlers 已被弃用,您也可以移除整个部分,只保留单行 app.yaml

runtime: python38

默认情况下,这将启动 Gunicorn WSGI Web 服务器,该服务器适用于所有应用。如果您熟悉 gunicorn,那么当它默认通过最基本的 app.yaml 启动时,系统会执行以下命令:

gunicorn main:app --workers 2 -c /config/gunicorn.py

可选:使用 entrypoint 指令

不过,如果您的应用需要特定的启动命令,则可以使用 entrypoint 指令指定该命令,从而生成如下所示的 app.yaml

runtime: python38
entrypoint: python main.py

此示例专门请求使用 Flask 开发服务器,而不是 gunicorn。您还必须向应用添加用于启动开发服务器的代码,以便通过在 main.py 底部添加以下小部分代码,在端口 8080 上的 0.0.0.0 接口上启动应用:

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=True)

如需详细了解 entrypoint,请参阅 app.yaml 参考页面。如需查看更多示例和最佳实践,请参阅 App Engine 标准环境启动文档以及 App Engine 柔性环境启动文档

删除 appengine_config.pylib

删除 appengine_config.py 文件和 lib 文件夹。在迁移到 Python 3 时,App Engine 会获取并安装 requirements.txt 中列出的软件包。

appengine_config.py 配置文件用于识别第三方库/软件包,无论您是自行复制这些库/软件包,还是使用 App Engine 服务器上已提供的库/软件包(内置)。迁移到 Python 3 时,主要变更总结如下:

  1. 不捆绑复制的第三方库(在 requirements.txt 中列出)
  2. 没有 pip installlib 文件夹中,即无 lib 文件夹期限
  3. 未在 app.yaml 中列出内置的第三方库
  4. 无需引用应用到第三方库,因此没有 appengine_config.py 文件

只需在 requirements.txt 中列出所有必需的第三方库即可。

部署应用

重新部署应用,确保其正常运行。您还可以确认您的解决方案与模块 3 中的 Python 3 示例代码的接近程度。如需直观呈现与 Python 2 的差异,请将代码与其 Python 2 版本进行比较。

恭喜您完成了模块 3 中的奖励步骤!请参阅有关为 Python 3 运行时准备配置文件的文档。最后,查看上文中的摘要,了解后续步骤和清理操作。

准备您的申请

当需要迁移应用时,您必须将 main.py 和其他应用文件移植到 3.x,因此,最佳做法是尽量编写 2.x 版应用以“向前兼容”的方式传送。

网上有很多资源可以帮助您实现这一目标,但以下是一些关键提示:

  1. 确保所有应用依赖项都与 3.x 完全兼容
  2. 确保您的应用在至少 2.6(最好是 2.7)版本的 Python 上运行
  3. 确保应用通过整个测试套件(且覆盖率至少达到 80%)
  4. 使用兼容性库,例如 six、Future 和/或 Modernize
  5. 了解 2.x 与 3.x 之间不向后兼容的主要区别
  6. 任何 I/O 都可能会导致 Unicode 与字节字符串不兼容

设计示例应用时要牢记所有这些,因此为什么该应用可以直接在 2.x 和 3.x 上运行,所以我们可以集中精力向您展示使用下一代平台需要更改的内容。

8. 其他资源

App Engine 迁移模块 Codelab 问题/反馈

如果您发现本 Codelab 存在任何问题,请先搜索您的问题,然后再提交。用于搜索和创建新问题的链接:

迁移时可参考的资源

下表中提供了指向模块 2(开始)和模块 3(完成)的代码库文件夹的链接。您还可以从所有 App Engine 迁移的代码库中访问这些示例,您可以克隆该代码库或下载 ZIP 文件。

Codelab

Python 2

Python 3

模块 2

代码

代码

模块 3

代码

代码

App Engine 资源

以下是有关此特定迁移的其他资源: