使用指南#

概述#

在撰写本文时,流行的键/值服务器包括 MemcachedRedis 和许多其他服务器。虽然这些工具的用法重点各不相同,但它们的共同点是存储模型基于根据键检索值;因此,它们都可能适合缓存,特别是首先设计用于缓存的 Memcached。

考虑到缓存系统,dogpile.cache 提供了一个针对该系统的特定 Python API 的接口。

dogpile.cache 配置包括以下组件

  • 一个区域,它是 CacheRegion 的一个实例,并定义了特定缓存后端的配置详细信息。可以将 CacheRegion 视为应用程序使用的“前端”。

  • 后端,即 CacheBackend 的一个实例,描述如何从后端存储和检索值。此接口仅指定 get()set()delete()。用于特定 CacheRegionCacheBackend 的实际类型由用于与缓存通信的基础 Python API(例如 Pylibmc)决定。在正常情况下,CacheBackend 会在后台实例化,且不会由应用程序直接访问。

  • 值生成函数。这些是用户定义的函数,用于生成要放入缓存的新值。虽然 dogpile.cache 提供了将数据放入缓存的通常“set”方法,但通常的使用模式只是指示它“get”一个值,并向其传递一个创建函数,该函数仅在需要时用于生成新值。这种“get-or-create”模式是“Dogpile”系统的全部关键,它协调对特定键的许多并发 get 操作中的单个值创建操作,从而消除了许多工作进程同时冗余重新生成过期值的问题。

基本用法#

dogpile.cache 包含一个 Pylibmc 后端。基本配置如下所示

from dogpile.cache import make_region

region = make_region().configure(
    'dogpile.cache.pylibmc',
    expiration_time = 3600,
    arguments = {
        'url': ["127.0.0.1"],
    }
)

@region.cache_on_arguments()
def load_user_info(user_id):
    return some_database.lookup_user_by_id(user_id)

上面,我们使用 CacheRegion 函数创建了一个 make_region(),然后通过 CacheRegion.configure() 方法应用后端配置,该方法返回区域。后端的名称是 CacheRegion.configure() 本身唯一需要的参数,本例中为 dogpile.cache.pylibmc。但是,在本例中,pylibmc 后端还需要在 arguments 字典中传递 memcached 服务器的 URL。

配置分为两个部分。通过 make_region() 构造后,CacheRegion 对象可用,通常在模块导入时可用,用于装饰函数。传递给 CacheRegion.configure() 的其他配置详细信息通常从配置文件中加载,因此不一定在运行时可用,因此采用两步配置过程。

传递给 CacheRegion.configure() 的关键参数包括 expiration_time,这是传递给 Dogpile 锁的过期时间,以及 arguments,这是后端直接使用的参数 - 本例中,我们使用直接传递给 pylibmc 模块的参数。

区域配置#

make_region() 函数当前直接调用 CacheRegion 构造函数。

dogpile.cache.region.CacheRegion(名称: str | = 函数键生成器: ~typing.Callable[[...], ~typing.Callable[[...], str]] = <函数 函数键生成器>, 函数多键生成器: ~typing.Callable[[...], ~typing.Callable[[...], ~typing.Sequence[str]]] = <函数 函数多键生成器>, 键混淆器: ~typing.Callable[[str], str] | = 序列化器: ~typing.Callable[[~typing.Any], 字节] | = 反序列化器: ~typing.Callable[[字节], ~typing.Any] | = 异步创建运行器: ~typing.Callable[[~dogpile.cache.region.CacheRegion, str, ~typing.Callable[[], ~typing.Any], ~dogpile.cache.api.CacheMutex], 无] | = )

特定缓存后端的某个前端。

