使用 Remote API 访问 App Engine

区域 ID

REGION_ID 是 Google 根据您在创建应用时选择的区域分配的缩写代码。此代码不对应于国家/地区或省,尽管某些区域 ID 可能类似于常用国家/地区代码和省代码。对于 2020 年 2 月以后创建的应用,REGION_ID.r 包含在 App Engine 网址中。对于在此日期之前创建的现有应用,网址中的区域 ID 是可选的。

详细了解区域 ID

Remote API 库允许任何 Python 客户端访问 App Engine 应用可用的服务。

例如,如果您的 App Engine 应用使用 Datastore 或 Google Cloud Storage,则 Python 客户端可以使用 Remote API 访问这些存储资源。

您可以使用 Remote API 从本地计算机上运行的应用或本地交互式 Remote API shell 访问应用的数据存储。Remote API 与实际服务交互,因此该访问会占用配额和可计费的资源。

在应用中启用 Remote API 访问

要为应用启用 Remote API,最简单的方法是在应用的 app.yaml 文件中使用 builtins 指令,该指令会指定默认网址 /_ah/remote_api/。但是,您可以在同一文件中使用 url 指令来指定其他网址。

builtin

app.yaml 文件中的 builtins 指令允许通过默认网址 /_ah/remote_api 访问 Remote API:

runtime: python27
api_version: 1
threadsafe: true

builtins:
- remote_api: on

URL

使用 app.yaml 中的 url 指令可以指定用于 Remote API 的其他网址:

