[python]使用flask-caching缓存数据

打印 上一主题 下一主题

主题 684|帖子 684|积分 2052

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
简介

Flask-Caching 是 Flask 的一个扩展,为任何 Flask 应用步伐添加了对各种后端的缓存支持。它基于 cachelib 运行,并通过统一的 API 支持 werkzeug 的全部原始缓存后端。开发者还可以通过继承 flask_caching.backends.base.BaseCache 类来开发自己的缓存后端。
官方文档 - https://flask-caching.readthedocs.io/en/latest/index.html
安装
  1. pip install Flask-Caching
复制代码
设置

缓存通过缓存实例来管理
  1. from flask import Flask
  2. from flask_caching import Cache
  3. config = {
  4.     "DEBUG": True,          # some Flask specific configs
  5.     "CACHE_TYPE": "SimpleCache",  # Flask-Caching related configs
  6.     "CACHE_DEFAULT_TIMEOUT": 300
  7. }
  8. app = Flask(__name__)
  9. # tell Flask to use the above defined config
  10. app.config.from_mapping(config)
  11. cache = Cache(app)
复制代码
也可以使用init_app来延后配置缓存实例
  1. cache = Cache(config={'CACHE_TYPE': 'SimpleCache'})
  2. app = Flask(__name__)
  3. cache.init_app(app)
复制代码
还可以提供一个备用的配置字典,假如有多个Cache缓存实例,每个实例使用不同的后端,这将非常有用。
  1. #: Method A: During instantiation of class
  2. cache = Cache(config={'CACHE_TYPE': 'SimpleCache'})
  3. #: Method B: During init_app call
  4. cache.init_app(app, config={'CACHE_TYPE': 'SimpleCache'})
复制代码
缓存视图函数

使用cached()装饰器缓存视图函数,默认使用path作为缓存的key
  1. @app.route("/")
  2. @cache.cached(timeout=50)
  3. def index():
  4.     return render_template('index.html')
复制代码
cached 装饰器还有另一个可选参数叫做 unless。这个参数担当一个可调用对象,它返回 True 或 False。假如 unless 返回 True,那么将完全跳过缓存机制。
为了在视图中动态确定超时时间,可以返回 CachedResponse,这是 flask.Response 的子类。
  1. @app.route("/")
  2. @cache.cached()
  3. def index():
  4.     return CachedResponse(
  5.         response=make_response(render_template('index.html')),
  6.         timeout=50,
  7.     )
复制代码
缓存插拔式视图类
  1. from flask.views import View
  2. class MyView(View):
  3.     @cache.cached(timeout=50)
  4.     def dispatch_request(self):
  5.         return 'Cached for 50s'
复制代码
缓存其它函数

使用相同的 @cached 装饰器,还可以缓存其他非视图相关的函数的效果。需要留意更换 key_prefix,否则它将使用 request.path 作为 cache_key。键控制从缓存中获取什么内容。例如,假如一个键在缓存中不存在,将会在缓存中创建一个新的键值对条目。否则,将会返回该键的值(即缓存的效果)。
  1. @cache.cached(timeout=50, key_prefix='all_comments')
  2. def get_all_comments():
  3.     comments = do_serious_dbio()
  4.     return [x.author for x in comments]
  5. cached_comments = get_all_comments()
复制代码
自定义缓存键

有时您希望为每个路由定义自己的缓存键。使用 @cached 装饰器,您可以指定怎样生成这个键。当缓存键不应仅仅是默认的 key_prefix,而是必须从请求中的其他参数派生时,这大概会非常有用。例如,在缓存 POST 路由时,缓存键应该根据请求中的数据而不仅仅是路由或视图本身来确定,这时就可以使用这个功能。
  1. def make_key():
  2.    """A function which is called to derive the key for a computed value.
  3.       The key in this case is the concat value of all the json request
  4.       parameters. Other strategy could to use any hashing function.
  5.    :returns: unique string for which the value should be cached.
  6.    """
  7.    user_data = request.get_json()
  8.    return ",".join([f"{key}={value}" for key, value in user_data.items()])
  9. @app.route("/hello", methods=["POST"])
  10. @cache.cached(timeout=60, make_cache_key=make_key)
  11. def some_func():
  12.    ....
复制代码
记忆化