参数:
  • 名称 – 可选,区域的字符串名称。它在内部不会被使用,但可以通过 .name 参数进行访问,这有助于从配置文件中配置区域。

  • 函数键生成器

    可选。使用 CacheRegion.cache_on_arguments() 方法时,给定数据创建函数和参数,将生成“缓存键”的函数。该函数的结构应为两级:给定数据创建函数,返回一个基于给定参数生成键的新函数。例如

    def my_key_generator(namespace, fn, **kw):
        fname = fn.__name__
        def generate_key(*arg):
            return namespace + "_" + fname + "_".join(str(s) for s in arg)
        return generate_key
    
    
    region = make_region(
        function_key_generator = my_key_generator
    ).configure(
        "dogpile.cache.dbm",
        expiration_time=300,
        arguments={
            "filename":"file.dbm"
        }
    )
    

    namespace 传递给 CacheRegion.cache_on_arguments()。此函数外部不查询它,因此实际上可以采用任何形式。例如,它可以作为元组传递,用于指定从 **kw

    def my_key_generator(namespace, fn):
        def generate_key(*arg, **kw):
            return ":".join(
                    [kw[k] for k in namespace] +
                    [str(x) for x in arg]
                )
        return generate_key
    

    装饰器可能用作

    @my_region.cache_on_arguments(namespace=('x', 'y'))
    def my_function(a, b, **kw):
        return my_data()
    

    另请参阅

    function_key_generator() - 默认键生成器

    kwarg_function_key_generator() - 还使用关键字参数的可选生成器

  • function_multi_key_generator

    可选。类似于 function_key_generator 参数,但它用于 CacheRegion.cache_multi_on_arguments()。生成的函数应返回键列表。例如

    def my_multi_key_generator(namespace, fn, **kw):
        namespace = fn.__name__ + (namespace or '')
    
        def generate_keys(*args):
            return [namespace + ':' + str(a) for a in args]
    
        return generate_keys
    

  • key_mangler – 在将所有传入键传递给后端之前,将在所有传入键上使用的函数。默认为 None,在这种情况下,将使用缓存后端推荐的键处理函数。典型的处理程序是 sha1_mangle_key() 中找到的 SHA1 处理程序,它将键强制转换为 SHA1 哈希,以便固定字符串长度。要禁用所有键处理,请设置为 False。另一个典型的处理程序是内置 Python 函数 str,它可用于将非字符串或 Unicode 键转换为字节串,在 Python 2.x 下与 bsddb 或 dbm 等后端结合使用 Unicode 键时需要这样做。

  • serializer

    在将所有值传递给后端之前,将应用该函数。默认为 None,在这种情况下,将使用后端推荐的序列化程序。典型的序列化程序包括 pickle.dumpsjson.dumps

    在 1.1.0 版本中添加。

  • deserializer

    将应用于后端返回的所有值的函数。默认为 None,在这种情况下,将使用后端推荐的反序列化程序。典型的反序列化程序包括 pickle.dumpsjson.dumps

    如果反序列化程序无法从后端反序列化值,则可以引发 api.CantDeserializeException,表示反序列化失败,并且缓存应继续重新生成值。这允许已更新的应用程序从容地重新缓存旧项,这些旧项由应用程序的先前版本持久化,并且无法再成功反序列化。

    在 1.1.0 版本中添加: 添加了“deserializer”参数

    在 1.2.0 版本中添加: 添加了对 api.CantDeserializeException 的支持

  • async_creation_runner

    当缓存中存在陈旧值时,将传递给 dogpile.lock 并由其调用的一个可调用对象(如果已指定)。它将传递给互斥锁,并在完成后负责释放该互斥锁。这可用于通过例如后台线程、长时间运行的队列或类似于 Celery 的任务管理器系统,将昂贵的创建器函数的计算推迟到以后的时间点。

    对于使用 async_creation_runner 的特定示例,可以在后台线程中创建新值,如下所示

    import threading
    
    def async_creation_runner(cache, somekey, creator, mutex):
        ''' Used by dogpile.core:Lock when appropriate  '''
        def runner():
            try:
                value = creator()
                cache.set(somekey, value)
            finally:
                mutex.release()
    
        thread = threading.Thread(target=runner)
        thread.start()
    
    
    region = make_region(
        async_creation_runner=async_creation_runner,
    ).configure(
        'dogpile.cache.memcached',
        expiration_time=5,
        arguments={
            'url': '127.0.0.1:11211',
            'distributed_lock': True,
        }
    )
    

    请记住,对于没有关联值的键的第一个请求将始终被阻塞;不会调用 async_creator。但是,对于缓存但已过期的值的后续请求仍会立即返回。它们将通过所提供的 async_creation_runner 可调用对象实现的任何异步方式进行刷新。

    默认情况下,async_creation_runner 被禁用并设置为 None

    在版本 0.4.2 中添加:添加了 async_creation_runner 功能。

拥有 CacheRegion 后,CacheRegion.cache_on_arguments() 方法可用于装饰函数,但缓存本身在调用 CacheRegion.configure() 之前无法使用。该方法的接口如下

