迁移到 Cloud NDB

App Engine 位置

App Engine 是区域级的,这意味着运行您的应用的基础架构位于特定区域并由 Google 代管,以使其在该区域内的所有可用区以冗余方式提供。

选择要在哪个区域运行应用时,首先要考虑该区域是否能满足您的延迟时间、可用性或耐用性要求。通常,您可以选择距离应用的用户最近的地区,但也要考虑提供 App Engine 的位置以及应用使用的其他 Google Cloud 产品和服务的位置。跨多个位置使用服务可能会影响应用的延迟时间及其价格

应用的区域一经设置,便无法更改。

如果您已创建 App Engine 应用,则可以通过执行以下任一操作来查看其区域:

Cloud NDB 是一个 Python 客户端库,用于替换 App Engine NDB。App Engine NDB 允许 Python 2 应用在 Datastore 数据库中存储和查询数据。Cloud NDB 允许 Python 2 和 Python 3 应用在同一数据库中存储和查询数据,但管理这些数据库的产品已从 Datastore 更改为 Datastore 模式的 Firestore。虽然 Cloud NDB 库可以访问使用 App Engine NDB 创建的任何数据,但无法通过 App Engine NDB 访问某些使用 Cloud NDB 存储的结构化数据类型。因此,迁移到 Cloud NDB 应被视为不可撤销。

我们建议您在将应用升级到 Python 3 之前先迁移到 Cloud NDB。使用这种增量式迁移方法,您可以在迁移过程中维护一个功能齐全和可测试的应用。

Cloud NDB 旨在替换 App Engine NDB 中的功能,因此它不支持 Datastore 模式的 Firestore 的新功能。我们建议新的 Python 3 应用使用 Datastore 模式客户端库,而不是 Cloud NDB。

如需详细了解 Cloud NDB,请参阅 GitHub 上的以下页面:

App Engine NDB 和 Cloud NDB 的对比

相似之处:

  • Cloud NDB 几乎支持 App Engine NDB 支持的所有功能,只在方法语法上存在微小差异。

差异:

  • 依赖于 App Engine Python 2.7 运行时特定服务的 App Engine NDB API 已更新或已从 Cloud NDB 中移除。

  • Python 3 和 Django 的新功能无需使用 google.appengine.ext.ndb.django_middleware。您只需几行代码即可轻松编写自己的中间件。

  • App Engine NDB 要求应用和 Datastore 数据库位于同一 Google Cloud 项目中,并且 App Engine 会自动提供凭据。Cloud NDB 可以访问任何项目中的 Datastore 模式数据库,只要您正确地对客户端进行身份验证即可。这与其他 Google Cloud API 和客户端库一致。

  • Cloud NDB 不使用 App Engine Memcache 服务来缓存数据。

    相反,Cloud NDB 可以在由 MemorystoreRedis Labs 或其他系统管理的 Redis 内存数据存储区中缓存数据。虽然目前仅支持 Redis 数据存储区,但 Cloud NDB 已对抽象 GlobalCache 接口进行了泛化和定义缓存,该接口可支持额外的具体实现。

    要访问 Memorystore for Redis,您的应用需要使用无服务器 VPC 访问通道。

    Memorystore for Redis 和无服务器 VPC 访问通道都不提供免费层级,并且这些产品可能无法在您应用的地区中提供。如需了解详情,请参阅开始迁移之前

如需查看完整的差异列表,请参阅 Cloud NDB GitHub 项目的迁移说明

代码示例:

开始迁移之前

开始迁移之前:

  1. 如果尚未设置,请设置 Python 开发环境以使用与 Google Cloud 兼容的 Python 版本,并安装用于创建独立 Python 环境的测试工具。

  2. 确定是否需要缓存数据

  3. 如果您需要缓存数据,请确保无服务器 VPC 访问通道和 Memorystore for Redis 支持应用的地区

  4. 了解 Datastore 模式权限

确定是否需要缓存数据

如果您的应用需要缓存数据,请注意 Memorystore for Redis 和无服务器 VPC 访问通道没有免费层级,并且不支持所有 Google Cloud 区域。