在记忆化中,函数参数也会包含在cache_key中
留意:对于不接收参数的函数来说,cached() 和 memoize() 现实上是相同的。
Memoize 也实用于方法,因为它会将 self或 cls 参数的身份作为缓存键的一部分。
记忆化背后的理论是,假如你有一个函数需要在一次请求中多次调用,那么它只会在第一次使用这些参数调用该函数时举行盘算。例如,一个 sqlalchemy 对象用来确定一个用户是否具有某个脚色。在一次请求中,你大概需要多次调用这个函数。为了避免每次需要这些信息时都访问数据库,你大概会做如下操作:
  1. class Person(db.Model):
  2.     @cache.memoize(50)
  3.     def has_membership(self, role_id):
  4.         return Group.query.filter_by(user=self, role_id=role_id).count() >= 1
复制代码
将可变对象(类等)作为缓存键的一部分大概会变得棘手。建议不要将对象实例转达给记忆化函数。然而,memoize 会对传入的参数执行 repr(),因此假如对象有一个返回唯一标识字符串的 __repr__ 函数,该字符串将被用作缓存键的一部分。
例如,一个 sqlalchemy 的 person 对象,返回数据库 ID 作为唯一标识符的一部分:
  1. class Person(db.Model):
  2.     def __repr__(self):
  3.         return "%s(%s)" % (self.__class__.__name__, self.id)
复制代码
删除记忆化缓存

您大概需要按函数删除缓存。使用上述示例,假设您更改了用户的权限并将其分配给某个脚色,但现在您需要重新盘算他们是否拥有某些成员资格。您可以使用 delete_memoized() 函数来实现这一点:
  1. cache.delete_memoized(user_has_membership)
复制代码
假如仅将函数名称作为参数提供,那么该函数的全部记忆化版本都将失效。然而,您可以通过提供与缓存时相同的参数值来删除特定的缓存。在下面的示例中,只有用户脚色的缓存被删除:
  1. user_has_membership('demo', 'admin')
  2. user_has_membership('demo', 'user')
  3. cache.delete_memoized(user_has_membership, 'demo', 'user')
复制代码
假如一个类方法被记忆化,您必须将类作为第一个 *args 参数提供。
  1. class Foobar(object):
  2.     @classmethod
  3.     @cache.memoize(5)
  4.     def big_foo(cls, a, b):
  5.         return a + b + random.randrange(0, 100000)
  6. cache.delete_memoized(Foobar.big_foo, Foobar, 5, 2)
复制代码
缓存Jinja2模板

根本使用
  1. {% cache [timeout [,[key1, [key2, ...]]]] %}
  2. ...
  3. {% endcache %}
复制代码
默认情况下,“模板文件路径” + “块开始行”的值被用作缓存键。此外,键名也可以手动设置。键会连接成一个字符串,这样可以避免在不同模板中评估相同的块。
将超时设置为 None 以表现没有超时,但可以使用自定义键。
  1. {% cache None, "key" %}
  2. ...
  3. {% endcache %}
复制代码
设置timeout为del来删除缓存值
  1. {% cache 'del', key1 %}
  2. ...
  3. {% endcache %}
复制代码
假如提供了键,您可以轻松生成模板片段的键,并在模板上下文外部删除它。
  1. from flask_caching import make_template_fragment_key
  2. key = make_template_fragment_key("key1", vary_on=["key2", "key3"])
  3. cache.delete(key)
复制代码
思量使用render_form_field和render_submit
  1. {% cache 60*5 %}
  2.     <form>
  3.     {% render_form_field(form.username) %}
  4.     {% render_submit() %}
  5.     </form>
  6. {% endcache %}
复制代码
清空缓存

清空应用缓存的简单示例
  1. from flask_caching import Cache
  2. from yourapp import app, your_cache_config
  3. cache = Cache()
  4. def main():
  5.     cache.init_app(app, config=your_cache_config)
  6.     with app.app_context():
  7.         cache.clear()
  8. if __name__ == '__main__':
  9.     main()
复制代码
某些后端实现不支持完全扫除缓存。此外,假如您不使用键前缀,一些实现(例如 Redis)会清空整个数据库。请确保您没有在缓存数据库中存储任何其他数据。
显式缓存数据

数据可以通过直接使用署理方法如 Cache.set() 和 Cache.get() 来显式缓存。通过 Cache 类还有很多其他可用的署理方法。
  1. @app.route("/html")
  2. @app.route("/html/<foo>")
  3. def html(foo=None):
  4.     if foo is not None:
  5.         cache.set("foo", foo)
  6.     bar = cache.get("foo")
  7.     return render_template_string(
  8.         "<html><body>foo cache: {{bar}}</body></html>", bar=bar
  9.     )