CacheRegion.configure(backend: str, expiration_time: float | timedelta | None = None, arguments: Mapping[str, Any] | None = None, _config_argument_dict: Mapping[str, Any] | None = None, _config_prefix: str | None = None, wrap: Sequence[ProxyBackend | Type[ProxyBackend]] = (), replace_existing_backend: bool = False, region_invalidator: RegionInvalidationStrategy | None = None) Self

配置 CacheRegion

将返回 CacheRegion 本身。

参数:
  • backend – 必需。这是要使用的 CacheBackend 的名称,并通过从 dogpile.cache 入口点加载类来解析。

  • expiration_time

    可选。传递给 dogpile 系统的过期时间。可以作为整数秒数或 datetime.timedelta 值传递。

    CacheRegion.get_or_create() 方法以及 CacheRegion.cache_on_arguments() 装饰器(但请注意:不是 CacheRegion.get() 方法)将在自上次生成以来经过此时间段后调用值创建函数。

  • arguments – 可选。此处的结构直接传递给正在使用的 CacheBackend 的构造函数,但通常是字典。

  • wrap

    可选。ProxyBackend 类和/或实例的列表,每个类和/或实例都将在链中应用,以最终包装原始后端,以便可以应用自定义功能增强。

    在 0.5.0 版本中添加。

    另请参阅

    更改后端行为

  • replace_existing_backend

    如果为 True,则将替换现有的缓存后端。如果没有此标志,则在已配置后端时会引发异常。

    在 0.5.7 版本中添加。

  • region_invalidator

    可选。使用 RegionInvalidationStrategy 的自定义实现覆盖默认失效策略。

    在 0.6.2 版本中添加。

CacheRegion 还可以使用 CacheRegion.configure_from_config() 方法,通过字典进行配置

CacheRegion.configure_from_config(config_dict, prefix)

通过配置字典和前缀进行配置。

示例

local_region = make_region()
memcached_region = make_region()

# regions are ready to use for function
# decorators, but not yet for actual caching

# later, when config is available
myconfig = {
    "cache.local.backend":"dogpile.cache.dbm",
    "cache.local.arguments.filename":"/path/to/dbmfile.dbm",
    "cache.memcached.backend":"dogpile.cache.pylibmc",
    "cache.memcached.arguments.url":"127.0.0.1, 10.0.0.1",
}
local_region.configure_from_config(myconfig, "cache.local.")
memcached_region.configure_from_config(myconfig,
                                    "cache.memcached.")

使用区域#

CacheRegion 对象是我们与缓存的前端接口。它包含以下方法

CacheRegion.get(key: str, expiration_time: float | None = None, ignore_expiration: bool = False) Any | Literal[NoValue.NO_VALUE]

根据给定的键从缓存中返回一个值。

如果值不存在,该方法将返回令牌 api.NO_VALUEapi.NO_VALUE 的值为 False,但与 None 分开,以区分 None 的缓存值。

默认情况下,已配置的 CacheRegion 的过期时间,或者 expiration_time 参数提供的过期时间,将根据检索到的值的创建时间与当前时间(由 time.time() 报告)进行测试。如果过时,则忽略缓存值并返回 api.NO_VALUE 令牌。传递标志 ignore_expiration=True 会绕过过期时间检查。

0.3.0 版中已更改: CacheRegion.get() 现在会根据过期时间检查值创建的时间,而不是无条件地返回值。

该方法还会根据由 invalidate() 方法设置的当前“失效”时间来解释缓存值。如果存在一个值,但其创建时间早于当前失效时间,则返回 api.NO_VALUE 令牌。传递标志 ignore_expiration=True 会绕过失效时间检查。

在版本 0.3.0 中添加: 支持 CacheRegion.invalidate() 方法。

参数:
  • key – 要检索的键。虽然键通常为字符串,但它最终会直接传递到缓存后端,在可选地由 key_mangler 函数处理之前,因此可以是后端或 key_mangler 函数(如果存在)识别的任何类型。

  • expiration_time

    可选的过期时间值,它将取代 CacheRegion 本身上配置的值。

    注意

    CacheRegion.get.expiration_time 参数不会持久存储在缓存中,并且仅与此特定缓存检索操作相关,相对于与现有缓存值一起存储的创建时间。后续调用 CacheRegion.get() 不会受到此值的影响。

    在版本 0.3.0 中添加。

  • ignore_expiration

    如果为 True,则无论配置的过期时间或是否调用了 invalidate(),都将从缓存中返回该值。

    在版本 0.3.0 中添加。