一般来说:

  • 如果您的应用经常读取相同的数据,缓存可能会缩短延迟时间。

  • 您的应用处理的请求越多,缓存可能产生的影响就越大。

要了解您目前对缓存数据的依赖程度,请查看 Memcache 信息中心,了解缓存命中与未命中的比率。如果该比率较高,使用数据缓存可能会对减少应用的延迟时间产生很大影响。

打开 App Engine Memcache

如需了解价格,请参阅 Memorystore 价格无服务器 VPC 访问通道价格

确认应用的地区

如果需要缓存数据,请确保 Memorystore for Redis 和无服务器 VPC 访问通道支持应用的地区:

  1. 查看应用的区域,该区域显示在 Google Cloud 控制台中的 App Engine 信息中心顶部附近。

    转到 App Engine

    该地区会显示在页面顶部附近,就在应用网址的下方。

  2. 确认您的应用位于无服务器 VPC 访问通道支持的地区之一。

  3. 通过访问“创建连接器”页面并查看地区列表中的地区,确认您的应用是否位于 Memorystore for Redis 支持的地区之一。

    转到无服务器 VPC 访问通道

如果您的应用不在 Memorystore for Redis 和无服务器 VPC 访问通道支持的地区中,请执行以下操作:

  1. 创建 Google Cloud 项目。

  2. 在项目中创建一个新的 App Engine 应用,并选择一个受支持的区域。

  3. 在新项目中创建应用使用的 Google Cloud 服务。

    或者,您可以更新应用以使用旧项目中的现有服务,但在使用其他项目和地区中的服务时,价格和资源用量可能有所不同。如需了解详情,请参阅每种服务的文档。

  4. 将应用部署到新项目。

了解 Datastore 模式权限

与 Google Cloud 服务之间进行的每一次交互操作都需要经过授权。例如,如需在 Datastore 模式数据库中存储或查询数据,您的应用需要提供有权访问该数据库的账号的凭据。

默认情况下,您的应用会提供 App Engine 默认服务账号的凭据,该账号有权访问您的应用所在项目中的数据库。

如果满足以下任一条件,您需要使用明确提供凭据的备用身份验证技术:

  • 您的应用与 Datastore 模式数据库位于不同的 Google Cloud 项目中。

  • 您已更改分配给默认 App Engine 服务账号的角色。

如需了解其他身份验证方法,请参阅为服务器到服务器的生产应用设置身份验证

迁移过程概览

要迁移到 Cloud NDB,请执行以下操作:

  1. 更新您的 Python 应用

    1. 安装 Cloud NDB 客户端库。

    2. 更新导入语句以从 Cloud NDB 导入模块。

    3. 添加用于创建 Cloud NDB 客户端的代码。客户端可以读取应用的环境变量,并使用数据进行 Datastore 模式身份验证。

    4. 添加使用客户端运行时环境的代码,以使缓存和事务在各线程之间保持独立。

    5. 移除或更新使用不再受支持的方法和属性的代码。

  2. 启用缓存

  3. 测试您的更新

  4. 将应用部署到 App Engine

    与您对应用所做的任何更改一样,请考虑使用流量拆分来逐渐增加流量。请先仔细监控应用是否存在任何数据库问题,若无再将更多流量路由到更新后的应用。

更新您的 Python 应用

为 Python 应用安装 Cloud NDB 库

