简介

在 Flask 开发中,我们经常会有数据库操作、模板渲染等,这些操作单次可能速度感觉挺快,压力不大,但是当次数一上来,那就不行了,各种问题就来了,所以为了缓解这些问题,我们经常会采用缓存的方法以空间换时间。

但是,我们要怎么缓存呢?在 Flask 的官方文档中有一个简单的解决方法,那就是利用 Werkzeug(Flask非常依赖它) 的 SimpleCache 来缓存,如果有兴趣可以查看一下这页文档:Caching。虽然这是官网的一个介绍,但是并非是很好的实践,因为我们需要参与的事情太多了,所以为了解决一些繁杂的手续,有人开发了一个插件 Flask-Cache,它能帮助我们解决很多不需要的代码,简化到只需要一个装饰器。

下面,我就以我实践过的项目介绍一下 Flask-Cache 的基本使用和一些比较好的实践。

安装 

这个就是老生常谈了,大多数的依赖都可以通过 pip 解决,这个也不例外:

1
pip install Flask-Cache

项目应用

一开始就先来个简单的应用,展示一下如何使用 Flask-Cache 吧,至于一些实践之类的就先介绍完基本用法之后在后面介绍。下面就举一个缓存 view 结果的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
# encoding: utf-8
from flask import Flask
from flask.ext.cache import Cache

app = Flask(__name__)
# Check Configuring Flask-Cache section for more details
cache = Cache(app,config={'CACHE_TYPE': 'simple'})


@app.route('/index')
@cache.cached()
def index():
    print "index called"
    return "Hello World"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

这里是一个简单的 Flask 应用,在 Line:12 我们指定了缓存 view index,所以我们想把服务器跑起来,然后访问一下这个 URL:

1
http://localhost:8080

当第一次访问的时候我们可以看到控制台的输出是:

1
2
3
* Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
index called
192.168.1.100 - - [02/Nov/2016 00:08:15] "GET /index HTTP/1.1" 200 -

然后我们再刷新一遍浏览器,这个时候看一下控制台的输出:

1
2
3
4
* Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
index called
192.168.1.100 - - [02/Nov/2016 00:08:15] "GET /index HTTP/1.1" 200 -
192.168.1.100 - - [02/Nov/2016 00:08:18] "GET /index HTTP/1.1" 200 -

对比一下可以发现,当我们第二次访问的时候,其实是没有再执行一遍 index 函数了,而是直接将缓存的结果返回了,这就达到了我们缓存的目的了。

清除缓存

可能有的时候我们修改过数据或者更换过模板之类的情况,需要清理缓存,这有主动和被动两种办法:

这里就针对两种方式分别介绍一下如何使用:

被动清除

被动清除就是我们在设置缓存装饰器的时候指定缓存生效时间,如果不想设置也行,那就在 Flask 的设置中加上配置项:CACHE_DEFAULT_TIMEOUT 这个配置,推荐使用变量 CACHE_DEFAULT_TIMEOUT 的配置方式,但是这里就介绍装饰器的方式,还是以之前的示例为例进行介绍:

1
2
3
4
5
@app.route('/index')                                                                                                         
@cache.cached(timeout=50)
def index():  
    print "index called"
    return "Hello World"

这里可以看到和之前的代码有所区别,区别就在于 cache.cached 是带参数了,参数就是 timeout,然后值是 50,其实就是说每次缓存的有效期是 50 秒,50 秒过期之后需要重新获取一遍,然后再缓存 50 秒。这样我们就可以在一定程度上保证缓存的新鲜度

主动清除

其实,要保持更高的新鲜度,主动清除才是比较合适的方式,主动清除就是我们调用 cache 的 delete 方法来删除指定的缓存,首先想看下 delete 的函数原型:

1
delete(*args, **kwargs)

好像看不出什么东西,那这里直说吧,一个调用的示例是这样的:

1
cache.delete(key='view//')

这是删除首页的示例,这里可能你会有点疑问了,这个 key 是什么,我要删除一个缓存我怎么知道它的 key 是什么?

对于这个问题,可能文档不会告诉你,但是我稍看了下源码,发现这个 key 的函数很简单:

1
2
3
4
5
6
7
8
9
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

在第 5 行 可以看到 key 就是 key_prefix % request.path,和请求的路径是相关的,那么 key_prefix 是什么呢,稍作查找就会发现是: key_prefix='view/%s',这样的话我们就可以很快得知道哪个 view 的 key 是什么,也能很方便得清除缓存了。

其他缓存

前面介绍的都是缓存 view 的,那么假设我要缓存数据库查询怎么办,其实也很方便,和 view 的缓存差不多,但是有一点点不一样的就是我们需要制定缓存的 key。因为我们前面可以看到,如果我们不指定 key 的话,默认的缓存 key 是和请求路径相关的,那么就会和 view 的缓存 key 冲突,这样的话就悲剧了,混淆了缓存。下面是一个缓存数据库查询的示例:

1
2
3
@cache.cached(key='all_post')
def get_all_post():
    return Post.query.all()

删除缓存

既然我们都明确指定 key 了,那么删除缓存也就水到渠成的事情了,直接调用 cache.delete('all_post') 就删除掉这个缓存了,简单明了。

缓存位置

前面我们都对这个问题避而不谈,现在是时候来聊聊了,那就是缓存缓存,我们的缓存到底缓存在哪个地方?

我们往前面的例子看一下,可以看到初始化 Cache 的代码:

cache = Cache(app,config={'CACHE_TYPE': 'simple'})

有个参数是 CACHE_TYPE,这里设置的值是 simple,那么代表着什么?根据官方文档的介绍,这个 simple 的意思就是说使用本地Python字典作为缓存的位置,也就是说我们的缓存都是放到内存中 python 的字典上,当获取缓存的时候直接从字典中根据 key 获取。

那么,除了 simple,还有那些可选的位置?根据官方文档的描述,目前支持的存储位置有:

Flask 模块化中的实践

前面举的例子是在一个简单的 Flask 应用中的,但是,我们正常在使用的时候并不会就这么简单得使用 Flask,我们会划分为各个模块,例如 routes.py models.py 以及 templates 等等,那么,在这种情况下,我们该如何在 Blueprint 上缓存 view 呢?

首先先举个 bad example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// views.py

cache = Cache()
@bp.route('test')
@cache.cached()
def test()
    ...


// persistens.py
cache = Cache()
@cache.cached()
def get_all_post()
    ...

这里是每个地方都实例化了一个 Cache ,这明显是不合理的,这里我给的一个比较合理的建议。

缓存初始化实践

首先先说下目录结构:

1
2
3
4
application/
    route.py
    service.py
    extensions.py

然后将初始化放到 extensions.py 里面去:

// extension.spy
...
cache = Cache()

然后再在需要使用的地方 import 进来就好了:

1
2
3
4
5
from application.extensions import cache

@cache.cached()
def test()
    ...

更新 Cache

之前说过了,当我们需要更新数据的时候需要刷新 Cache,有两种方式:主动和被动。主动刷新我们需要删除缓存,那么假设对于一个博客系统,我们再多个地方都缓存了 Cache,那么在更新文章的时候难道需要在更新函数里面刷新?像这样:

1
2
3
4
5
def update_post(post):
    update(post)
    cache.delete('view//post')
    cache.delete('get_all_post')
    ...

这样好像很难看,而且当我们在哪个添加了一个新的缓存的时候,还有可能要在这里添加东西,那么,有什么好的办法可以处理这个?我给的建议是使用 信号,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// service.py
def update_post(post):
    update(post)
    update_post_signal.send(app, post.id)

// signal_process.py

@update_post_signal.connect
def post_updated(sender, post_id):
    cache.delete('view//post')
    cache.delete('get_post_with_id_{}'.format(post_id))

通过 信号 我们将 cache 操作都统一起来了,这样的话我们以后修改/新增/删除都直接找这个文件就好了,不用到处找更新的地方了。

总结

ok,关于 Flask-Cache 缓存的就讲这么多先,如果想了解更多的内容,可以参照一下官方文档,内容不会很多,比较简单直接,而且内容也还可以。

Reference