CacheRegion.get_or_create(key: str, creator: Callable[[...], Any], expiration_time: float | None = None, should_cache_fn: Callable[[Any], bool] | None = None, creator_args: Tuple[Any, Mapping[str, Any]] | None = None) Any

根据给定的键返回缓存值。

如果该值不存在或根据其创建时间被认为已过期,则可以使用给定的创建函数重新创建该值并将新生成的值持久存储在缓存中。

是否使用该函数取决于是否可以获取dogpile 锁。如果无法获取,则表示另一个线程或进程已经针对缓存为此键运行创建函数。当无法获取 dogpile 锁时,如果之前没有可用值,则该方法将阻塞,直到释放锁且有新值可用。如果之前有可用值,则立即返回该值而不进行阻塞。

如果已调用 invalidate() 方法,并且检索到的值的 timestamp 早于失效 timestamp,则无条件阻止返回该值。该方法将尝试获取 dogpile 锁以生成新值,或者将等待锁释放以返回新值。

在版本 0.3.0 中更改:如果创建时间早于最后一次调用 invalidate(),则无条件地重新生成该值。

参数:
  • key – 要检索的键。虽然键通常为字符串,但最终会直接传递到缓存后端,然后由 key_mangler 函数(如果存在)进行可选处理,因此可以是后端或 key_mangler 函数(如果存在)识别的任何类型。

  • creator – 创建新值的函数。

  • creator_args

    如果存在,将传递给 creator 函数的可选元组 (args, kwargs)。

    在版本 0.7.0 中添加。

  • expiration_time

    如果为 None,则可选的过期时间将覆盖已在此 CacheRegion 上配置的过期时间。要设置不设置过期时间,请使用值 -1。

    注意

    参数 CacheRegion.get_or_create.expiration_time 不会保留在缓存中,并且仅与此特定缓存检索操作相关,相对于与现有缓存值一起存储的创建时间。后续对 CacheRegion.get_or_create() 的调用不受此值影响。

  • should_cache_fn

    可选的可调用函数,它将接收“创建者”返回的值,然后返回 True 或 False,指示该值是否应实际缓存。如果它返回 False,则该值仍将返回,但不会缓存。例如:

    def dont_cache_none(value):
        return value is not None
    
    value = region.get_or_create("some key",
                        create_value,
                        should_cache_fn=dont_cache_none)
    

    在上面,如果缓存无效,该函数将返回 create_value() 的值,但是如果返回值为 None,则不会缓存该值。

    在 0.4.3 版本中添加。

另请参阅

CacheRegion.get()

CacheRegion.cache_on_arguments() - 使用装饰器将 get_or_create() 应用于任何函数。

CacheRegion.get_or_create_multi() - 多个键/值版本

CacheRegion.set(key: str, value: Any) None

将新值放入给定键下的缓存中。

CacheRegion.delete(key: str) None

从缓存中删除一个值。

此操作是幂等的(可以安全地多次调用或在不存在的键上调用)

CacheRegion.cache_on_arguments(namespace: str | None = None, expiration_time: float | ~typing.Callable[[], float] | None = None, should_cache_fn: ~typing.Callable[[~typing.Any], bool] | None = None, to_str: ~typing.Callable[[~typing.Any], str] = <class 'str'>, function_key_generator: ~typing.Callable[[...], ~typing.Callable[[...], str]] | None = None) Callable[[Callable[[...], Any]], Callable[[...], Any]]

一个函数装饰器,它将使用从函数本身及其参数派生的键来缓存函数的返回值。

该装饰器在内部使用 CacheRegion.get_or_create() 方法来访问缓存并有条件地调用函数。有关其他行为详细信息,请参见该方法。

例如

@someregion.cache_on_arguments()
def generate_something(x, y):
    return somedatabase.query(x, y)

然后可以正常调用装饰后的函数,其中数据将从缓存区域中提取,除非需要新值

result = generate_something(5, 6)

该函数还被赋予一个属性 invalidate(),它用于使值失效。将与传递给函数本身相同参数传递给 invalidate() 以表示特定值

generate_something.invalidate(5, 6)

添加了另一个属性 set(),以提供相对于该函数的额外缓存可能性。这是一个方便的方法,用于 CacheRegion.set(),它将直接存储给定值,而无需调用装饰函数。要缓存的值作为第一个参数传递,而通常传递给函数的参数应紧随其后

