借助 Namespaces API,您可以在应用中轻松实现多租户功能,只需使用 NamespaceManager
软件包在 web.xml
中为每个租户选择命名空间字符串即可。
设置当前命名空间
您可以使用 NamespaceManager
获取、设置和验证命名空间。通过命名空间管理器,您能够为支持命名空间的 API 设置当前命名空间。您可以使用 web.xml
预先设置当前命名空间,数据存储区和 Memcache 会自动使用该命名空间。
大多数 App Engine 开发者会使用其 Google Workspace(以前称为 G Suite)网域作为当前命名空间。借助 Google Workspace,您可以将应用部署到您拥有的任何网域,从而可轻松利用此机制为不同网域配置不同的命名空间。然后,您可以使用这些单独的命名空间分隔各个网域中的数据。如需了解详情,请参阅映射自定义网域。
以下代码示例演示了如何将当前命名空间设置为用于映射网址的 Google Workspace 网域。值得注意的是,对于通过同一 Google Workspace 网域映射的所有网址,此字符串是相同的。
您可以在调用 servlet 方法之前使用 servlet 过滤器界面在 Java 中设置命名空间。以下简单示例演示了如何将 Google Workspace 网域用作当前命名空间:
必须在 web.xml
文件中配置命名空间过滤器。请注意,如果有多个过滤器条目,则要设置的第一个命名空间即为要使用的命名空间。
以下代码示例演示了如何在 web.xml
中配置命名空间过滤器:
如需详细了解 web.xml
文件以及如何将网址路径映射到 Servlet,请参阅部署描述符:web.xml
。
您还可以使用下方显示的 try
/finally
格式,为临时操作设置新的命名空间,并在操作完成后重置原始命名空间:
如果您没有为 namespace
指定值,命名空间将设置为空字符串。namespace
可以是任意字符串,但只能包含字母数字字符、句点、下划线和连字符且长度不超过 100 个字符。更明确地说,命名空间字符串必须与正则表达式 [0-9A-Za-z._-]{0,100}
匹配。
按照惯例,所有以“_
”(下划线)开头的命名空间将保留给系统使用。该系统命名空间规则不强制实施,但若不遵循,则很可能造成难以预料的负面影响。
避免数据泄露
与多租户应用相关的一个常见风险是,命名空间中可能会发生数据泄露。导致数据意外泄露的原因有很多,其中包括:
- 将命名空间与尚不支持命名空间的 App Engine API 一起使用。例如,Blobstore 不支持命名空间。如果将命名空间与 Blobstore 一起使用,需要避免对最终用户请求使用 Blobstore 查询,并避免使用来自不可信来源的 Blobstore 键。
- 通过
URL Fetch
或其他机制使用外部存储介质(而不是 Memcache 和数据存储区),但并未为命名空间提供划分方案。 - 根据用户的电子邮件网域设置命名空间。大多数情况下,您并不希望一个网域中的所有电子邮件地址都访问同一个命名空间。此外,使用电子邮件网域时,在用户登录后您的应用才可使用命名空间。
部署命名空间
以下部分介绍了如何使用其他 App Engine 工具和 API 来部署命名空间。
根据用户创建命名空间
部分应用需要根据用户来创建命名空间。如果要在用户级别为已登录用户划分数据,请考虑使用 User.getUserId()
,它会为用户返回唯一的永久 ID。以下代码示例演示了如何使用 Users API 实现此目的:
通常,基于用户创建命名空间的应用还会为不同用户提供特定的着陆页。在这些情况下,应用需要提供一个网址架构,以指示向用户显示哪个着陆页。
将命名空间与数据存储区一起使用
默认情况下,数据存储区使用命名空间管理器中的当前命名空间设置来处理数据存储区请求。Key
或 Query
对象创建完成时,API 会将此当前命名空间应用于这些对象。因此,如果应用在序列化表单中存储 Key
或 Query
对象,您需要特别小心,因为命名空间将保留在这些序列化内容中。
如果您使用反序列化的 Key
和 Query
对象,请确保这些对象会按预期方式工作。通过在调用任何 Datastore API 之前设置当前命名空间,大多数使用数据存储区 (put
/query
/get
) 而未使用其他存储机制的简单应用将按预期方式工作。
对于命名空间,Query
和 Key
对象会表现出以下独特行为:
Query
和Key
对象构造完毕后,将继承当前命名空间,除非您设置了明确的命名空间。- 当应用通过祖先实体创建新的
Key
时,新Key
会继承祖先实体的命名空间。 - 尚无任何适用于 Java 的 API 可用来明确设置
Key
或Query
的命名空间。
SomeRequest
请求处理程序,它用于递增 Counter
数据存储区实体中的当前命名空间以及任意命名的 -global-
命名空间的计数。
将命名空间与 Memcache 一起使用
默认情况下,Memcache 会使用命名空间管理器中的当前命名空间来处理 Memcache 请求。大多数情况下,您不需要在 Memcache 中明确设置命名空间,那样做可能会产生意外错误。
不过,在一些特殊情况下,您可能需要在 Memcache 中明确设置命名空间。例如,应用可能在所有命名空间之间共享相同的数据(例如,包含国家/地区代码的表格)。
以下代码段演示了如何在 Memcache 中明确设置命名空间:
默认情况下,适用于 Java 的 Memcache API 会从 MemcacheService
中查询命名空间管理器中的当前命名空间。当您使用 getMemcacheService(Namespace)
构造内存缓存时,也可以明确声明命名空间。对于大多数应用,您都无需明确指定命名空间。
以下代码示例演示了如何创建 Memcache 并让其使用命名空间管理器中的当前命名空间。
以下代码示例在创建 Memcache 服务时明确指定了命名空间:
将命名空间与任务队列一起使用
默认情况下,推送队列会使用创建任务时在命名空间管理器中设置的当前命名空间。大多数情况下,您不需要在任务队列中明确设置命名空间,这么做可能会导致产生意外的错误。
任务名称会在所有命名空间中共享。即使任务使用不同的命名空间,也不能同名。如果要对多个命名空间使用相同的任务名称,您只需将每个命名空间附加到任务名称中。
当新任务调用任务队列 add()
方法时,任务队列会从命名空间管理器复制当前命名空间和(如果适用)Google Workspace 网域。执行任务后,系统将恢复当前命名空间和 Google Workspace 命名空间。
如果当前命名空间不是在原始请求中设置的(换句话说,如果 get()
返回 null
),则任务队列在执行的任务中将命名空间设置为 ""
。
在一些特殊情况下,您需要为在所有命名空间中执行的任务明确设置命名空间。例如,您可以创建一个任务,以汇总所有命名空间中的使用情况统计信息。然后,您可以明确设置该任务的命名空间。以下代码示例演示了如何为任务队列明确设置命名空间。
首先,创建一个将在 Counter
数据存储区实体中递增计数的任务队列处理程序:
和
然后,使用 servlet 创建任务:
将命名空间与 Blobstore 一起使用
Blobstore 不按命名空间划分。要在 Blobstore 中保留命名空间,您需要通过支持命名空间的存储介质(当前只有 Memcache、数据存储区和任务队列)访问 Blobstore。例如,如果 blob 的 Key
存储在数据存储区实体中,您可以使用支持命名空间的数据存储区 Key
或 Query
访问该实体。
如果应用通过支持命名空间的存储空间中存储的键访问 Blobstore,则 Blobstore 本身无需按命名空间进行划分。应用必须通过以下方法避免命名空间之间的 blob 泄露:
- 不使用
com.google.appengine.api.blobstore.BlobInfoFactory
来处理最终用户请求。您可以对管理请求(例如,生成关于所有应用 Blob 的报告)使用 BlobInfo 查询,但对最终用户请求使用该查询可能会导致数据泄露,因为并非所有 BlobInfo 记录都按命名空间划分。 - 不使用来自不可信来源的 Blobstore 键。
为数据存储区查询设置命名空间
在 Google Cloud 控制台中,您可以为 Datastore 查询设置命名空间。
如果不想使用默认值,请从下拉列表中选择要使用的命名空间。
将命名空间与批量加载器一起使用
批量加载器支持 --namespace=NAMESPACE
标志,后者可用于指定要使用的命名空间。每个命名空间都需要单独处理;如果要访问所有命名空间,则需要依次处理这些命名空间。
将命名空间与搜索一起使用
Index
的新实例将继承用于创建该实例的 SearchService
的命名空间。创建对索引的引用后,其命名空间便无法更改。在使用 SearchService
创建索引之前,您可以通过两种方式为其设置命名空间:
- 默认情况下,新
SearchService
会采用当前命名空间。您可以在创建服务之前设置当前命名空间:
您可以在创建服务时在 SearchServiceConfig
中指定命名空间: