前言
最近自己一直在折腾站点程序,觉得自己的站点响应速度越来越慢。就想着引入一些缓 存,毕竟这是提高速度最简单的方式了。但是动态博客不像静态博客那样,不需要考虑数 据的更新,全局添加缓存。所以若想加入缓存就需要考虑数据的更新问题。
这里需要分为管理员更新信息和游客浏览信息,管理员更新属于可控的更新,比如管理员 自己对于网站的更新(更新文章,发布文章,更新站点信息等)操作,我们可以自己控制 清除对应缓存等待新的缓存,而且管理员不会很频繁的有什么大的更新。游客浏览的信息 分为频繁更新页面和少有更新页面。
对于少有信息更新的页面,比如关于,说说还有标签,分类等页,我们可以直接设置 30
天的缓存期限。但是一旦页面有一些信息的更新,这里比如love me
按钮的次数,一旦有
游客点击(只有不曾点击过的游客才会请求到路由)并且成功请求到love-me
路由,就清
除所有的缓存,再更新新的缓存数据。
而一些经常更新信息的页面,比如文章页和首页需要更新文章浏览次数,所以不应该使用 缓存,但是用了缓存后速度确实有很大的提升,所以就给每一篇文章加入5分钟的缓存期 限。
其实就本站程序而言,唯一拖慢速度的程序就是app/main/__init__.py
文件里定义更新
全局信息的global_datas
函数,而函数返回的信息除了love-me
按钮次数外都是管理员
更新的数据,所以此函数必须添加缓存。
使用 redis
虽然werkzeug
库中为我们实现了基础的缓存支持,但是为了方便免去自己封装,我决定
使用flask-cache
插件。 插件文档
下面就是给对应的路由添加对应的缓存,这里只要按照文档的来做就可以了。
由于文档给我们的例子使用的是simple
类型,该类型使用的是本地Python
字典作为缓
存的存储,但是这只推荐用于测试开发环境,不推荐用于生产环境。所以我建议使用
redis
类型,使用redis
数据库做缓存存储。
配置 redis
Windows
系统下载地址 here
其他系统地址 here
安装 redis
操作库:pip install redis
。这一步很关键,没了他我们并不能成功的
使用python
操作redis
。
配置 redis 类型信息
# YuBlog/config.py
...
class Config(object):
...
# cache 使用 Redis 数据库缓存配置
CACHE_TYPE = 'redis'
CACHE_REDIS_HOST = '127.0.0.1'
CACHE_REDIS_PORT = 6379
CACHE_REDIS_DB = os.getenv('CACHE_REDIS_DB') or ''
CHCHE_REDIS_PASSWORD = os.getenv('CHCHE_REDIS_PASSWORD') or ''
@staticmethod
def init_app(app):
pass
# YuBlog/app/__init__.py
...
from flask_cache import Cache
cache = Cache()
def create_app(config_name):
...
cache.init_app(app)
...
缓存分页疑问
添加缓存只需要按照文档的例子,给路由或者函数加对应的装饰器就可以了。
在给首页加缓存后,点击首页确实速度提升了很多,但是由于首页是分页显示全部内容 的。当点击第二页和第三页时,发现响应的内容并不是应该出现的内容,而是之前缓存的 首页也就是第一页的内容。
这是为什么呢?原因很简单,cache.cached(timeout=None, key_prefix='view/%s', unless=None)
装饰器有三个参数:
- timeout: 指的是缓存过期时间,默认永不过期;
- key_prefix: 指缓存项键值的前缀,默认
'view/%s'
; - unless: 回调函数,当返回
True
时,缓存不起作用,默认缓存有效。
不明白默认的key_prefix
具体是什么,而且文档也没有详细的说明,索性看他的源码:
class Cache(object):
...
def cached(self, timeout=None, key_prefix='view/%s', unless=None):
def decorator(f):
@functools.wraps(f)
def decorated_function(*args, **kwargs):
#: Bypass the cache entirely.
if callable(unless) and unless() is True:
return f(*args, **kwargs)
try:
cache_key = decorated_function.make_cache_key(*args, **kwargs)
rv = self.cache.get(cache_key)
except Exception:
if current_app.debug:
raise logger.exception("Exception possibly due to cache backend.")
return f(*args, **kwargs)
if rv is None:
rv = f(*args, **kwargs)
try:
self.cache.set(cache_key, rv, timeout=decorated_function.cache_timeout)
except Exception:
if current_app.debug:
raise logger.exception("Exception possibly due to cache backend.")
return f(*args, **kwargs)
return rv
def make_cache_key(*args, **kwargs):
if callable(key_prefix):
cache_key = key_prefix()
elif '%s' in key_prefix:
cache_key = key_prefix % request.path
else:
cache_key = key_prefix
return cache_key
decorated_function.uncached = f
decorated_function.cache_timeout = timeout
decorated_function.make_cache_key = make_cache_key
return decorated_function
return decorator
这段代码是cache.cached()
装饰器核心部分了,我后面的疑问在看完这段代码后就很明
白了。首先,key_prefix
参数以%s
作为占位符,用来格式化request.path
:
...
elif '%s' in key_prefix:
cache_key = key_prefix % request.path
...
那么request.path
是什么了,request
引自flask
库,我们将他放到路由函数中去,
打印一次就知道了。很显然这是路由url
的路径了,首页路径就是/index
,归档页就是
/archives
。而上面之所以首页的第二页第三页缓存的内容是一样的,就是因为,他们虽
然url
是/index?page=2
和/index?page=3
,但是他们的路径是一样的,即:
/index
。装饰器把他们当作是一个缓存项了。
分页缓存的解决
那这种情况如何处理,在我查了一圈网络上的博客后,发现千篇一律不是照搬文档,就是 稍加改变的文档实例。没有关于这种情况的处理实例,既然文档也没有,那还是得看源 码。
其实从上面的源码中我们可以发现,key_prefix
参数,不仅可以接收一个字符串,还可
以接受一个函数。
# 返回缓存 key 的函数
def make_cache_key(*args, **kwargs):
if callable(key_prefix):
cache_key = key_prefix()
elif '%s' in key_prefix:
cache_key = key_prefix % request.path
else:
cache_key = key_prefix
return cache_key
Python
的callable()
函数检测key_prefix
是否是一个可调用函数,如果是,即调用
函数返回缓存key
。
所以我们只需实现一个函数,使得key_prefix
不在只是为路径,还需加上页数信息,这
样每一页的键值就不一样了。
def cache_key(*args, **kwargs):
"""
自定义缓存键:
首页和归档页路由 url 是带参数的分页页数组成:/index?page=2
flask-cache 缓存的 key_prefix 默认值获取 path :/index
需要自定义不同页面的 cache_key : /index/page/2
"""
path = request.path
args = dict(request.args.items())
return (path + '/page/' + str(args['page'])) if args else path
request.args.items()
返回的是一个什么呢,我们放入路由函数打印出来看看:
@app.route('/index')
def index():
path = request.path
args = request.args.items()
print(path)
print(args)
print(type(args))
print(args.__next__())
...
调用返回:
/index
<generator object items at 0x000000000626A948>
<class 'generator'>
('page', '3')
可见args
是一个生成器类型,我们使用生成器的__next__()
方法打印他的值,返回的就
是我们的页数了,我们将他们组成字典方便调用。
如此我们就解决了分页路由函数的缓存键值相同的问题了,接下来只要将路由函数上的装
饰器key_prefix
参数的值改为函数cache_key
就可以了。
最后
对于写一个个人博客,使用flask
就感觉恰到好处。如果用django
就觉得是在用大炮打
蚊子,杀鸡用牛刀。但是折腾的劲来了,最求的功能多了,插件库用的多了,flask
就变
得越来越臃肿,没有起初的从容优雅,显得越来越像django
。
其实折腾多了,就会发现,写个博客就为了记录,要太多的功能其实并没有太大的卵用。 一个简单的静态博客远远满足需求。