generate_something.set(3, 5, 6)

如果函数要生成值 3 作为要缓存的值,则上述示例等效于调用 generate_something(5, 6)

在版本 0.4.1 中添加: 向装饰函数添加 set() 方法。

类似于 set() 的是 refresh()。此属性将调用装饰函数,并将新值填充到缓存中,同时返回该值

newvalue = generate_something.refresh(5, 6)

在版本 0.5.0 中添加: 向装饰函数添加 refresh() 方法。

original() 另一方面将调用装饰函数,而不进行任何缓存

newvalue = generate_something.original(5, 6)

在版本 0.6.0 中添加: 向装饰函数添加 original() 方法。

最后,get() 方法返回针对给定键缓存的值,或者如果不存在此类键,则返回标记 NO_VALUE

value = generate_something.get(5, 6)

在版本 0.5.3 中添加: 向装饰函数添加 get() 方法。

默认键生成将使用函数的名称、函数的模块名称、传递的参数以及可选的“命名空间”参数来生成缓存键。

给定模块 myapp.tools 中的函数 one

@region.cache_on_arguments(namespace="foo")
def one(a, b):
    return a + b

在上面,调用 one(3, 4) 将生成如下缓存键

myapp.tools:one|foo|3 4

键生成器将忽略 selfcls 的初始参数,使装饰器适合(有警告)与实例或类方法一起使用。给定示例

class MyClass:
    @region.cache_on_arguments(namespace="foo")
    def one(self, a, b):
        return a + b

对于 MyClass().one(3, 4) 的上述缓存键将再次生成相同的缓存键 myapp.tools:one|foo|3 4 - 名称 self 被跳过。

namespace 参数是可选的,通常用于消除同一模块中同名两个函数的歧义,如下所示,在装饰实例或类方法时可能会出现这种情况

class MyClass:
    @region.cache_on_arguments(namespace='MC')
    def somemethod(self, x, y):
        ""

class MyOtherClass:
    @region.cache_on_arguments(namespace='MOC')
    def somemethod(self, x, y):
        ""

在上面,namespace 参数消除了 MyClassMyOtherClasssomemethod 之间的歧义。否则,Python 类声明机制会阻止装饰器识别 MyClassMyOtherClass 名称,因为装饰器在函数成为实例方法之前就接收了该函数。

可以使用 make_region()CacheRegion 上存在的 function_key_generator 参数,在每个区域的基础上完全替换函数键生成。如果默认为 function_key_generator()

参数:
  • namespace – 可选的字符串参数,将作为缓存键的一部分建立。这可能需要消除同一源文件中同名函数的歧义,例如与类关联的函数 - 请注意,装饰器本身无法在函数上看到父类,因为类正在被声明。

  • expiration_time

    如果非 None,将覆盖正常过期时间。

    可以指定为一个不带参数的可调用对象,它返回一个值用作 expiration_time。无论在缓存还是检索中,只要调用装饰函数本身,就会调用此可调用对象。因此,这可用于确定缓存函数结果的动态过期时间。示例用例包括“缓存结果直到当天、本周或时间段结束”和“缓存直到某个日期或时间过去”。

  • should_cache_fn – 传递给 CacheRegion.get_or_create()

  • to_str – 可调用对象,将按顺序对每个函数参数调用它以转换为字符串。默认为 str()。如果函数在 Python 2.x 上接受非 ASCII unicode 参数,则可以替换 unicode() 内置函数,但请注意这会生成 unicode 缓存键,在到达缓存之前可能需要对键进行修改。

  • function_key_generator – 将生成“缓存键”的函数。此函数将取代 CacheRegion 本身上配置的函数。

创建后端#

后端使用 setuptools 入口点系统进行定位。为了让临时后端的编写者更轻松,包含了一个帮助函数,它以与现有 sys.path 中的部分相同的方式注册任何后端。

例如,要创建一个名为 DictionaryBackend 的后端,我们对 CacheBackend 进行子类化

from dogpile.cache.api import CacheBackend, NO_VALUE

class DictionaryBackend(CacheBackend):
    def __init__(self, arguments):
        self.cache = {}

    def get(self, key):
        return self.cache.get(key, NO_VALUE)

    def set(self, key, value):
        self.cache[key] = value

    def delete(self, key):
        self.cache.pop(key)

然后确保类在入口点 dogpile.cache 下可用。如果我们在 setup.py 文件中执行此操作,它将作为 setup() 中的

