用 Flask 来写个轻博客 (27) — 使用 Flask-Cache 实现网页缓存加速

时间:2022-08-20 20:29:12

目录

前文列表

用 Flask 来写个轻博客 (1) — 创建项目
用 Flask 来写个轻博客 (2) — Hello World!
用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy
用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表
用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解
用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)
用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)
用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级
用 Flask 来写个轻博客 (9) — M(V)C_Jinja 语法基础快速概览
用 Flask 来写个轻博客 (10) — M(V)C_Jinja 常用过滤器与 Flask 特殊变量及方法
用 Flask 来写个轻博客 (11) — M(V)C_创建视图函数
用 Flask 来写个轻博客 (12) — M(V)C_编写和继承 Jinja 模板
用 Flask 来写个轻博客 (13) — M(V)C_WTForms 服务端表单检验
用 Flask 来写个轻博客 (14) — M(V)C_实现项目首页的模板
用 Flask 来写个轻博客 (15) — M(V)C_实现博文页面评论表单
用 Flask 来写个轻博客 (16) — MV(C)_Flask Blueprint 蓝图
用 Flask 来写个轻博客 (17) — MV(C)_应用蓝图来重构项目
用 Flask 来写个轻博客 (18) — 使用工厂模式来生成应用对象
用 Flask 来写个轻博客 (19) — 以 Bcrypt 密文存储账户信息与实现用户登陆表单
用 Flask 来写个轻博客 (20) — 实现注册表单与应用 reCAPTCHA 来实现验证码
用 Flask 来写个轻博客 (21) — 结合 reCAPTCHA 验证码实现用户注册与登录
用 Flask 来写个轻博客 (22) — 实现博客文章的添加和编辑页面
用 Flask 来写个轻博客 (23) — 应用 OAuth 来实现 Facebook 第三方登录
用 Flask 来写个轻博客 (24) — 使用 Flask-Login 来保护应用安全
用 Flask 来写个轻博客 (25) — 使用 Flask-Principal 实现角色权限功能
用 Flask 来写个轻博客 (26) — 使用 Flask-Celery-Helper 实现异步任务

扩展阅读

Flask-Cache — Flask-Cache 0.13 documentation
缓存— Flask 0.10.1 documentation

Flask-Cache

网页的加载时间决定了你的网站是否能够受到用户的欢迎, 现在为止我们 blog 应用模板的每次渲染都会查询数据库, 这是不合理的. 对一些数据不被经常改变的页面我们应该引入缓存的机制来减少对数据库的访问.

Flask-Cache 能够让我们把视图函数返回的结果缓存下来, 以后再次渲染时, 只需要从缓存中取出这些结果就可以了, 无须重复的渲染.

  • 安装
pip install Flask-Cache
pip freeze > requirement.txt

应用 Flask-Cache 实现视图函数缓存

  • 初始化
    vim jmilkfansblog/extensions.py
from flask.ext.cache import Cache


...

#### Create the Flask-Cache's instance
cache = Cache()
  • 将 cache 对象注册到 app 对象中
    vim jmilkfansblog/__init__.py
from jmilkfansblog.extensions import cache

...

def create_app(object_name):
"""Create the app instance via `Factory Method`"""
...

#### Init the Flask-Cache via app object
cache.init_app(app)
  • 配置 Cache 类型
class DevConfig(Config):
"""Development config class."""
...

#### Flask-Cache's config
CACHE_TYPE = 'simple'

Flask-Cache 定义缓存的方式就是为你需要缓存的函数加上缓存装饰器, 这样的装饰器由多种类型, 对应装饰在不同的函数或视图函数中.

缓存无参数的普通函数

vim jmilkfansblog/controllers/blog.py

@cache.cached(timeout=7200, key_prefix='sidebar_data')
def sidebar_data():
"""Set the sidebar function."""

# Get post of recent
recent = db.session.query(Post).order_by(
Post.publish_date.desc()
).limit(5).all()

