【Flask插件】Flask-Caching使用说明

了解Flask的应该都清楚,Flask是轻量级web框架,自带的功能比较少,如果需要实现其他功能,就需要安装插件。

在Flask需要加上缓存的话,当属flask-caching为目前最流行的Flask插件,本文记录一下在项目中如何使用flask-caching

安装

安装flask-caching当然是通过pip

pip install flask-caching

引入项目

简单引入

我们通过引入Cache类来使用flask-caching

from flask import Flask
from flask_caching import Cache

config = {
    "CACHE_TYPE": "simple", # 缓存类型,后面会降到
    "CACHE_DEFAULT_TIMEOUT": 300 #默认缓存时长,单位:s
}
app = Flask(__name__)
app.config.from_mapping(config) #读取配置
cache = Cache(app)

后续使用flask-caching大部分都是通过装饰器的方式使用,当然也可以自定义使用setgetdelete等方法

配置参数

上方的CACHE_TYPECACHE_DEFAULT_TIMEOUT都是flask-caching的配置参数,flask-caching的配置参数还有很多,这里说一下其他常用的配置参数

  1. CACHE_TYPE

    缓存类型,可选(大小写敏感):

    • null默认值,即不做缓存

    • simple。通过Python数组做缓存,非线程安全,重启服务缓存就没了测试时可选,不建议用于正式环境

    • filesystem。使用文件做缓存。如果选用这个模式,需要设置CACHE_DIR,这个参数为一个目录

    • redis。使用redis做缓存

      如果选用这个模式,则可选配置参数为:CACHE_REDIS_HOSTCACHE_REDIS_PORTCACHE_REDIS_PASSWORDCACHE_REDIS_DB,即redis的主机(默认localhost)、端口(默认6379)、密码(默认为None)、数据库(默认为0)。

      如果不想设置那么多参数,可以直接设置CACHE_REDIS_URL参数,格式为:redis://user:[email protected]:6379/2

    • redissentinel。使用有主从备份的redis做缓存

      如果选用这个模式,则可选配置参数为:CACHE_REDIS_SENTINELSCACHE_REDIS_SENTINEL_MASTERCACHE_REDIS_PASSWORDCACHE_REDIS_DB,其中CACHE_REDIS_SENTINELS为包含主机名端口号列表CACHE_REDIS_SENTINEL_MASTER为主数据库。

    • uwsgi。使用uwsgi做缓存

      如果选用这个模式,则可选配置参数为:CACHE_UWSGI_NAME,格式为:[email protected]:3031

    • memcached。使用memcached做缓存

      如果选用这个模式,则可选参数为:CACHE_MEMCACHED_SERVERSCACHE_MEMCACHED_USERNAMECACHE_MEMCACHED_PASSWORD

    • 其他的模式还有:gaememcachedsaslmemcachedspreadsaslmemcached

  2. CACHE_DEFAULT_TIMEOUT

    默认的缓存时长,默认值:300,即5分钟。

  3. CACHE_THRESHOLD

    最大缓存数量,默认是500,当缓存数超过500时,会删除快过期的缓存。仅作用于simplefilesystem两种缓存模式下

  4. CACHE_KEY_PREFIX

    当设置了这个参数后,会给每个主键前面加上这个参数,这样可以确保不同的项目使用同一个数据库,而不会发生主键冲突。仅作用于redismemcached及相关缓存模式下

缓存试图函数

对于flask-caching来说,最常见的用法就是用于缓存试图函数,通过装饰器的方式生效,使用示例

@app.route("/")
@cache.cached(timeout=50) #注意cache的装饰器在@app.route下方!
def index():
    return render_template('index.html')

这里默认的主键是request.path生成的,像这里的主键就是.index

对于无需传入参数的试图函数来说,这样做没有问题,但是如果是需要传入参数的试图函数,这样就会造成:无论你传入什么参数,获取到的数据都是一样的

使用query_string

像上面说的问题,显然不是一个最优的使用方法。

我们可以在装饰器里面用到query_string参数,设置query_string=True,就会将传入的参数以固定的顺序加到主键中,这样传参?limit=10&offset=20和传参?offset=20&limit=10生成的主键是同一个,因此不必担心传参的顺序。用法示例:

@app.route("/")
@cache.cached(timeout=50,query_string=True) 
def index():
        page=requests.args.get('page')
        limit=requests.args.get('limit')
    return render_template('index.html',page=page,limit=limit)

使用自定义主键生成方法

首先提个问题,上面通过query_string=True的方式可以保证以get方式传入的方式不同可以得到不同的结果,那么以post提交的参数生不生效呢?

想要回答这个问题,我们可以直接看一下flask-caching的参数,找到query_string=True生效的地方

可以看到,当query_string=True时,调用的是_make_cache_key_query_string函数,再看看_make_cache_key_query_string函数:

