Python实战项目-9 Redis/celery-基础使用
Redis介绍与安装
Redis->缓存数据库【大部分时间用来做缓存,不仅仅可以做缓存】
也是称为非关系型数据库,区别与Mysql关系型数据库
-noSql:泛指非关系型数据库,not only Sql
redis数据存储在内存中
在取值,放值速度非常快
快的原因:
1.纯内存操作
2.网络模型使用的是I/O多路复用(epoll)
用了这模型,处理的请求数更多
3.6.x之前,单进程/单线程架构(没有进程线程间切换,更少的消耗资源)
redis是用c语言写的服务(监听端口)用来存储数据,数据是存储在内存中 取值/放值速度非常快
qps == 10w
安装
-mac 源码编译安装
-linux 源码编译安装
-win 微软自己基于源码改动,编译为安装包
- 最新5.x版本 https://github.com/tporadowski/redis/releases/
- 最新3.x版本 https://github.com/microsoftarchive/redis/releases
一路下一步,安装完释放出两个命令,会把redis自动加入到服务中
版本
最新:7.x
公司里 5.x 比较多
- 启动服务端,启动客户端
redis-cli redis-server
- 客户端和服务端在同一台机器上
- 本地的客户端可以连接远程的服务器
客户端连接redis
方式一:
redis-cli 默认连接本地的6379端口
方式二:
redis-cli -h 地址 -p 端口 自己可以自定义地址端口连接
方式三:
使用图形化客户端操作
Redis Desktop Manager : 开源软件,原先免费,后续收费了.. 推荐用(mac,win,linux 都有)
Qt5 qt是个平台,专门用来做图形化界面的
-可以使用c++写
-可以使用python写 pyqt5 使用python写图形化界面 (少量公司再用)
-resp-2022.1.0.0.exe 一路下一步,安装完启动起来
Redis Client 小众
图形化界面,连接redis 输入地址和端口,点击连接即可
redis默认有16个库,默认连进去就是第0
扩展:
mysql是cs架构软件
pymysql 是 mysql的客户端
Navicate 是mysql的客户端
客户端连接-cmd中使用redis-cli
图形化界面,
python的redis模块操作redis
python连接redis
普通连接
连接池连接
django使用mysql连接池 目的:-->防止连接数过高,服务挂掉
进程线程协程
进程:资源分配的最小单位,一个程序运行起来可能是一个进程,或多个进程
线程:CPU调度的最小单位,程序要执行,是线程在执行,Cpu调度的最小单位,一个进程里面可以有许多线程,->操作系统控制切换
协程:单线程下的并发,程序层面控制,遇到io操作,切换到别的任务执行
Redis之本地连接和连接池
python 相当于redis的客户端进行操作redis
我们操作redis只需要安装响应模块即可
pip install redis
django中操作mysql是没有连接池的,一个请求就是一个mysql连接
但是这样可能会处问题,并发数过高,导致mysql连接数过高,影响mysql性能
参考:
普通连接
在python中安装 redis 模块
- 导入模块Redis类
from redis import Redis
- 实例化得到对象
conn = Redis(host='127.0.0.1',port=6379)
- 使用conn操作redis
res = conn.get('name') # 返回数据是bytes格式
- 设置值
conn.set('age',19)
conn.close()
连接池连接
pool.py
import redis
POOL = redis.ConnerctionPool(max_connections=10,host='127.0.0.1',port=6379)
# 创建一个大小为10的redis连接池
# 测试
import redis
from threading imoport Thread
from pool import POOL
def task():
# 做成模块后,导入,无论导入多少次,导入的都那一个POOL对象
conn = redis.Redis(connection_pool=POOL)
# 报错的原因是拿连接,连接池池里连接不够了,没有等待,线程报错 可以设置等待参数
print(conn.get('name'))
for i in range(1000):
t = Thread(target=task) # 每次都是一个新的连接,会导致 的连接数过多
t.start
redis之列表
列表方法示例
lpush(name, values)
rpush(name,values) 与lpush相反
lpushx(name,value) 如果键存在,则从左侧插入,反之右插入,键不存在不做操作
rpushx(name, value) 表示从右向左操作
llen(name) 获取键长度 获取不到返回0
linsert(name, where, refvalue, value))
例:
** **linsert('girls','before','迪丽热巴','古力娜扎') 在迪丽热巴前面插入古力娜扎
linsert('girls','after','小红','小绿') 在小红后面插入小绿
如果插入的数据不存在,插不进去返回-1
lset(name, index, value) 按照位置修改值
例:
conn.lset('girls',1,'xiaoming') 在girls键对应的列表中 索引为1的值修改为xiaoming
lrem(name, value, num) 删除值
conn.lrem('girls',1,'xxx') # 从左侧开始,删除1个xxx
conn.lrem('girls',-1,'xxx') # 从右侧开始,删除1个xxx
conn.lrem('girls',0,'xxx') # 从左开始,删除全部xxx
lpop(name)弹出列表左侧第一个值
rpop(name)弹出列表右侧第一个值
lindex(name, index) 获取索引对应位置的值,如果没有返回None
lrange(name, start, end) 切片返回列表
ltrim(name, start, end) 修剪不在这个区间的所有值
rpoplpush(src,dst)
conn.rpoplpush("girls", 'boys')
- 将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。
- 将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。
blpop(keys, timeout) 可以做消息队列使用 阻塞式弹出,如果没有,就阻塞
res=conn.blpop('boys')
print(res)
brpoplpush(src,dst,timeout=0)
- 命令从列表中取出最后一个元素,并插入到另外一个列表的头部; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
redis之字符串
redis 是key-value形式存储
redis 数据存放在内存中,如果断点,数据丢失--->需要有持久化的方案
# 5 种数据类型,value类型
-字符串:用的最多,做缓存;做计数器
-列表: 简单的消息队列
-字典(hash):缓存
-集合:去重
-有序集合:排行榜
字符串方法示例
设置值
1 set(name, value, ex=None, px=None, nx=False, xx=False)
参数:
ex,过期时间(秒)
px,过期时间(毫秒)
nx,如果设置为True,则只有name不存在时,当前set操作才执行, 值存在,就修改不了,执行没效果
xx,如果设置为True,则只有name存在时,当前set操作才执行,值存在才能修改,值不存在,不会设置新值
2 setnx(name, value)
等同于:conn.set('name','xiaoming',nx=True)
conn.setnx('name', '刘亦菲')
3 setex(name, value, time)
等同于:conn.set('name','xiaoming',ex=3)
conn.setex('wife', 3, '刘亦菲')
4 psetex(name, time_ms, value)
参数:
time_ms,过期时间(数字毫秒 或 timedelta对象
5 mset(*args, **kwargs)
批量设置
如:
mset(k1='v1', k2='v2')
或
mget({'k1': 'v1', 'k2': 'v2'})
6 get(name)
获取值
7 mget(keys, *args)
批量获取值
8 getset(name, value)
设置新值并获取原来的值
9 getrange(key, start, end)
获取子序列(根据字节获取,非字符)
参数:
name,Redis 的 name
start,起始位置(字节)
end,结束位置(字节)
如: "刘亦菲" ,0-3表示 "刘"
''''
10 setrange(name, offset, value)
修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加)
# 参数:
offset,字符串的索引,字节(一个汉字三个字节)
value,要设置的值
11 setbit(name, offset, value)
# 对name对应值的二进制表示的位进行操作
参数:
name,redis的name
offset,位的索引(将值变换成二进制后再进行索引)
value,值只能是 1 或 0
注:如果在Redis中有一个对应: n1 = "foo",
那么字符串foo的二进制表示为:01100110 01101111 01101111
所以,如果执行 setbit('n1', 7, 1),则就会将第7位设置为1,
那么最终二进制则变成 01100111 01101111 01101111,即:"goo"
12 getbit(name, offset)
获取name对应的值的二进制表示中的某位的值 (0或1)
13 bitcount(key, start=None, end=None)
获取name对应的值的二进制表示中 1 的个数
参数:
key,Redis的name
start,位起始位置
end,位结束位置
14 bitop(operation, dest, *keys)
获取多个值,并将值做位运算,将最后的结果保存至新的name对应的值
参数:
operation,AND(并) 、 OR(或) 、 NOT(非) 、 XOR(异或)
dest, 新的Redis的name
*keys,要查找的Redis的name
# 如:
bitop("AND", 'new_name', 'n1', 'n2', 'n3')
获取Redis中n1,n2,n3对应的值,然后讲所有的值做位运算(求并集),然后将结果保存 new_name 对应的值中
15 strlen(name)
返回name对应值的字节长度(一个汉字3个字节)
16 incr(self, name, amount=1)
自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。
参数:
name,Redis的name
amount,自增数(必须是整数)
# 注:同incrby
17 incrbyfloat(self, name, amount=1.0)
自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。
参数:
name,Redis的name
amount,自增数(浮点型)
18 decr(self, name, amount=1)
自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减。
参数:
name,Redis的name
amount,自减数(整数)
19 append(key, value)
# 在redis name对应的值后面追加内容
参数:
key, redis的name
value, 要追加的字符串
redis之hash
import redis
conn = redis.Redis()
conn.hset('userinfo','name','xiaoming')
conn.hset('userinfo',mapping={'name':'xiaoming'})
1 hset(name, key, value)
res = conn.hget('userinfo','name')
print(res) # b'xiaoming'
2 hmset(name, mapping)
res = conn.hmget('userinfo',['name'])
print(res) # b'xiaoming'
3 hget(name,key) # 获取值
5 hgetall(name)
res = conn.hgetall('userinfo')
print(res) # 全部拿出来,但是要慎用 因为数据量可能过大
6 hlen(name)
res = conn.hlen('userinfo') # 获取键对应字典长度
7 hkeys(name)
res = conn.hkeys('userinfo')
print(res) # 拿出所有的key 二进制类型
9 hexists(name, key)
res = conn.hexists('userinfo','name')
print(res) # true 如果name不存在,则False
10 hdel(name,*keys)
res = conn.hdel('userinfo','age')
print(res) # 删除 如果没有就报错
11 hincrby(name, key, amount=1)
conn.hincrby('userinfo','age',2) # 自增
12hscan(name, cursor=0, match=None, count=None)
# 一次性全部取出,效率低,可能占内存很多
14 hscan_iter(name, match=None, count=None) # 分批获取
# generator 只要函数中有yield关键字,这个函数执行的结果就是生成器
#生成器就是迭代器,可以被for循环
redis管道
redis支持事务.但是并不是很严谨,因为可能满足不了持久性的要求(几率小,但是不代表没有)
事务四大特性:
原子性
一致性
隔离性
持久性
使用:
import redis
conn = redis.Redis()
p=conn.pipeline(transaction=True)
p.multi()
p.decr('zhangsan_je', 100)
# raise Exception('崩了')
p.incr('lisi_je', 100)
p.execute()
conn.close()
redis其他操作
''' 通用操作,不指定类型,所有类型都支持
1 delete(*names)
2 exists(name)
3 keys(pattern='*')
4 expire(name ,time)
5 rename(src, dst)
6 move(name, db))
7 randomkey()
8 type(name)
'''
import redis
conn = redis.Redis()
1 delete(*names)
conn.delete('name', 'userinfo2')
conn.delete(['name', 'userinfo2']) # 不能用它
conn.delete(*['name', 'userinfo2']) # 可以用它
2 exists(name)
res=conn.exists('userinfo')
print(res)
3 keys(pattern='*')
res=conn.keys('w?e') # ?表示一个字符, * 表示多个字符
print(res)
4 expire(name ,time)
conn.expire('userinfo',3)
5 rename(src, dst)
conn.rename('hobby','hobby111')
6 move(name, db))
conn.move('hobby111',8)
7 randomkey()
res=conn.randomkey()
print(res)
8 type(name)
print(conn.type('girls'))
print(conn.type('age'))
conn.close()
django中使用redis
1.自定义包方案(通用方案,不针对与django其他也可以用)
1.写一个pool.py
2.在以后使用的地方直接使用即可
import redis
POOL = redis.Connectionpool(max_connections=100)
conn = redis.Redis(connection_pool=Pool)
conn.incr('count')
res = conn.get('count')
return JsonResponse({'count':"今日访问次数%s"%res})
2.django中使用redis作为缓存【推荐使用】
django的缓存使用redis 【推荐使用】
-settings.py 中配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100} 最大连接数100
# "PASSWORD": "123",
}
}
}
-在使用redis的地方:cache.set('count', res+1)
-如果是对象的话是通过pickle序列化后,存入的
3.第三方:django-redis模块
from django_redis import get_redis_connection
def test_redis(request):
conn=get_redis_connection()
print(conn.get('count'))
return JsonResponse({'count': '今天这个接口被访问的次数为:%s'}, json_dumps_params={'ensure_ascii': False})
celery介绍与安装
celery 是什么
翻译过来是 "芹菜"
框架:服务,python的框架跟django无关
能用来做:
1.异步任务
2.定时任务
3.延时任务
# 理解celery的运行原理
"""
1)可以不依赖任何服务器,通过自身命令,启动服务
2)celery服务为为其他项目服务提供异步解决任务需求的
注:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求
人是一个独立运行的服务 | 医院也是一个独立运行的服务
正常情况下,人可以完成所有健康情况的动作,不需要医院的参与;但当人生病时,就会被医院接收,解决人生病问题
人生病的处理方案交给医院来解决,所有人不生病时,医院独立运行,人生病时,医院就来解决人生病的需求
"""
celery架构
1.任务中间件 Broker(中间件),其他服务提交的异步任务,放在里面排队
redis rabbitmq (需要借助第三方)
2.任务执行单元 Worker 真正执行异步任务的进程
celery 提供的
3.结果存储 Backend 结果存储m函数的返回结果 存到backend中
需要借助于第三方
redis,mysql
# 使用场景
异步执行:解决耗时任务
延迟执行:解决延迟任务
定时执行:解决周期(周期)任务
celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成
celery快速使用
安装celery与eventlet模块
pip install celery
pip install eventlet
1.新建main.py
from celery import Celery
- 提交的异步任务,放在里面
broker = 'redis://127.0.0.1:6379/1'
redis协议,存储在1号库中
- 执行完的结果,放在这里
backend = 'redis://127.0.0.1:6379/2'
app = Celery('test', broker=broker, backend=backend)
@app.task
def add(a, b):
import time
time.sleep(3)
print('------',a + b)
return a + b
2.其他程序提交任务
res = add.delay(5,6) # 原来add的参数,直接放在delay中传入即可
print(res) # f150d8a5-c955-478d-9343-f3b60d0d5bdb
3.启动worker
# 启动worker命令,windows需要安装eventlet
win:
-4.x之前版本
celery worker -A main -l info -P eventlet
-4.x之后
celery -A main worker -l info -P eventlet
mac:
celery -A main worker -l info
4.worker会执行消息中间件的任务,把结果存起来
5.可以拿到执行的结果
from main import app
from celery.result import AsyncResult
id = '51611be7-4914-4bd2-992d-749008e9c1a6' # 第三步返回的任务id
if __name__ == '__main__':
a = AsyncResult(id=id, app=app)
if a.successful(): # 执行完了
result = a.get() #
print(result)
elif a.failed():
print('任务失败')
elif a.status == 'PENDING':
print('任务等待中被执行')
elif a.status == 'RETRY':
print('任务异常后正在重试')
elif a.status == 'STARTED':
print('任务已经开始被执行')
celery包结构
project
├── celery_mission # celery包
│ ├── __init__.py # 包文件
│ ├── celery.py # celery连接和配置相关文件,且名字必须叫celery.py
│ └── tasks.py # 所有任务函数
├── add_mission.py # 添加任务
└── get_result.py # 获取结果
- 1.新建包 celery_mission >>(别的名字也可以)
在包内编写代码:
from celery import Celery
# 提交的异步任务,放在里面
broker = 'redis://127.0.0.1:6379/1'
# 执行完的结果,放在这里
backend = 'redis://127.0.0.1:6379/2'
# 不要忘了include
'上面两个配置是设置存储数据的redis库位置'
'下面的app中多了一格include属性,他相当于是注册需要执行的函数'
app = Celery(broker=broker,backend=backend
,include=['celery_mission.order_mission','celery_mission.user_mission'])
- 2.接着我们我们在包内编写task文件,编写任务函数,我们可以根据用途分成不同的task文件
order_mission
from .celery import app
import time
@app.task
def add(a,b):
print('输出结果为>>>>>:',a+b)
time.sleep(2)
return a + b
user_mission
from .celery import app
import time
@app.task
def send_sms(phone,code):
print(f'给{phone}发送短信成功,验证码为{code}')
time.sleep(2)
return True
- 3.提交任务到中间件,等待worker执行
# add_mission.py
from celery_mission.user_mission import send_sms
from celery_mission.order_mission import add
# res = send_sms.delay('1929292939','8888')
# print(res)
'异步调用'
res = add.delay(99,99) #原来add的参数,直接放在delay中传入即可
print(res)
会获取到 a0d3c93b-8392-4235-adb7-2054087f485b 类似与任务编号一样的东西
- **4.在包所在目录下启动worker **
启动worker(任务执行单元),执行任务
在执行worker命令之前,windows系统必须要安装eventlet模块
worker会执行消息中间件中的任务,把结果存到我们指定的redis的第二个库中
只要我们不主动关闭,celery worker就会一直运行
win:
4.x之前版本
celery worker -A main -l info -P eventlet
4.x之后
celery -A main worker -l info -P eventlet
mac:
celery -A main worker -l info
main为包名,如实例 为celery_mission
ps:-A后面跟的是celery对象所在的文件,-P是指定我们需要使用eventlet
- 5.worker执行完成,执行结果会被存到backend中
- 6.使用代码查看结果依旧是使用跟上面一样的代码,导入的东西需要改一下
上图是我们通过图形化界面看到的结果,正常来说我们需要用代码来获得运行的结果
这里我们创建一个新的py文件来获取结果,并且需要用到之前获取到的id来查询结果
# get_result.py
from celery_mission.celery import app
from celery.result import AsyncResult
id = "1cd88a3f-c325-4bc8-bb38-4cac5fafbe1c"
if __name__ == '__main__':
a = AsyncResult(id=id, app=app)
if a.successful(): # 执行完了
result = a.get() #
print('执行成功',result)
elif a.failed():
print('任务失败')
elif a.status == 'PENDING':
print('任务等待中被执行')
elif a.status == 'RETRY':
print('任务异常后正在重试')
elif a.status == 'STARTED':
print('任务已经开始被执行')
celery执行异步任务,延时任务,定时任务
异步任务:
任务.delay(任务所需参数)
延迟任务:
任务.apply_async(args=[参数],eta=时间对象) >>如果没有修改时区,需要使用utc事件
定时任务:
需要启动beat 和 worker
-beat > 定时提交任务的进程 >> 配置在app.conf.beat_schedule的任务
celery -A celery_mission beat -l info
-worker> 执行任务
celery -A celery_mission worker -l info -P eventlet
定时任务使用步骤:
1.在celery.py文件中写入
app.conf.timezone = 'Asia/Shanghai'
选择是否使用UTC时区
app.conf.enable_utc = False
定时任务app的配置文件中配置:
app.conf.beat_schedule = {
'send_sms_task': {
'task': 'celery_task.user_task.send_sms',
'schedule': timedelta(seconds=5), # 时间对象
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'args': ('11122233311', '6666'),
},
'add_task': {
'task': 'celery_task.home_task.add',
'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'args': (10, 20),
}
}
2.启动beat
celery -A celery_mission beat -l info
3.启动worker
celery -A celery_mission worker -l info -P eventlet
需要注意:
- 启动命令的执行位置,如果是包结构,一定要在包这一层
- include=['celery.order_mission'],路径从包名下开始导入,因为我们在包这层执行的命令
补充
如果我们在公司,由于celery框架还是比较大,我们如果只需要做定时任务的话
我们可以通过APSchedule
帮助我们完成任务.
使用步骤:
-1 把咱们写的包,复制到项目目录下
-luffy_api
-celery_task #celery的包路径
-luffy_api #源代码路径
-2 在使用提交异步任务的位置,导入使用即可
-视图函数中使用,导入任务
-任务.delay() # 提交任务
-3 启动worker,如果有定时任务,启动beat
-4 等待任务被worker执行
-5 在视图函数中,查询任务执行的结果
django中使用celery
如果在公司中,只是做定时任务,有一个更简单的框架
APSchedule -> 定时任务框架
使用步骤:
1.把我们编写的包,复制到项目路径下即可
-luffy_api 项目路径
-celery_task 包路径
-luffy_api 源代码路径
2.在使用提交异步任务的位置,导入使用即可
3.启动worker,如果有定时任务,启动beat
4.等待任务被worker执行
5.在视图函数中,查询任务执行的结果
秒杀功能
前端:
秒杀按钮 用户点击 -->发送ajax请求到后端
视图函数 提交秒杀任务 --> 借助于celery,提交到中间件
档次秒杀的请求,就回去了,携带者任务id号在前端
前端开启定时任务,每隔3s 带着任务id向后端发送请求,查看是否秒杀成功
后端:
任务还在等待被执行,返回给前端,前端继续每隔3s发送一次请求
任务执行完了,秒杀成功了--> 返回给前端,恭喜秒杀成功 -->关闭前端定时器
任务执行完了,秒杀失败了--> 返回给前端,秒杀失败 -->关闭前端定时器
视图层
我们后端使用cbv
from celery_mission.order_mission import sckill_mission
from celery.result import AsyncResult
from celery_mission.celery import app
class SckillView(ViewSet):
# 由于我们是秒杀功能并不需要与数据库打交道,所以我们使用APIView+ViewSetMixin自动生成路由即可
@action(methods=['get'],detail=False)
def sckill(self,request):
# 我们需要获前端用户的id,点击秒杀按钮后会携带用户Id
id = request.query_params.get('id')
# 使用异步提交一个秒杀任务
res = sckill_mission.delay(id)
#res就是任务的id号,但是它是对象,所以我们可以通过.的方式点出来
return APIResponse(task_id=res.id)
@action(methods=['get'],detail=False)
def get_result(self,request):
task_id = request.query_params.get('task_id')
res = AsyncResult(app=app,id=task_id)
if res.successful():
print(res.get())
result = res.get()
if result:
return APIResponse(msg='秒杀成功')
else:
return APIResponse(msg='秒杀失败',code=101)
elif res.status == 'PENDING':
print('任务等待被执行中')
return APIResponse(msg='正在秒杀中',code=888)
order_mission.py
@app.task
def sckill_mission(goods_id):
# 生成订单,减库存,其实都需要在一个事务中
print(f'商品{goods_id}>>>秒杀开始')
# 这个过程,可能是1,2,3s中更多任意一个
time.sleep(random.choice([5,6,7]))
print(f'商品{goods_id}秒杀结束')
return random.choice([True,False])
celery.py
from celery import Celery
broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
app = Celery(broker=broker,backend=backend
,include=['celery_mission.order_mission','celery_mission.user_mission'])
前端 Sckcill界面
<template>
<div>
<el-button @click="handleSckill">点我秒杀</el-button>
</div>
</template>
<script>
export default {
name: "Skill",
data(){
return{
id:22,
task_id:'',
t:null
}
},
methods:{
handleSckill(){
this.$axios.get(this.$settings.BASE_URL+'userinfo/active/sckill/?id='+this.id).then(
res=>{
this.task_id = res.data.task_id
this.t = setInterval(()=>{
this.$axios.get(this.$settings.BASE_URL+'userinfo/active/get_result/?task_id='+this.task_id).then(
res => {
if (res.data.code == 888 ){
console.log(res.data.msg)
}else {
// 秒杀结束,无论成功失败,这个定时任务都结束
clearInterval(this.t)
this.t = null
this.$message(res.data.msg)
}
})
},2000)
}).catch(res=>{
console.log(res)
})
}
}
}
</script>
<style scoped>
</style>
django中使用celery
-1 把咱们写的包,复制到项目目录下
-luffy_api
-celery_mission #celery的包路径
-celery.py # 一定不要忘了一句话
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
-luffy_api #源代码路径
-2 在使用提交异步任务的位置,导入使用即可
-视图函数中使用,导入任务
-任务.delay() # 提交任务
-3 启动worker,如果有定时任务,启动beat
-4 等待任务被worker执行
-5 在视图函数中,查询任务执行的结果
重点:celery中使用djagno,有时候,任务中会使用django的orm,缓存,表模型等等..
一定要加:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
轮播图接口加缓存
我们网站首页被访问的频率是比较高的,如果几乎同时有1w个人在访问首页,首页的轮播图接口就会执行1w次
并且这1w次查询轮播图信息基本是固定的
我们就可以想一种方式,让这1w个访问,效率更高一些,不查数据库了,直接走缓存
Redis正好比较符合我们的需求,并且效率很高
至此前端的逻辑为:
- 轮播图接口请求来了,先去缓存中找,如果找不到,直接返回
- 如果没有,查数据库,然后把轮播图数据,放到redis中,缓存起来
修改我们之前的轮播图接口
from rest_framework.mixins import ListModelMixin
from .response import APIResponse
from django.core.cache import cache
class CommonListModelMixin(ListModelMixin):
def list(self, request, *args, **kwargs):
banner_list = cache.get('banner_list')
if banner_list:
print('走了缓存')
return APIResponse(result=banner_list)
else:
print('走了数据库')
res = super().list(request, *args, **kwargs)
cache.set('banner_list',res.data)
return APIResponse(result=res.data)
这是封装的mixin.py
双写一致性
当我们将数据加入缓存中时,缓存中有数据,先去缓存拿数据,但是如果mysql中数据改变了
缓存中的数据并没有改变
这就会导致我们mysql和缓存数据库数据不一致
解决方案:
- 修改数据时,删除缓存
- 修改数据并更新缓存
- 定时更新缓存-->实时性较差
我们就可以通过使用celery的定时任务完成接口的实时更新
首页轮播图定时更新
- 首先我们需要在celery.py中配置定时任务
app.conf.beat_schedule = {
'update_banner': {
'task': 'celery_task.home_task.update_banner',
'schedule': timedelta(seconds=3), # 时间对象 三秒更新
},
}
- 启动worker 启动beat
接口添加缓存
写一个类,继承这个类,就查询所有就可以有缓存,不继承就没有缓存
-common_view.py
class CacheListsModelMixin(ListModelsMixin)
cache_key = None
def list(self,request,*args,**kwargs):
data = cache.get(self.cache_key)
if data:
return APIResponse(data=data)
res = super().list(request,*args,**kwargs)
cache.set(self.cache_key,res.data)
return APIReponse(data=res.data)
我们也可以通过编写装饰器来给接口增加缓存的设置