# Get the tags and sort by count of posts.
top_tags = db.session.query(
Tag, func.count(posts_tags.c.post_id).label('total')
).join(
posts_tags
).group_by(Tag).order_by('total DESC').limit(5).all()
return recent, top_tags

NOTE 1: 装饰器 @ cache.cached() 的形参数 key_prefix 是必须的, 并且传入的值是唯一的, 这样 Flask-Cache 才能够正确的返回该函数缓存的值.

NOTE 2: 该函数被缓存的时间设定为 2 小时, 这是因为该函数返回的值不被经常改变, 而且就算是被改变了也不会对整体页面的显示有大的影响.

缓存带参数的普通函数

装饰器 cache.cached() 不会考虑到参数的问题, 如果函数在接收到不同的实参后还返回相同结果的话, 这明显是不科学的. 为了解决这个问题, 我们使用 @cache.memoize() 装饰器来定义.
vim jmilkfansblog/models.py

    @staticmethod
@cache.memoize(60)
def verify_auth_token(token):
"""Validate the token whether is night."""

serializer = Serializer(
current_app.config['SECRET_KEY'])
try:
# serializer object already has tokens in itself and wait for
# compare with token from HTTP Request /api/posts Method `POST`.
data = serializer.loads(token)
except SignatureExpired:
return None
except BadSignature:
return None

user = User.query.filter_by(id=data['id']).first()
return user

NOTE 1: 装饰器 @cache.memoize() 不仅仅会缓存运行的结果, 还缓存调用时的参数, 所以在函数接受到相同的参数时, 就会将缓存中该参数对应的结果返回.

NOTE 2: @cache.momoize() 非常不适用于返回结果依赖全局变量或依赖数据库查询的函数, 因为在这很容易导致数据竞态, 即输入了相同的参数, 可能会返回不同的结果. 最适合被缓存的函数是纯函数(在传入的参数相同的情况下会输出相同的结果)

缓存无动态参数的视图函数

# Use the Blueprint object to set the Route URL
# Register the view function into blueprint
@blog_blueprint.route('/')
@blog_blueprint.route('/<int:page>')
@cache.cached(timeout=60)
def home(page=1):
"""View function for home page"""

posts = Post.query.order_by(
Post.publish_date.desc()
).paginate(page, 10)

recent, top_tags = sidebar_data()

return render_template('home.html',
posts=posts,
recent=recent,
top_tags=top_tags)

NOTE 1: 这里仍然是使用装饰器 @cache.cached(), 但却不必需要指定 key_prefix 形参.
NOTE 2: timeout 表示该视图函数返回的结果将会被缓存的时长.

缓存带动态参数的视图函数

因为该类型视图函数的参数 post_id 是被动态解析成为 URL(/post/<string:post_id>) 的, 所以并不是每一次调用都会返回相同的结果, 这样就需要我们进一步的确认, 只有在确定两次不同请求的都是同一个目标(即 URL)时, 才会将缓存返回, 否则创建一个新的缓存.

def make_cache_key(*args, **kwargs):
"""Dynamic creation the request url."""

path = request.path
args = str(hash(frozenset(request.args.items())))
return (path + args).encode('utf-8')
...

@blog_blueprint.route('/post/<string:post_id>', methods=('GET', 'POST'))
@cache.cached(timeout=60, key_prefix=make_cache_key)
def post(post_id):
"""View function for post page"""

# Form object: `Comment`
form = CommentForm()

...

NOTE 1: 这里我们仍然使用了 key_prefix 参数来进行定位, 而且该形参除了可以接受 String 类型对象之后, 也可以接收一个函数.

NOTE 2: 函数 make_cache_key() 会动态的生成一个唯一的缓存键, 用于确定这一次请求的 URL 是否存在缓存, 如果存在则返回, 如果不存在则新建. 所以在该函数中我们使用了请求 path + hash 字符串结合而成的唯一字符串作为缓存键.