All Sunday

首页

mako根据条件判断是否使用页面缓存

最近遇到网站速度慢的情况,排查许久没查出什么原因。于是想着匿名用户的访问量占据了一半多,如果这一部分的请求全部缓存下来,那么应该能够很大程度上提升网站的响应速度。 之前已经在一些页面里面使用了Mako的页面缓存,具体的文档可以查看 http://docs.makotemplates.org/en/latest/caching.html

# CacheImepl的定义
class CMemcachedImpl(CacheImpl):
    def __init__(self, cache):
        from mysite.model.init_db import page_mc as mc
        super(CMemcachedImpl, self).__init__(cache)
        self.mc = mc

    def get_or_create(self, key, creation_function, **kw):
        value = self.mc.get(key)
        if not value:
            value = creation_function()
            timeout = kw.get('timeout', 60)
            try:
                timeout = int(timeout)
            except ValueError:
                timeout = 60

            # 防止缓存雪崩,即大量待缓存在同一时刻失效
            timeout = timeout + random.randint(0, timeout / 10)
            self.mc.set(key, value, timeout)

        return value

    def set(self, key, value, **kw):
        timeout = kw.get('timeout', 60)
        return self.mc.set(key, value, timeout)

    def get(self, key, **kw):
        return self.mc.get(key)

    def invalidate(self, key, **kw):
        return self.mc.delete(key)

模板中用法:

<%block cached="True" cache_key="cache_key_to_generate" cache_timeout="3600">
page content
</%block>

因为只对匿名用户缓存,最开始想到的就是动态的设置cached的值,登录用户为False,匿名用户为True。于是开始第一次尝试

<%block cached="${request.user}" cache_key="cache_key_to_generate" cache_timeout="3600">
page content
</%block>

结果Mako直接报错了,${request.user}无法eval。查看代码得知,Mako直接把cached的值当做Python代码执行。那么把${}去掉怎么样?

<%block cached="request.user" cache_key="cache_key_to_generate" cache_timeout="3600">
page content
</%block>

又报了错:request未定义。eval的上下文中没有request的定义。这个方法失败。

Mako是可以在block里面加cache_xxx这种cache_为前缀的参数,这些参数是可以被CacheImpl读取到的。既然cached没办法动态的修改,那么我新加一个cache_passthrough的参数(用来表示穿透缓存,即不使用缓存)。匿名用户访问的时候cache_passthrough设置为True,登录用户访问的时候改为False。然后在CacheImpl里面根据读取到的passthrough来决定是否直接跳过缓存。

def get_or_create(self, key, creation_function, **kw):
        passthrough = kw.get('passthrough', False)
        if passthrough:
          return creation_function()

        value = self.mc.get(key)
        if not value:
            value = creation_function()
            timeout = kw.get('timeout', 60)
            try:
                timeout = int(timeout)
            except ValueError:
                timeout = 60

            # 防止缓存雪崩,即大量待缓存在同一时刻失效
            timeout = timeout + random.randint(0, timeout / 10)
            self.mc.set(key, value, timeout)

        return value
<%block cached="True" cache_key="cache_key_to_generate" cache_timeout="3600" cache_passthrough="bool(request.user)">
page content
</%block>

然后结果很奇怪。如果第一次是登录状态访问,那么之后无论登录与否,页面都不会被缓存。如果第一次是匿名访问,那么之后无论登录与否,返回的都是缓存下来的同一个结果。花时间看Mako的代码发现,Mako对除了cache_key以外的cache_的参数进行了缓存。

def _get_cache_kw(self, kw, context):
        defname = kw.pop('__M_defname', None)
        if not defname:
            tmpl_kw = self.template.cache_args.copy()
            tmpl_kw.update(kw)
        elif defname in self._def_regions:   # 这个分支,参数被缓存在了 self._def_regions
            tmpl_kw = self._def_regions[defname]
        else:
            tmpl_kw = self.template.cache_args.copy()
            tmpl_kw.update(kw)
            self._def_regions[defname] = tmpl_kw
        if context and self.impl.pass_context:
            tmpl_kw = tmpl_kw.copy()
            tmpl_kw.setdefault('context', context)
        return tmpl_kw

完整代码在 mako/cache.py

因为不明白为什么要缓存参数,还去Mako的邮件组里面提问了下。Michael Bayerh回复说CacheImpl这个东西的存在是为了抽象出从不同地方的缓存去数据这个行为。而不是用来做其他一些逻辑上的东西。原话如下

but the arguments that are passed to get_or_create() were intended to be for the purposes of executing the “data retrieval” function, and not for the benefit of the cache impl wrapper itself.

他推荐用decorator自己实现一个缓存的机制来做这个事情。decorator的文档在 http://docs.makotemplates.org/en/latest/filtering.html#decorating 。

我的需求是对全站的页面根据登录状态进行缓存,所以缓存的key就根据URL来自动生成了。代码中的request_hash就是根据URL来生成hash值。

<%!
from mako.runtime import capture
from mysite.ctrl.utils import request_hash

def cache_for_anomynous(fn):
    def def_func(context, *args, **kwargs):
        cache = context.get('local').cache
        cache_enabled = getattr(cache.template, 'cache_enabled', True)
        user = context.get('request').user
        if not cache_enabled or user:
            val = capture(context, fn, *args, **kwargs)
        else:
            key = 'html.cache_for_anoumynous:%s' % request_hash()
            val = cache.get(key)
            if not val:
                val = capture(context, fn, *args, **kwargs)
                cache.set(key, val, timeout=300)
        context.write(val)
        return ''
    return def_func
%>

<%block decorator="cache_for_anonymous">

page content

</%block>

下一篇: 制作moosefs_debian安装包