mako根据条件判断是否使用页面缓存
最近遇到网站速度慢的情况,排查许久没查出什么原因。于是想着匿名用户的访问量占据了一半多,如果这一部分的请求全部缓存下来,那么应该能够很大程度上提升网站的响应速度。 之前已经在一些页面里面使用了 Mako 的页面缓存,具体的文档可以查看 Mako 官网
# 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>