复制代码
根本使用示例
  1. from flask import Flask
  2. from flask_caching import Cache
  3. import time
  4. flask_cache = Cache(config={'CACHE_TYPE': 'SimpleCache'})
  5. app = Flask(__name__)
  6. fake_db = {
  7.     "zhangsan": "qwerty"
  8. }
  9. def do_io(username: str):
  10.     time.sleep(0.01)
  11.     return fake_db.get(username, "")
  12. @app.get("/user/<username>")
  13. def get_user(username):
  14.     if data := flask_cache.get(username):
  15.         print(f"getting data from cache, username: {username}")
  16.         return data
  17.     else:
  18.         print("data not found in cache")
  19.    
  20.     db_data = do_io(username)
  21.     flask_cache.set(username, db_data, timeout=10)
  22.     return db_data
  23. if __name__ == "__main__":
  24.     flask_cache.init_app(app)
  25.     app.run("127.0.0.1", 8000)
复制代码

  • 测试
  1. wrk -t1 -c10 -d30s http://127.0.0.1:8000/user/zhangsan
复制代码
SimpleCache在gunicorn中的问题

gunicorn会创建多个子历程,子历程之间是否共享simplecache?
先写一个普通的service,暴露两个api

  • GET /cache/: 根据key name获取缓存值
  • POST /cache: 添加缓存
  1. from flask import Flask, request
  2. from flask_caching import Cache
  3. from typing import Optional
  4. flask_config = {
  5.     "CACHE_TYPE": "SimpleCache",
  6.     "CACHE_DEFAULT_TIMEOUT": 300
  7. }
  8. app = Flask(__name__)
  9. app.config.from_mapping(flask_config)
  10. cache = Cache(app)
  11. @app.get("/cache/<foo>")
  12. def get_cached_data(foo: Optional[str]):
  13.     if not foo:
  14.         return "foo is None\n"
  15.     cache_rst = cache.get(foo)
  16.     if not cache_rst:
  17.         return f"key {foo} is not in cache\n"
  18.     return f"find key {foo} in cache, value is {cache_rst}\n"
  19. @app.post("/cache")
  20. def set_cached_data():
  21.     try:
  22.         req_body = request.get_json()
  23.     except Exception as e:
  24.         raise Exception(f"request body is not json format, error: {e}\n") from e
  25.    
  26.     key = req_body.get("key", None)
  27.     value = req_body.get("value", None)
  28.     if not key or not value:
  29.         return "key or value is None\n"
  30.     if cached_data := cache.get(key):
  31.         return f"key {key} is already in cache, value is {cached_data}\n"
  32.     cache.set(key, value)
  33.     return f"set key {key} in cache, value is {value}\n"
  34. if __name__ == "__main__":
  35.     app.run(host="0.0.0.0", port=5000)
复制代码
先用flask默认运行方式运行,测试接口是否正常
  1. # 添加键值对缓存
  2. curl -X POST http://127.0.0.1:5000/cache -H 'Content-Type: application/json' -d '{"key": "k1", "value": "v1"}'
  3. # 获取缓存
  4. curl http://127.0.0.1:5000/cache/k1
复制代码
假如响应正常的话,再用gunicorn启动。如下命令将启动4个工作子历程
  1. gunicorn demo:app -b 0.0.0.0:5000 -w 4 -k gevent --worker-connections 2000
复制代码
请求测试。第一个请求设置缓存,后面四个获取缓存,可见工作历程之间并不共享flask_cache。假如用gunicorn或多个flask service实例,最好换其他cache type,比如RedisCache。
  1. $ curl -X POST http://127.0.0.1:5000/cache -H 'Content-Type: application/json' -d '{"key": "k1", "value": "v1"}'
  2. set key k1 in cache, value is v1
  3. $ curl http://127.0.0.1:5000/cache/k1
  4. key k1 is not in cache
  5. $ curl http://127.0.0.1:5000/cache/k1
  6. key k1 is not in cache
  7. $ curl http://127.0.0.1:5000/cache/k1
  8. find key k1 in cache, value is v1
  9. $ curl http://127.0.0.1:5000/cache/k1
  10. key k1 is not in cache
复制代码
参考


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

星球的眼睛

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表