- url: /some-URL/*
  script: google.appengine.ext.remote_api.handler.application

确保在执行此更改后将应用部署到 App Engine。

使用 Remote API shell

Python SDK 包含一个 Remote API shell,允许您对应用使用的 App Engine 服务调用 Python 命令。您无需提供任何其他身份验证,因为此时会自动使用您将应用上传到 App Engine 所用的相同凭据。

启动 Remote API shell 的方法如下:

  1. 在本地计算上的某个终端窗口中调用如下命令:

    SDK-INSTALL-DIRECTORY/remote_api_shell.py -s YOUR-PROJECT-ID. REGION_ID.r.appspot.com

    [SDK-INSTALL-DIRECTORY] 替换为转向 Python 版 App Engine SDK 的路径,将 [YOUR-PROJECT-ID] 替换为您的项目 ID。

  2. 在所显示的交互式 shell 中,调用要运行的 Python 命令。例如,如果您的应用使用 Datastore,则可以调用以下 ndb 查询来提取 10 条记录:

     >>> from google.appengine.ext import ndb
     >>>
     >>> # Fetch 10 keys from the datastore
     >>> ndb.Query().fetch(10, keys_only=True)
    

在本地客户端中使用 Remote API

您还可以在本地应用中使用 Remote API 来访问 App Engine 中运行的应用所用的服务。

要在本地应用中使用 Remote API,请执行以下操作:

  1. 启用 Remote API

  2. 导出 Python 目录的 PYTHONPATH 环境变量,例如:

     export PYTHONPATH=/usr/somedir/v3/bin/python2.7
    

    将该路径替换为 Python 所在位置的实际值。

  3. 将适用于 Python 的 App Engine SDK 的位置添加到 PYTHONPATH

     export GAE_SDK_ROOT="/usr/local/home/mydir/google_appengine"
     export PYTHONPATH=${GAE_SDK_ROOT}:${PYTHONPATH}
    

    将上面显示的 SDK 路径替换为 App Engine SDK 的实际路径。

  4. 在您的客户端代码中,导入 dev_appserver 并调用 dev_appserver.fix_sys_path(),以确保正确导入所有 App Engine SDK 模块:

    try:
        import dev_appserver
        dev_appserver.fix_sys_path()
  5. 将以下 remote_api_stub 代码添加到应用中,确保在代码中将项目 ID 传递给该代码:

    remote_api_stub.ConfigureRemoteApiForOAuth(
        '{}.appspot.com'.format(project_id),
        '/_ah/remote_api')

    如果您未使用 Remote API 的默认网址 /_ah/remote_api,则必须更改上面的代码,体现您所使用的网址。如需了解 remote_api_stub.ConfigureRemoteApiForOAuth 的定义和文档,请参阅 SDK 文件 [SDK-INSTALL-DIRECTORY]/google/appengine/ext/remote_api/remote_api_stub.py

  6. 添加任何必要的 App Engine 导入和 Python 代码,以访问所需的 App Engine 服务。以下示例代码访问项目的数据存储:

    
    import argparse
    
    try:
        import dev_appserver
        dev_appserver.fix_sys_path()
    except ImportError:
        print('Please make sure the App Engine SDK is in your PYTHONPATH.')
        raise
    
    from google.appengine.ext import ndb
    from google.appengine.ext.remote_api import remote_api_stub
    
    def main(project_id):
        remote_api_stub.ConfigureRemoteApiForOAuth(
            '{}.appspot.com'.format(project_id),
            '/_ah/remote_api')
    
        # List the first 10 keys in the datastore.
        keys = ndb.Query().fetch(10, keys_only=True)
    
        for key in keys:
            print(key)
    
    if __name__ == '__main__':
        parser = argparse.ArgumentParser(
            description=__doc__,
            formatter_class=argparse.RawDescriptionHelpFormatter)
        parser.add_argument('project_id', help='Your Project ID.')
    
        args = parser.parse_args()
    
        main(args.project_id)
  7. 将应用部署到 App Engine,并启动 Remote API 客户端:

     python your-client.py YOUR-PROJECT-ID
    

    your-client.py 替换为客户端模块,并将 YOUR-PROJECT-ID 替换为项目 ID。这种处理方式假设您的客户端按照 client.py 代码示例接受项目 ID 作为命令行输入。

限制和最佳做法

remote_api 模块竭尽全力来确保尽可能表现得像本地 App Engine 数据存储区。在某些情况下,这意味着处理效率可能会相对低下。使用 remote_api 时,需牢记以下几点:

每一个数据存储区请求都需要一个往返

因为您通过 HTTP 访问数据存储区,所以开销和延迟会略多于本地访问数据存储区。为了加快速度和降低负载,请尝试通过批处理获取内容和写入内容,并从查询提取批量实体来限制往返次数。这不仅适用于 remote_api,也适用于一般的数据存储区,因为批处理操作仅被视为单个数据存储区操作。 例如,不用:

for key in keys:
  rec = key.get()
  rec.foo = bar
  rec.put()

而使用:

records = ndb.get_multi(keys)
for rec in records:
  rec.foo = bar
  ndb.put_multi(records)

上述两个示例效果相同,但后者总计只需要两个往返,而前者针对每一实体都需要两个往返。

对 remote_api 使用配额的请求

由于 remote_api 通过 HTTP 运行,您进行的每个数据存储区调用都会产生对以下配额的使用:HTTP 请求、输入和输出字节,以及常规的数据存储区配额。如果您使用 remote_api 来进行批量更新,请牢记这一点。

1 MB API 限制

在本地运行时,对于 API 请求和响应的 1MB 限制仍然适用。如果您的实体特别大,则可能需要限制一次提取或放置的数量,以免超过此限制。但这与最小化往返次数存在冲突,因此最好的建议是尽可能使用最大的批而不超过请求或响应大小限制。但对于大多数实体来说,这不太可能成为问题。

避免迭代查询

数据存储区访问的一个共同的模式如下:

q = MyModel.query()
for entity in q:
  # Do something with entity

当您进行此操作时,SDK 以 20 为批次从数据存储区中提取实体,每当用完现有批次时就提取一个新的批次。由于每一批次都必须在单独请求中由 remote_api 提取,因此这种做法的效率没那么高。反之,remote_api 对每一批次执行一个全新的查询,使用偏移功能来进一步接近结果。

如果您知道所需实体的数目,则可以通过请求所需的数量来在一个请求中完成全部提取。

entities = MyModel.query().fetch(100)
for entity in entities:
  # Do something with entity

如果您不知道需要多少实体,则可以使用游标高效地迭代大型结果集。这还可以避开对常规数据存储区查询施加的 1000 实体的限制。

事务效率较低

为通过 remote_api 实施事务,它累积有关在事务内提取的实体的信息,以及在事务内放置或删除的实体的副本。事务被提交时会向 App Engine 服务器发送所有这些信息。在 App Engine 服务器中,事务必须再次提取事务中所使用的所有实体,验证这些实体未被修改,然后放置和删除事务所做的所有更改并将其提交。如果有冲突,服务器将回滚事务并通知客户端,然后客户端必须再次重复这一过程。

此方法可行,并能准确提供由事务在本地数据存储区上提供的功能,但是效率相当低下。在必要时,请务必使用事务,但出于效率的考虑,建议尽量限制所执行事务的数量和复杂性。