core api其他电脑不能访问接口_第 12 篇:加缓存为接口提速
作者:HelloGitHub—追梦人物
目前,用户对于接口的操作基本都需要查询数据库。获取文章列表需要从数据库查询,获取单篇文章需要从数据库查询,获取评论列表也需要查询数据。但是,对于博客中的很多资源来说,在某个时间段内,他们的内容几乎都不会发生更新。例如文章详情,文章发表后,除非对其内容做了修改,否则内容就不会变化。还有评论列表,如果没人发布新评论,评论列表也不会变化。
要知道查询数据库的操作相对而言是比较缓慢的,而直接从内存中直接读取数据就会快很多,因此缓存系统应运而生。将那些变化不那么频繁的数据缓存到内存中,内存中的数据相当于数据库中的一个副本,用户查询数据时,不从数据库查询而是直接从缓存中读取,数据库的数据发生了变化时再更新缓存,这样,数据查询的性能就大大提升了。
当然数据库性能也没有说的那么不堪,对于大部分访问量不大的个人博客而言,任何关系型数据库都足以应付。但是我们学习 django-rest-framework 不仅仅是为了写博客,也许你在工作中,面对的是流量非常大的系统,这时候缓存就不可或缺。
确定需缓存的接口
先来整理一下我们已有的接口,看看哪些接口是需要缓存的:
接口名 URL 需缓存 文章列表 /api/posts/ 是 文章详情 /api/posts/:id/ 是 分类列表 /categories/ 是 标签列表 /tags/ 是 归档日期列表 /posts/archive/dates/ 是 评论列表 /api/posts/:id/comments/ 是 文章搜索结果 /api/search/ 否
补充说明
- 文章列表:需要缓存,但如果有文章修改、新增或者删除时应使缓存失效。
- 文章详情:需要缓存,但如果文章内容修改或者删除了应使缓存失效。
- 分类、标签、归档日期:可以缓存,但同样要注意在相应的数据变化时使缓存失效。
- 评论列表:可以缓存,新增或者删除评论时应使缓存失效。
- 搜索接口:因为搜索的关键词是多种多样的,可以缓存常见搜索关键词的搜索结果,但如何确定常见搜索关键词是一个复杂的优化问题,这里我们不做任何缓存处理。
配置缓存
django 为我们提供了一套开箱即用的缓存框架,缓存框架对缓存的操作做了抽象,提供了统一的读写缓存的接口。无论底层使用什么样的缓存服务(例如常用的 Redis、Memcached、文件系统等),对上层应用来说,操作逻辑和调用的接口都是一样的。
配置 django 缓存,最重要的就是选择一个缓存服务,即缓存结果存储和读取的地方。本项目中我们决定开发环境使用本地内存(Local Memory)缓存服务,线上环境使用 Redis 缓存。
开发环境配置
在开发环境的配置文件 settings/local.py 中加入以下的配置项即开启本地内存缓存服务。
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', }}
线上环境配置
线上环境使用到 Redis 缓存服务,django 并未内置 Redis 缓存服务的支持,不过对于 Redis 来说当然不缺乏第三方库的支持,我们选择 django-redis-cache,先来安装它:
$ pipenv install django-redis-cache
然后在项目的线上环境配置文件 settings/production.py 中加入以下配置:
CACHES = { "default": { "BACKEND": "redis_cache.RedisCache", "LOCATION": "redis://:UJaoRZlNrH40BDaWU6fi@redis:6379/0", "OPTIONS": { "CONNECTION_POOL_CLASS": "redis.BlockingConnectionPool", "CONNECTION_POOL_CLASS_KWARGS": {"max_connections": 50, "timeout": 20}, "MAX_CONNECTIONS": 1000, "PICKLE_VERSION": -1, }, },}
这样,django 的缓存功能就启用了。至于如何启动 Redis 服务,请参考教程最后的 Redis 服务部分。
drf-extensions Cache
django 的缓存框架比较底层,drf-extensions 在 django 缓存框架的基础上,针对 django-rest-framework 封装了更多缓存相关的辅助函数和类,我们将借助这个第三方库来大大简化缓存逻辑的实现。
首先安装它:
$ pipenv install drf-extensions
那么 drf-extensions 对缓存提供了哪些辅助函数和类呢?我们需要用到的主要有这些:
KeyConstructor
可以理解为缓存键生成类。我们先来看看 API 接口缓存的逻辑,伪代码是这样的:
给定一个 URL, 尝试从缓存中查找这个 URL 接口的响应结果if 结果在缓存中: return 缓存中的结果else: 生成响应结果 将响应结果存入缓存 (以便下一次查询) return 生成的响应结果
缓存结果是以 key-value 的键值对形式存储的,这里关键的地方在于存储或者查询缓存结果时,需要生成相应的 key。例如我们可以把 API 请求的 URL 作为缓存的 key,这样同一个接口请求将返回相同的缓存内容。但是在更为复杂的场景下,不能简单使用 URL 作为 key,比如即使是同一个 API 请求,已认证和未认证的用户调用接口得到的结果是不一样的,所以 drf-extensions 使用 KeyConstructor 辅助基类来提供灵活的 key 生成方式。
KeyBit
可以理解为 KeyConstructor 定义的 key 生成规则中的某一项规则定义。例如,同一个 API 请求,已认证和未认证的用户将得到不同的响应结果,我们可以定义 key 的生成规则为请求的 URL + 用户的认证 id。那么 URL 可以看成一个 KeyBit,用户 id 是另一个 KeyBit。
cache_response 装饰器
这个装饰器用来装饰 django-rest-framework 的视图(单个视图函数、视图集中的 action 等),被装饰的视图将具备缓存功能。
缓存博客文章
我们首先来使用 cache_response 装饰器缓存文章列表接口,代码如下:
blog/views.pyfrom rest_framework_extensions.cache.decorators import cache_responseclass PostViewSet( mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): # ... @cache_response(timeout=5 * 60, key_func=PostListKeyConstructor()) def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) @cache_response(timeout=5 * 60, key_func=PostObjectKeyConstructor()) def retrieve(self, request, *args, **kwargs): return super().retrieve(request, *args, **kwargs)
这里我们分别装饰了 list(获取文章列表的 action)和 retrieve(获取单篇文章),timeout 参数用于指定缓存失效时间, key_func 指定缓存 key 的生成类(即 KeyConstructor),当然 PostListKeyConstructor、和 PostObjectKeyConstructor 还未定义,接下来我们就来定义这两个缓存 key 生成类:
blog/views.pyfrom rest_framework_extensions.key_constructor.bits import ( ListSqlQueryKeyBit, PaginationKeyBit, RetrieveSqlQueryKeyBit,)from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructorclass PostListKeyConstructor(DefaultKeyConstructor): list_sql = ListSqlQueryKeyBit() pagination = PaginationKeyBit() updated_at = PostUpdatedAtKeyBit()class PostObjectKeyConstructor(DefaultKeyConstructor): retrieve_sql = RetrieveSqlQueryKeyBit() updated_at = PostUpdatedAtKeyBit()
PostListKeyConstructor 用于文章列表接口缓存 key 的生成,它继承自 DefaultKeyConstructor,这个基类中定义了 3 条缓存 key 的 KeyBit:
- 接口调用的视图方法的 id,例如 blog.views. PostViewSet.list。
- 客户端请求的接口返回的数据格式,例如 json、xml。
- 客户端请求的语言类型。
另外我们还添加了 3 条自定义的缓存 key 的 KeyBit:
- 执行数据库查询的 sql 查询语句
- 分页请求的查询参数
- Post 资源的最新更新时间
以上 6 条分别对应一个 KeyBit,KeyBit 将提供生成缓存键所需要的值,如果任何一个 KeyBit 提供的值发生了变化,生成的缓存 key 就会不同,查询到的缓存结果也就不一样,这个方式为我们提供了一种有效的缓存失效机制。例如 PostUpdatedAtKeyBit 是我们自定义的一个 KeyBit,它提供 Post 资源最近一次的更新时间,如果资源发生了更新,返回的值就会发生变化,生成的缓存 key 就会不同,从而不会让接口读到旧的缓存值。PostUpdatedAtKeyBit的代码如下:
blog/views.pyfrom .utils import UpdatedAtKeyBitclass PostUpdatedAtKeyBit(UpdatedAtKeyBit): key = "post_updated_at"
因为资源更新时间的 KeyBit 是比较通用的(后面我们还会用于评论资源),所以我们定义了一个基类 UpdatedAtKeyBit,代码如下:
blog/utils.pyfrom datetime import datetimefrom django.core.cache import cachefrom rest_framework_extensions.key_constructor.bits import KeyBitBaseclass UpdatedAtKeyBit(KeyBitBase): key = "updated_at" def get_data(self, **kwargs): value = cache.get(self.key, None) if not value: value = datetime.utcnow() cache.set(self.key, value=value) return str(value)
get_data 方法返回这个 KeyBit 对应的值,UpdatedAtKeyBit 首先根据设置的 key 从缓存中读取资源最近更新的时间,如果读不到就将资源最近更新的时间设为当前时间,然后返回这个时间。
当然,我们需要自动维护缓存中记录的资源更新时间,这可以通过 django 的 signal 来完成:
blog/models.pyfrom django.db.models.signals import post_delete, post_savedef change_post_updated_at(sender=None, instance=None, *args, **kwargs): cache.set("post_updated_at", datetime.utcnow())post_save.connect(receiver=change_post_updated_at, sender=Post)post_delete.connect(receiver=change_post_updated_at, sender=Post)
每当有文章(Post)被新增、修改或者删除时,django 会发出 post_save 或者 post_delete 信号,post_save.connect 和 post_delete.connect 设置了这两个信号的接收器为 change_post_updated_at,信号发出后该方法将被调用,往缓存中写入文章资源的更新时间。
整理一下请求被缓存的逻辑:
- 请求文章列表接口
- 根据 PostListKeyConstructor 生成缓存 key,如果使用这个 key 读取到了缓存结果,就直接返回读取到的结果,否则从数据库查询结果,并把查询的结果写入缓存。
- 再次请求文章列表接口,PostListKeyConstructor 将生成同样的缓存 key,这时就可以直接从缓存中读到结果并返回了。
缓存更新的逻辑:
- 新增、修改或者删除文章,触发 post_delete, post_save 信号,文章资源的更新时间将被修改。
- 再次请求文章列表接口,PostListKeyConstructor 将生成不同的缓存 key,这个新的 key 不在缓存中,因此将从数据库查询最新结果,并把查询的结果写入缓存。
- 再次请求文章列表接口,PostListKeyConstructor 将生成同样的缓存 key,这时就可以直接从缓存中读到结果并返回了。
PostObjectKeyConstructor 用于文章详情接口缓存 key 的生成,逻辑和 PostListKeyConstructor 是完全一样。
缓存评论列表
有了文章列表的缓存,评论列表的缓存只需要依葫芦画瓢。
KeyBit 定义:
blog/views.pyclass CommentUpdatedAtKeyBit(UpdatedAtKeyBit): key = "comment_updated_at"
KeyConstructor 定义:
blog/views.pyclass CommentListKeyConstructor(DefaultKeyConstructor): list_sql = ListSqlQueryKeyBit() pagination = PaginationKeyBit() updated_at = CommentUpdatedAtKeyBit()
视图集:
@cache_response(timeout=5 * 60, key_func=CommentListKeyConstructor())@action( methods=["GET"], detail=True, url_path="comments", url_name="comment", pagination_class=LimitOffsetPagination, serializer_class=CommentSerializer, ) def list_comments(self, request, *args, **kwargs): # ...
缓存其它接口
其它接口的缓存大家可以根据上述介绍的方法来完成,就留作练习了。
Redis 服务
本地内存缓存服务配置简单,适合在开发环境使用,但无法适应多线程和多进程适的环境,线上环境我们使用 Redis 做缓存。有了 Docker,启动一个 Redis 服务就是一件非常简单的事。
在线上环境的容器编排文件 production.yml 中加入一个 Redis 服务:
version: '3'volumes: static: database: esdata: redis_data:services: hellodjango.rest.framework.tutorial: ... depends_on: - elasticsearch - redis redis: image: 'bitnami/redis:5.0' container_name: hellodjango_rest_framework_tutorial_redis ports: - '6379:6379' volumes: - 'redis_data:/bitnami/redis/data' env_file: - .envs/.production
然后在 .envs/.production 文件中添加如下的环境变量,这个值将作为 redis 连接的密码:
REDIS_PASSWORD=055EDy65AAhLgBxMp1u1
然后就可以将服务发布上线了。
core api其他电脑不能访问接口_第 12 篇:加缓存为接口提速相关推荐
- vue获取商品数据接口_基于 request cache 请求缓存技术优化批量商品数据查询接口...
Hystrix command 执行时 8 大步骤第三步,就是检查 Request cache 是否有缓存. 首先,有一个概念,叫做 Request Context 请求上下文,一般来说,在一个 we ...
- sdn主要包含哪些接口_解读SDN的东西、南北向接口
北向接口(Northbound Interface)是为厂家或运营商进行接入和管理网络的接口,即向上提供的接口. 南向接口(Southbound Interface)是提供对其他厂家网元的管理功能,支 ...
- 雷电2接口_让轻薄本不再受制于接口少 - Belkin 雷电3扩展坞-核心版
嗨,大家好!我是沈少,最近几年随着笔记本电脑的性能越来越强,我注意到越来越多的朋友都开始把笔记本电脑作为主力机,甚至唯一一台电脑使用.然而于此同时,也苹果为代表的一大票轻薄本为了追求更高的颜值和便携性 ...
- java 多态 接口_从零开始的Java日常: 多态,抽象类,接口
欢迎关注微博:@小白程序员的日常 欢迎关注微博超话:#小白程序员的日常# 本人目前在自学java,会不间断更新java知识 同为小白或者有大佬可以一起探讨一下 一.多态 1.什么是多态 同一个对象,在 ...
- php 项目中引用对方接口_关于PHP中为什么要写接口的问题说明
接口(软件类接口)是指对协定进行定义的引用类型.其他类型实现接口,以保证它们支持某些操作.接口指定必须由类提供的成员或实现它的其他接口.与类相似,接口可以包含方法.属性.索引器和事件作为成员. 接口是 ...
- c调用python接口_通过Python自带C/C++接口实现python与c/c++相互调用
python的底层是c/c++,因此两种语言都有相互的接口,在以前已经写过一篇c++调用python接口让opencv中的cv::Mat类型在两种语言中相互传递,ubuntu下C++与Python混编 ...
- lightning接口_新iPhone或将使用TypeC接口~
关于在iPhone上使用Type-C接口的消息,想必大家已经不陌生了,在2019年的iPhone 11系列上,苹果终于加入了Type-C接口,只是这次Type-C接口是长在充电器上,而不是iPhone ...
- 对接高德接口心得,本篇不涉及具体接口
对接高德接口心得 本篇不涉及具体接口,接口均为高德定制开发,只是总结其中遇到的问题. 通过一段时间调用高德接口总结如下: 1.接口延迟比较严重 2.经常返回接口内部异常 3.接口经常超时 针对上述问题 ...
- win32 api setwindowlong 第2个参数_第 6 篇:分页接口
作者:HelloGitHub-追梦人物 如果没有设置分页,django-rest-framework 会将所有资源类表序列化后返回,如果资源很多,就会对网站性能造成影响.为此,我们来给博客文章列表 A ...
最新文章
- 数据结构(C语言版) 第 六 章 图 知识梳理 + 习题详解
- java 安装多版本_一台电脑安装多个版本的jdk
- java笔记:自己动手写javaEE
- Java实现字符串反转的四种方式代码示例
- 为什么子孙后代会讨厌使用java.util.Stack
- Apache RocketMQ:简单消息示例
- linux menuconfig usb,[Linux]make menuconfig里面的选项很重要
- Java知多少(50)Java throws子句
- 为什么eolinker发送老是等待_eolinker之初体验
- iPhone 竟没人要了?
- 详解Windows Shim的攻防利用
- Beagleboard-xM Rev B 开发板探索 0.1
- 【百度地图2.5D、3D在Vue项目中的使用】嵌入二维百度地图、三维百度地图、多种显示模式风格样式颜色的百度地图
- Python爬取wfxnews 小说网站,实现批量下载小说
- 开源数据可视化 datart-自定义Jquery图表插件教程
- 最全的硬盘修复专题帖1(转贴)
- 【excel】解决录制宏时报错:为便于记录,启动文件夹中的“个人宏工作簿”必须保持打开状态
- ‘cnpm‘ 不是内部或外部命令,也不是可运行的程序或批处理文件
- Unity Texture2D byte[] sprit转换
- 金蝶软件怎么过账_超详细!金蝶财务软件实操流程
热门文章
- 测试对等网络的连通性
- Noip 2016 Day1 题解
- 【李宏毅2020 ML/DL】P10 Classification_1 | 简单的例子告诉你使用 wx+b 以及 Sigmoid 作为激活函数的合理性
- java中的远程debug调试
- mysql解压版有配置文件么_3分钟安装MySQL5.7解压版
- HTML-DOM零碎
- android tabhost 生命周期,FragmentTabHost + FragmentLayout布局框架,Fragment生命周期
- 三维点云学习(5)2-Deep learning for Point Cloud-PointNet
- TPLINK-WR720N刷openwrt
- 面向对象JavaScript入门——来自Mozilla的官网教程