NDB 缓存

NDB 可为您管理缓存。 有两个缓存级别:一个是上下文缓存,一个是使用 App Engine 标准缓存服务 - Memcache。 默认情况下,所有实体类型都会启用这两种缓存,您可通过配置这些缓存来满足高级需求。 此外,NDB 可实现一项名为自动批处理的功能,该功能会尝试将操作组合在一起,从而最大限度地减少服务器往返次数。

简介

缓存对大多数类型的应用都大有裨益。NDB 会自动缓存其写入或读取的数据(除非应用将其配置为不自动缓存)。 从缓存读取比从 Datastore 读取的速度要快。

您可以通过传递上下文选项参数来修改多个 NDB 函数的缓存行为。 例如,您可以调用 key.get(use_cache=False, use_memcache=False) 来绕过缓存。您还可以在 NDB 上下文中更改默认缓存政策,如下文所述。

注意:在您使用管理控制台的数据存储区查看器修改数据存储区内容时,缓存的值将不会更新。 因此,您的缓存可能会不一致。 对于上下文缓存,这通常不会造成问题。 对于 Memcache,我们建议使用管理控制台来刷新缓存。

上下文对象

缓存管理使用名为 Context 的类:每个线程和每个事务均在新的上下文中执行。每个传入 HTTP 请求都会启动一个新线程,各请求也会通过新的上下文执行。要访问当前上下文,请使用 ndb.get_context() 函数。

注意:在多个线程或请求之间共享 Context 对象是没有意义的。请勿将上下文保存为全局变量! 最好是将上下文存储在局部变量或线程局部变量中。

上下文对象具有用于设置缓存政策以及处理缓存的方法。

上下文缓存

上下文缓存仅在单个线程的持续时间内持续存在。这意味着系统会为每个传入 HTTP 请求指定一个新的上下文缓存,且该上下文缓存仅对处理相关请求的代码“可见”。在处理请求时,如果您的应用生成任何其他线程,则这些线程还将有一个新的单独上下文缓存。

上下文缓存速度很快;此缓存位于内存中。当 NDB 函数写入 Datastore 时,它也会写入上下文缓存。当 NDB 函数读取实体时,它会先检查上下文缓存。如果在上下文缓存中找到相应实体,则无需进行 Datastore 交互。

当 NDB 函数查询 Datastore 时,将从 Datastore 中检索结果列表。不过,如果上下文缓存中存在任何单个结果,则将使用该结果来代替通过 Datastore 查询检索到的值。 查询结果会写回上下文缓存,前提是缓存政策这样规定(但永不写回 Memcache)。

在后台任务中执行长时间运行的查询时,上下文缓存可能会消耗大量内存。这是因为缓存保存了在当前上下文中检索或存储的每个实体的副本。为避免长时间运行的任务中发生内存异常,您可以停用缓存或设置政策,排除消耗大部分内存的所有实体。

Memcache

Memcache 是 App Engine 的标准缓存服务,速度比 Datastore 快得多,但比上下文缓存慢(毫秒级与微秒级的区别)。

默认情况下,非事务性上下文会缓存 Memcache 中的所有实体。 应用的所有上下文都使用同一 Memcache 服务器,还会看到一组一致的缓存值。

Memcache 不支持事务。因此,旨在同时应用于 Datastore 和 Memcache 的更新可能只会对两者中的一个进行。 为了在此类情况下保持一致性(可能以牺牲性能为代价),更新后的实体将从 Memcache 中删除,然后再写入 Datastore。后续读取操作将发现 Memcache 中缺少该实体,从 Datastore 中检索它,然后在 Memcache 中更新该实体(这也是读取的一个“副作用”)。 此外,事务内的 NDB 读取会忽略 Memcache。

在事务中写入实体时,将不使用 Memcache;在提交事务时,其上下文将尝试从 Memcache 中删除所有此类实体。不过请注意,某些故障可能会阻止这些删除操作。

政策函数

自动缓存对大多数应用有利,但您的应用可能不常见,并且您需要对部分或所有实体关闭自动缓存。 您可以通过设置政策函数来控制缓存的行为。 以下是一个用于进程内缓存的政策函数,其设置如下:

context = ndb.get_context()
context.set_cache_policy(func)

还有一个用于 Memcache 的政策函数,其设置如下:

context = ndb.get_context()
context.set_memcache_policy(func)

每个政策函数均接受键并返回布尔值结果。 如果政策函数返回 False,则由该键标识的实体将不会保存在相应的缓存中。例如,要对所有 Account 实体绕过进程内缓存,您可以写入以下代码:

context = ndb.get_context()
context.set_cache_policy(lambda key: key.kind() != 'Account')

(不过,请继续阅读,找到一种更简单的方法来实现此目的。) 为方便起见,您可以传递 TrueFalse,而不是传递始终返回同一值的函数。默认政策会缓存所有实体。

还有一个 Datastore 政策函数,用于管理将哪些实体写入 Datastore 本身:

context = ndb.get_context()
context.set_datastore_policy(func)

该函数的工作原理类似于上下文缓存和 Memcache 政策函数:如果 Datastore 政策函数为给定键返回 False,则相应的实体将不会写入 Datastore。(该实体可能会写入进程内缓存或 Memcache,前提是其政策函数允许。) 如果您想要缓存类似实体的数据,但不需要将其存储在 Datastore 中,则此函数非常有用。 与缓存政策一样,您可以传递 TrueFalse,而不是传递始终返回同一值的函数。

在内存不足时,Memcache 会自动使项目过期。 您可以设置 Memcache 超时政策函数,以确定实体在缓存中的最长有效期:

context = ndb.get_context()
context.set_memcache_timeout_policy(func)

此函数通过键参数调用,并且应返回一个指定最长有效期(以秒为单位)的整数;如果值为 0 或 None,则表示无限期(只要 Memcache 服务器具有足够的内存即可)。为方便起见,您可以只传递一个整数常量,而不是传递始终返回同一值的函数。 如需详细了解超时,请参阅 Memcache 文档。

注意:上下文缓存没有单独的有效期政策:缓存的有效期与其上下文(即单个传入 HTTP 请求)的生命周期相同。不过,您可以通过调用以下函数来清除进程内缓存:
context = ndb.get_context()
context.clear_cache()

全新的上下文以一个空的进程内缓存开始。

虽然政策函数非常灵活,但实际上大多数政策都很简单。例如:

  • 不要缓存属于特定模型类的实体。
  • 将此模型类中的实体的 Memcache 超时设置为 30 秒。
  • 无需将此模型类中的实体写入 Datastore。

为免去您编写和不断更新简单政策函数的工作(甚至是使用上下文选项替换每项操作的政策),默认政策函数可从传递给它们的键获取模型类,然后在模型类中查找特定的类变量:

类变量类型说明
_use_cache bool 指定是否在进程内缓存中存储实体;替换默认进程内缓存政策。
_use_memcache bool 指定是否在 Memcache 中存储实体;替换默认 Memcache 政策。
_use_datastore bool 指定是否在数据存储区中存储实体;替换默认数据存储区政策。
_memcache_timeout int 实体在 Memcache 中的最长有效期;替换默认 Memcache 超时政策。

注意:这是每个政策的默认政策函数的一项功能。 如果您指定了自己的政策函数但还想要回退到默认政策,则可明确调用默认政策函数作为 Context 类的静态方法:

  • default_cache_policy(key)
  • default_memcache_policy(key)
  • default_datastore_policy(key)
  • default_memcache_timeout_policy(key)