要在 App Engine Python 应用中安装 Cloud NDB 客户端库,请执行以下操作:

  1. 更新 app.yaml 文件。按照适用于您的 Python 版本的说明操作:

    Python 2

    对于 Python 2 应用,请添加最新版本的 grpciosetuptools 库。

    下面是一个 app.yaml 文件示例:

    runtime: python27
    threadsafe: yes
    api_version: 1
    
    libraries:
    - name: grpcio
      version: latest
    - name: setuptools
      version: latest
    

    Python 3

    对于 Python 3 应用,请使用受支持的 Python 3 版本指定 runtime 元素,并删除不必要的行。例如,您的 app.yaml 文件可能如下所示:

    runtime: python310 # or another support version
    

    Python 3 运行时会自动安装库,因此您无需指定过往 Python 2 运行时中的内置库。如果您的 Python 3 应用在迁移时使用其他旧版捆绑服务,请保持 app.yaml 文件不变。

  2. 更新 requirements.txt 文件。 按照适用于您的 Python 版本的说明操作:

    Python 2

    将适用于 Cloud NDB 的 Cloud 客户端库添加到 requirements.txt 文件中的依赖项列表。

    google-cloud-ndb
    

    然后,运行 pip install -t lib -r requirements.txt 以更新应用的可用库列表。

    Python 3

    将适用于 Cloud NDB 的 Cloud 客户端库添加到 requirements.txt 文件中的依赖项列表。

    google-cloud-ndb
    

    App Engine 会在 Python 3 运行时的应用部署期间自动安装这些依赖项,因此如果存在 lib 文件夹,请删除它。

  3. 对于 Python 2 应用,如果您的应用使用 lib 目录中指定的内置库或复制的库,您必须在 appengine_config.py 文件中指定这些路径:

    import pkg_resources
    from google.appengine.ext import vendor
    
    # Set PATH to your libraries folder.
    PATH = 'lib'
    # Add libraries installed in the PATH folder.
    vendor.add(PATH)
    # Add libraries to pkg_resources working set to find the distribution.
    pkg_resources.working_set.add_entry(PATH)
    

    请务必使用 pkg_resources 模块,该模块确保您的应用会使用正确的客户端库发布版本。

    上例中的 appengine_config.py 文件假定 lib 文件夹位于当前工作目录中。如果您无法保证 lib 始终位于当前工作目录中,请指定 lib 文件夹的完整路径。例如:

    import os
    path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'lib')

    在您部署应用时,App Engine 会上传您在 appengine_config.py 文件中指定的目录中的所有库。

更新导入语句

NDB 模块的位置已移至 google.cloud.ndb。更新应用的 import 语句,如下表所示:

移除 替换为
from google.appengine.ext import ndb from google.cloud import ndb

创建 Cloud NDB 客户端

与基于 Google Cloud API 的其他客户端库一样,使用 Cloud NDB 的第一步是创建 Client 对象。客户端包含连接到 Datastore 模式所需的凭据和其他数据。例如:

from google.cloud import ndb

client = ndb.Client()

之前所述的默认授权场景中,Cloud NDB 客户端包含 App Engine 默认服务账号中的凭据,该账号有权与 Datastore 模式进行交互。如果您未使用此默认场景,请参阅应用默认凭据 (ADC),了解如何提供凭据。

使用客户端的运行时环境

除了提供与 Datastore 模式交互所需的凭据之外,Cloud NDB 客户端还包含返回运行时环境的 context() 方法。运行时环境会将缓存和事务请求与其他并发 Datastore 模式的交互隔离开来。

所有与 Datastore 模式的交互都需要在 NDB 运行时环境中进行。由于创建模型定义不会与 Datastore 模式交互,因此您可以在创建 Cloud NDB 客户端并检索运行时环境之前定义模型类,然后在请求处理程序中使用运行时环境从数据库中获取数据。

例如:

from google.cloud import ndb


class Book(ndb.Model):
    title = ndb.StringProperty()


client = ndb.Client()


def list_books():
    with client.context():
        books = Book.query()
        for book in books:
            print(book.to_dict())

多线程应用

Cloud NDB 客户端返回的运行时环境仅适用于单个线程。如果您的应用针对单个请求使用多个线程,您需要为使用 Cloud NDB 库的每个线程检索单独的运行时环境。

将运行时环境与 WSGI 框架结合使用

如果您的 Web 应用使用 WSGI 框架,您可以为每个请求自动创建新的运行时环境,方法是创建一个用于检索运行时环境的中间件对象,然后将应用封装在中间件对象中。

在以下将中间件与 Flask 搭配使用的示例中:

  • middleware 方法会在 NDB 客户端的运行时环境中创建一个 WSGI 中间件对象。

  • Flask 应用封装在中间件对象中。

  • Flask 将通过中间件对象传递每个请求,该对象会为每个请求检索新的 NDB 运行时环境。

from flask import Flask

from google.cloud import ndb


client = ndb.Client()


def ndb_wsgi_middleware(wsgi_app):
    def middleware(environ, start_response):
        with client.context():
            return wsgi_app(environ, start_response)

    return middleware


app = Flask(__name__)
app.wsgi_app = ndb_wsgi_middleware(app.wsgi_app)  # Wrap the app in middleware.


class Book(ndb.Model):
    title = ndb.StringProperty()


@app.route("/")
def list_books():
    books = Book.query()
    return str([book.to_dict() for book in books])

将运行时环境与 Django 结合使用

Cloud NDB 库不支持由 App Engine NDB 库提供的 Django 中间件。如果您在应用中使用了此中间件 (google.appengine.ext.ndb.django_middleware),请按照以下步骤更新您的应用:

  1. 使用 Django 的中间件系统为每个请求创建新的运行时环境。

    在以下示例中:

    • ndb_django_middleware 方法会创建一个 Cloud NDB 客户端。

    • middleware 方法会在 NDB 客户端的运行时环境中创建一个中间件对象。

    from google.cloud import ndb
    
    
    # Once this middleware is activated in Django settings, NDB calls inside Django
    # views will be executed in context, with a separate context for each request.
    def ndb_django_middleware(get_response):
        client = ndb.Client()
    
        def middleware(request):
            with client.context():
                return get_response(request)
    
        return middleware
    
    
  2. 在 Django settings.py 文件中,更新 MIDDLEWARE 设置,使其列出您创建的新中间件,而不是 google.appengine.ext.ndb.NdbDjangoMiddleware

现在,Django 将通过您在 MIDDLEWARE 设置中列出的中间件对象传递每个请求,并且此对象会为每个请求检索新的 NDB 运行时环境。

为已移除或已更改的 NDB API 更新代码

依赖于 App Engine 特定 API 和服务的 NDB API 已更新或已从 Cloud NDB 库中移除。

如果代码使用以下任何 NDB API,则需要更新该代码:

模型和模型属性

无法在 Cloud NDB 库中使用 google.appengine.ext.ndb.Model 中的以下方法,因为它们使用的 App Engine 专用 API 已不再可用。

已移除的 API 替换
Model.get_indexes
Model.get_indexes_async
Model._deserialize
Model._serialize
Model.make_connection

下表介绍了 Cloud NDB 库中已更改的特定 google.appengine.ext.ndb.Model 属性:

属性 更改
TextProperty google.cloud.ndb.TextProperty 无法编入索引。如果您尝试设置 google.cloud.ndb.TextProperty.indexed,则会引发 NotImplementedError
StringProperty StringProperty 始终会编入索引。如果您尝试设置 google.cloud.ndb.StringProperty.indexed,则会引发 NotImplementedError
构造函数中包含 namekind 参数的所有属性。 namekind 必须是 str 数据类型,因为在 Python 3 中,unicode 已替换为 str

下表中的类和方法不再可用,因为它们使用的 App Engine 专用资源已不再可用。

已移除的 API 替换
google.appengine.ext.ndb.msgprop.MessageProperty
google.appengine.ext.ndb.msgprop.EnumProperty

如果您尝试创建这些对象,则会引发 NotImplementedError

来自 google.appengine.ext.ndb.model.Property
_db_get_value
_db_set_value
_db_set_compressed_meaning
_db_set_uncompressed_meaning
__creation_counter_global

这些方法依赖于已更改的 Datastore 模式协议缓冲区。

Model.make_connection

来自 google.appengine.ext.ndb.Key 的以下方法在 Cloud NDB 库中不可用。这些方法用于将键传递给不再受支持的 DB Datastore API(DB 是 App Engine NDB 的前身),以及从后者传递键。

已移除的 API 替换
Key.from_old_key
Key.to_old_key

此外,请注意以下变化:

App Engine NDB Cloud NDB
类型和字符串 ID 必须小于 500 个字节 类型和字符串 ID 必须小于 1500 个字节。
Key.app() 返回您在创建键时指定的项目 ID。 google.cloud.ndb.Key.app() 返回的值可能与传递给构造函数的原始 ID 不同。这是因为带有前缀的应用 ID(例如 s~example)是 App Engine 中的旧版标识符。它们已替换为等效的项目 ID,例如 example