可以看出_make_cache_key_query_string这个函数生成主键的方法:

  1. requests.args的参数使用sorted排序生成一个数组 -- > (('limit','12'),('page':'1'))
  2. 将生成的数组使用str方法转为字符串 --> "(('limit','12'),('page':'1'))"
  3. 使用hash_method(默认是md5)将字符串转为md5 --> 7ceb4a3f01397ea783424a2bde6b6e4
  4. request.path和md5合并作为主键

像上方的例子,生成的主键值为:.index7ceb4a3f01397ea783424a2bde6b6e4d

显而易见,默认的方法并没有考虑post提交的数据,因此我们需要一种更好的方法,将post提交的数据也考虑进来。

我们继续看源码,找到Cache().cached方法的备注:

我们看到,@cache.cached()方法有个key_prefix参数,用于生成默认主键,默认就是view/%s,和%s就是上面说的request.path

同时,key_prefix还可以是一个函数!我们看看当key_prefix是函数时是哪里运行的:

有了,_make_cache_key函数里面检测key_prefix是不是函数,如果是函数的话用使用key_prefix作为主键生成的算法。

这样的话就好办了,我们可以自定义一个主键生成算法,可以使用request的所有方法。参考_make_cache_key_query_string写一个算法:

import hashlib

def make_cache_key():
    #get传入的参数
    args_as_sorted_tuple = tuple(
        sorted((pair for pair in request.args.items(multi=True)))
    )
    args_as_bytes = str(args_as_sorted_tuple).encode()
    #post提交的参数
    form_as_sorted_tuple = tuple(
        sorted((pair for pair in request.form.items(multi=True)))
    )
    form_as_bytes = str(args_as_sorted_tuple).encode()

    hashed_args = str(hashlib.md5(args_as_bytes+form_as_bytes).hexdigest())
    cache_key = request.path + hashed_args
    return cache_key

@app.route("/")
@cache.cached(timeout=50,key_prefix=make_cache_key) 
def index():
        page=requests.args.get('page')
        limit=requests.args.get('limit')
    return render_template('index.html',page=page,limit=limit)

缓存非视图函数

如果是非视图函数,我们同样可以使用@cache.cached装饰器,唯一要注意的是需要自定义一个key_prefix,比如:

@cache.cached(timeout=50, key_prefix='all_comments')
def get_all_comments():
    comments = do_serious_dbio()
    return [x.author for x in comments]

cached_comments = get_all_comments()

Memoization

使用@cache.memoize装饰器

对于非视图函数来说,如果非试图函数无需传入参数,那么@cache.cached@cache.memoize的用法是一样的,那么为什么要用@cache.memoize呢?

我们同样看一下_make_cache_key这个函数的方法

  • 如果key_prefix是函数,那么就调用这个函数生成主键
  • 如果key_prefix%s占位符,那么将%s替换为request.path
  • 如果key_prefix是自定义主键,则直接返回自定义的主键

那么这里就有问题了,对于有参的非视图函数来说,无论参数是怎么变化,返回的都是同一个缓存值,这无疑是有问题的,这也是为什么说:对于非视图函数来说,如果非试图函数无需传入参数,那么@cache.cached@cache.memoize的用法是一样的。

使用@cache.memoize

因此就有了@cache.memoize这个装饰器,@cache.memoize的用法也很简单

@cache.memoize(timeout=50)
def param_func(a, b):
        return a + b + random.randrange(0, 1000)

通过@cache.memoize缓存函数在从数据库查询数据的时候很有用,比如:你要从数据库中查文章的评论,用户每次打开这篇文章都会查询一次,当访问量很高的时候,这样无疑会加重数据库的负担,这个时候如果可以将查询结果做个缓存,那么只有第一次打开文章时才需要查询数据库,后面再打开这篇文章都直接从缓存里面提取结果!

删除@cache.memoize缓存

上方我们使用@cache.memoizeparam_func函数做了缓存,删除缓存的方法也很简单

假如我们做了2个缓存:

>>> param_func(1, 2)
32
>>> param_func(1, 2)
32
>>> param_func(2, 2)
47

可以通过cache.delete_memoized删除缓存

>>> cache.delete_memoized(param_func, 1, 2)
>>> param_func(1, 2)
13
>>> param_func(2, 2)
47

像上方的例子,我们删除了param_func(1, 2)的缓存。

cache.delete_memoized第一个参数是需删除缓存的函数本身,而不是函数名

后面的可以带参数,也可以不带。

  • 如果带参数,像cache.delete_memoized(param_func, 1, 2) 删除的就是param_func(1, 2)的缓存

  • 假如不带参数,像cache.delete_memoized(param_func),那么删除的就是所有param_func函数的缓存

自定义使用flask-caching

我们可以像使用redis一样使用flask-caching

# 缓存一个键值对
cache.set('name','Abbey')

# 从缓存提取值
cache.get('name')

# 删除一个缓存
cache.delete('name')

# 清除所有缓存
cache.clear()

本文作者:Abbey

本文链接:https://www.abbeyok.com/archives/396

版权声明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0许可协议。转载请注明出处!

【开源】基于Flask二开的博客系统-支... <<
0 条评论

请先登陆注册

已登录,注销 取消