【问题标题】:Automatically cached models in App EngineApp Engine 中自动缓存的模型
【发布时间】:2011-11-09 10:37:45
【问题描述】:

我一直致力于创建一个自动缓存的 db.Model 子类,即:

  • instance.put 会将实体存储在内存缓存中,然后再将其持久化到数据存储中
  • class.get_by_key_name 会先检查缓存,如果错过了,会去datastore取回,取回后缓存

我开发了以下方法(这似乎对我有用),但我有几个问题:

  1. 我已经阅读了 Nick Johnson 在efficient model memcaching 上的文章,该文章建议通过协议缓冲区实现 memcache 的序列化。查看SDK中的memcache API源码,谷歌好像已经默认实现了protobuf序列化。我的解释正确吗?
  2. 在继承 db.Model 或覆盖这两种方法的过程中,我是否遗漏了一些重要的细节(这可能会在未来得到我)?
  3. 有没有更有效的方法来实施我在下面所做的事情?
  4. 是否有指导方针、基准或最佳实践来说明此类实体缓存何时从性能角度有意义?或者缓存实体总是有意义吗?在相关说明中,我是否应该阅读 Google 没有在建模 API 中提供缓存模型的事实?是否有太多特殊情况需要考虑?

以下是我当前的实现。我非常感谢有关缓存实体的任何和所有指导/建议(即使您的回答不是对上述 4 个问题之一的直接回答,而是与整个主题相关)。

from google.appengine.ext import db
from google.appengine.api import memcache

import os
import logging

class CachedModel(db.Model):
    '''Subclass of db.Model that automatically caches entities for put and 
    attempts to load from cache for get_by_key_name
    '''

    @classmethod
    def get_by_key_name(cls, key_names, parent=None, **kwargs):
        cache = memcache.Client()
        # Ensure that every new deployment of the application results in a cache miss
        # by including the application version ID in the namespace of the cache entry
        namespace = os.environ['CURRENT_VERSION_ID'] + '_' + cls.__name__

        if not isinstance(key_names, list):
            key_names = [key_names]
        entities = cache.get_multi(key_names, namespace=namespace)
        if entities:
            logging.info('%s (namespace=%s) retrieved from memcache' % (str(entities.keys()), namespace))

        missing_key_names = list(set(key_names) - set(entities.keys()))
        # For keys missed in memcahce, attempt to retrieve entities from datastore
        if missing_key_names:
            missing_entities = super(CachedModel, cls).get_by_key_name(missing_key_names, parent, **kwargs)
            missing_mapping = zip(missing_key_names, missing_entities)
            # Determine entities that exist in datastore and store them to memcache 
            entities_to_cache = dict()
            for key_name, entity in missing_mapping:
                if entity:
                    entities_to_cache[key_name] = entity
            if entities_to_cache:
                logging.info('%s (namespace=%s) cached by get_by_key_name' % (str(entities_to_cache.keys()), namespace))
                cache.set_multi(entities_to_cache, namespace=namespace)
            non_existent = set(missing_key_names) - set(entities_to_cache.keys())
            if non_existent:
                logging.info('%s (namespace=%s) missing from cache and datastore' % (str(non_existent), namespace))
            # Combine entities retrieved from cache and entities retrieved from datastore
            entities.update(missing_mapping)

        if len(key_names) == 1:
            return entities[key_names[0]]
        else:
            return [entities[key_name] for key_name in key_names]

    def put(self, **kwargs):
        cache = memcache.Client()
        namespace = os.environ['CURRENT_VERSION_ID'] + '_' + self.__class__.__name__
        cache.set(self.key().name(), self, namespace=namespace)
        logging.info('%s (namespace=%s) cached by put' % (self.key().name(), namespace))
        return super(CachedModel, self).put(**kwargs)

【问题讨论】:

  • 嘿,我写了一个simple module,它可以满足您的需求。你可以去看看。
  • @Can - 感谢您将我指向您的模型。我实际上对使用 memcache API 执行此操作很感兴趣,因为我认为这是一种处理缓存的更具可扩展性的方式。

标签: google-app-engine


【解决方案1】:

与其重新发明轮子,不如改用NDB,它已经实现了模型实例的memcaching?

【讨论】:

  • 尼克 - 感谢您为我指明正确的方向。这正是我需要的,但我不知道。我看到它已经在 1.6.0 SDK 和运行时中了,这很棒。
【解决方案2】:

您可以查看 Nick Johnson 关于添加 pre and post hooks for data model classes 作为覆盖 get_by_key_name 的替代方法的文章。这样,即使使用 db.get 和 db.put,你的钩子也可以工作。

也就是说,我发现在我的应用中,我在缓存更高级别的内容时获得了更显着的性能改进 - 例如渲染整个页面所需的所有内容,或者如果可能的话,页面的 html 本身。

您还可以查看asynctools 库,它可以帮助您并行运行数据存储查询缓存结果。

【讨论】:

  • 基于前/后挂钩方法,您是否建议我使用模型的键(而不是键名)作为内存缓存键?如果是这样,我假设我需要使用 str() 对密钥进行编码。如果您能澄清一下,我将不胜感激。
  • 密钥可以工作,在这种情况下,普通的旧 str(key) 就可以了。
【解决方案3】:

Nick Johnson 提出的许多您想要实施的好技巧已经在模块 appengine-mp 中实施。比如通过protocolbuf序列化或预取实体。

关于您的方法get_by_key_names,您可以check the code。如果您想创建自己的 db.Model 层,也许这可以帮助您,但您也可以为改进现有模型做出贡献。 ;)

【讨论】:

  • 感谢您将我指向您的项目。据我所知,它是通过完全重写 get/put/delete 而不是通过调用 super() 来覆盖的。我对这种方法的担忧是,这可能会导致相关 API 的未来版本出现问题。我想采用尽可能轻量级的方法(即让谷歌做它做得很好的事情,并且只以实现我的目标所需的最简单的方式扩展它)。我是否误解了您在代码中使用的方法?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-26
  • 1970-01-01
  • 1970-01-01
  • 2016-08-08
  • 2017-06-23
  • 2014-09-26
  • 2010-12-28
相关资源
最近更新 更多