查询

与 App Engine NDB 一样,Cloud NDB 提供了一个 QueryOptions 类 (google.cloud.ndb.query.QueryOptions),让您可以重复使用一组特定的查询选项,而不是为每个查询重新定义这些选项。但是,Cloud NDB 中的 QueryOptions 不会继承 google.appengine.datastore.datastore_rpc.Configuration,因此不支持 ...datastore_rpc.Configuration 方法

此外,google.appengine.datastore.datastore_query.Order 已替换为 google.cloud.ndb.query.PropertyOrder。与 Order 类似,PropertyOrder 类可让您指定多个查询的排序顺序。PropertyOrder 构造函数与 Order 的构造函数相同。只有类的名称发生了更改。

已移除的 API 替换
来自 google.appengine.datastore.datastore_rpc.Configuration
deadline(value)
on_completion(value)
read_policy(value)
force_writes(value)
max_entity_groups_per_rpc(value)
max_allocate_ids_keys(value)
max_rpc_bytes(value)
max_get_keys(value)
max_put_entities(value)
max_delete_keys(value)

如需了解有关这些方法的说明,请参阅源代码

google.appengine.ext.ndb.Order
例如:
order=Order(-Account.birthday, Account.name)
google.cloud.ndb.PropertyOrder
例如:
google.cloud.ndb.PropertyOrder(-Account.birthday, Account.name)

Utils

ndb.utils 模块 (google.appengine.ext.ndb.utils) 不再可用。该模块中的大多数方法都在 App Engine NDB 内部,由于新 ndb 中的实现差异,部分方法已被舍弃,而其他方法已被新的 Python 3 功能淘汰。

例如,旧 utils 模块中的定位修饰器声明只有函数或方法的前 nn 个参数可以是定位参数。但是,Python 3 可以使用仅限关键字的参数实现这一目的。过去的编写形式:

@utils.positional(2)
def function1(arg1, arg2, arg3=None, arg4=None)
  pass

在 Python 3 中的编写形式如下:

def function1(arg1, arg2, *, arg3=None, arg4=None)
  pass

命名空间

借助命名空间,多租户应用可为每个租户分别使用数据孤岛,同时仍使用相同的 Datastore 模式数据库。也就是说,每个租户都会将数据存储在自己的命名空间中。

您可以在创建 Cloud NDB 客户端时指定默认命名空间,然后通过在客户端运行时环境中调用 Cloud NDB 方法来使用默认命名空间,而不是使用 App Engine 专用的 google.appengine.api.namespacemanager。这遵循与支持命名空间的其他 Google Cloud API 相同的模式。

已移除的 API 替换
google.appengine.api.namespace_manager.namespace_manager.set_namespace(str)
google.appengine.api.namespacemanager.getnamespace()
client=google.cloud.ndb.Client(namespace="my namespace")

with client.context() as context:
    key = ndb.Key("SomeKind", "SomeId")
       
key-non-default-namespace=ndb.Key("SomeKind," "AnotherId",
namespace="non-default-nspace")
所有其他 google.appengine.api.namespacemanager 方法

Tasklets

Tasklets 现在可以使用标准 return 语句返回结果,而不是引发 Return 异常。例如:

App Engine NDB 库 Cloud NDB 库
        @ndb.tasklet
        def get_cart():
          cart = yield
        CartItem.query().fetch_async()
          raise Return(cart)
       
        @ndb.tasklet
        def get_cart():
          cart = yield
        CartItem.query().fetch_async()
          return cart
        

请注意,您仍然可以通过引发 Return 异常在 Cloud NDB 中返回结果,但不建议这样做。

此外,以下 Tasklets 方法和子类不再可用,这主要是因为 NDB 环境在 Cloud NDB 库中的创建和使用方式发生了变化。

已移除的 API 替换
来自 google.appengine.api.ext.ndb.tasklets
add_flow_exception
make_context
make_default_context
set_context
来自 google.appengine.api.ext.ndb.tasklets
QueueFuture
ReducedFuture
SerialQueueFuture

异常

虽然 Cloud NDB 库中的 google.cloud.ndb.exceptions 模块包含 App Engine NDB 库所产生的很多相同异常,但新库中并不存在所有旧异常。下表列出了不再存在的异常:

已移除的 API 替换
来自 google.appengine.api.datastore_errors
BadKeyError
BadPropertyError
CommittedButStillApplying
EntityNotFoundError
InternalError
NeedIndexError
QueryNotFoundError
ReferencePropertyResolveError
Timeout
TransactionFailedError
TransactionNotFoundError
google.cloud.ndb.exceptions

启用数据缓存

Cloud NDB 可以在由 MemorystoreRedis Labs 或其他系统管理的 Redis 内存数据存储区中缓存数据。本指南介绍如何使用 Memorystore for Redis 缓存数据:

  1. 设置无服务器 VPC 访问通道

  2. 设置 Memorystore for Redis

  3. 将 Redis 连接网址添加到您的应用

  4. 创建 RedisCache 对象

设置无服务器 VPC 访问通道

您的应用只能通过无服务器 VPC 访问通道连接器与 Memorystore 通信。要设置无服务器 VPC 访问通道连接器,请执行以下操作:

  1. 创建无服务器 VPC 访问通道连接器

  2. 将您的应用配置为使用连接器

设置 Memorystore for Redis

如需设置 Memorystore for Redis,请执行以下操作:

  1. 在 Memorystore 中创建 Redis 实例。 在创建实例时:

  2. 记下您创建的 Redis 实例的 IP 地址和端口号。您将在为 Cloud NDB 启用数据缓存时用到此信息。

    请务必使用 gcloud beta 命令部署应用更新。只有 Beta 版命令才能更新您的应用以使用 VPC 连接器。

添加 Redis 连接网址

您可以通过将 REDIS_CACHE_URL 环境变量添加到应用的 app.yaml 文件来连接到 Redis 缓存。REDIS_CACHE_URL 的值采用以下格式:

redis://IP address for your instance:port

例如,您可以将以下行添加到应用的 app.yaml 文件中:

     env_variables:
      REDIS_CACHE_URL: redis://10.0.0.3:6379

创建和使用 Redis 缓存对象

如果您已将 REDIS_CACHE_URL 设为环境变量,则可以使用单行代码创建 RedisCache 对象,然后在设置运行时环境时将其传递到 Client.context() 以使用该缓存:

client = ndb.Client()
global_cache = ndb.RedisCache.from_environment()

with client.context(global_cache=global_cache):
  books = Book.query()
  for book in books:
      print(book.to_dict())

如果您未将 REDIS_CACHE_URL 设置为环境变量,则需要构建一个 Redis 客户端,并将该客户端传递给 ndb.RedisCache() 构造函数。例如:

global_cache = ndb.RedisCache(redis.StrictRedis(host=IP-address, port=redis_port))

请注意,您不需要声明 Redis 客户端库的依赖项,因为 Cloud NDB 库已经依赖于 Redis 客户端库。

如需查看构建 Redis 客户端的示例,请参阅 Memorystore 示例应用

测试更新

要设置测试数据库并在本地运行应用,然后再将应用部署到 App Engine,请执行以下操作:

  1. 运行 Datastore 模式本地模拟器以存储和检索数据。

    请务必遵循设置环境变量的说明,以使您的应用连接到模拟器,而不是 Datastore 模式生产环境。

    如果您要使用预加载到数据库中的数据开始测试,还可以将数据导入模拟器

  2. 使用本地开发服务器运行您的应用。

    要确保在本地开发期间正确设置 GOOGLE_CLOUD_PROJECT 环境变量,请使用以下参数初始化 dev_appserver

    --application=PROJECT_ID

    PROJECT_ID 替换为您的 Google Cloud 项目 ID。您可以通过运行 gcloud config list project 命令或查看 Google Cloud 控制台中的项目页面来查找项目 ID。

部署应用

一旦您的应用能够在本地开发服务器上无错误地正常运行,请执行以下操作:

  1. 在 App Engine 上测试应用

  2. 如果应用无错误地正常运行,请使用流量拆分功能为更新后的应用缓慢增加流量。请先仔细监控应用是否存在任何数据库问题,然后再将更多流量路由到更新后的应用。

后续步骤