entry_points="""
  [dogpile.cache]
  dictionary = mypackage.mybackend:DictionaryBackend
  """

或者,如果我们希望在同一进程空间中注册插件,而不必费心安装任何内容,我们可以使用 register_backend

from dogpile.cache import register_backend

register_backend("dictionary", "mypackage.mybackend", "DictionaryBackend")

我们的新后端可以在如下区域中使用

from dogpile.cache import make_region

region = make_region("myregion")

region.configure("dictionary")

data = region.set("somekey", "somevalue")

我们在此处为后端接收的值是 CachedValue 的实例。这是一个长度为 2 的元组子类,形式如下

(payload, metadata)

其中“有效负载”是要缓存的内容,“元数据”是我们存储在缓存中的信息 - 一个字典,目前仅具有“创建时间”和“版本标识符”作为键/值。如果缓存后端需要序列化,则可以使用元组上的 pickle 或类似方法 - “元数据”部分将始终是一个小且易于序列化的 Python 结构。

更改后端行为#

ProxyBackend 是一个装饰器类,用于轻松增强现有后端行为,而无需扩展原始类。使用装饰器类也很有利,因为它允许我们在不同后端之间共享更改后的行为。

使用 CacheRegion.configure() 方法将代理添加到 CacheRegion 对象。只需要指定重写的方法,并且可以使用 ProxyBackend 中的 self.proxied 对象从内部访问真实后端。

例如,一个用于记录对 .set() 的所有调用的简单类将如下所示

from dogpile.cache.proxy import ProxyBackend

import logging
log = logging.getLogger(__name__)

class LoggingProxy(ProxyBackend):
    def set(self, key, value):
        log.debug('Setting Cache Key: %s' % key)
        self.proxied.set(key, value)

ProxyBackend 可以配置为可选地采用参数(只要正确调用 ProxyBackend.__init__() 方法,无论是直接调用还是通过 super() 调用)。在下面的示例中,RetryDeleteProxy 类在初始化时接受 retry_count 参数。在 delete() 上发生异常时,它将在返回之前重试多次

from dogpile.cache.proxy import ProxyBackend

class RetryDeleteProxy(ProxyBackend):
    def __init__(self, retry_count=5):
        super(RetryDeleteProxy, self).__init__()
        self.retry_count = retry_count

    def delete(self, key):
        retries = self.retry_count
        while retries > 0:
            retries -= 1
            try:
                self.proxied.delete(key)
                return

            except:
                pass

CacheRegion.configure()wrap 参数接受一个列表,其中可以包含实例化代理对象和未实例化的代理类的任意组合。将以上两个示例放在一起将如下所示

from dogpile.cache import make_region

retry_proxy = RetryDeleteProxy(5)

region = make_region().configure(
    'dogpile.cache.pylibmc',
    expiration_time = 3600,
    arguments = {
        'url':["127.0.0.1"],
    },
    wrap = [ LoggingProxy, retry_proxy ]
)

在上面的示例中,LoggingProxy 对象将由 CacheRegion 实例化,并应用于代表 retry_proxy 实例包装请求;该代理反过来代表原始 dogpile.cache.pylibmc 后端包装请求。

在版本 0.4.4 中添加: 添加对 ProxyBackend 类的支持。

配置日志记录#

在版本 0.9.0 中添加。

CacheRegion 包含日志记录工具,当发生关键缓存事件时将发出调试日志消息,包括当关键被重新生成以及当发生硬失效时。使用 Python 日志记录 模块,将日志级别设置为 dogpile.cachelogging.DEBUG

logging.basicConfig()
logging.getLogger("dogpile.cache").setLevel(logging.DEBUG)

调试日志记录将指示重新生成关键以及关键丢失时所花费的时间

DEBUG:dogpile.cache.region:No value present for key: '__main__:load_user_info|2'
DEBUG:dogpile.cache.region:No value present for key: '__main__:load_user_info|1'
DEBUG:dogpile.cache.region:Cache value generated in 0.501 seconds for keys: ['__main__:load_user_info|2', '__main__:load_user_info|3', '__main__:load_user_info|4', '__main__:load_user_info|5']
DEBUG:dogpile.cache.region:Hard invalidation detected for key: '__main__:load_user_info|3'
DEBUG:dogpile.cache.region:Hard invalidation detected for key: '__main__:load_user_info|2'