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 微软自己基于源码改动,编译为安装包

一路下一步,安装完释放出两个命令,会把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)
image.png
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之字符串

rediskey-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就会在需要时异步完成项目的需求

人是一个独立运行的服务 | 医院也是一个独立运行的服务
	正常情况下,人可以完成所有健康情况的动作,不需要医院的参与;但当人生病时,就会被医院接收,解决人生病问题
	人生病的处理方案交给医院来解决,所有人不生病时,医院独立运行,人生病时,医院就来解决人生病的需求
"""

image.png

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

image.png

  • 5.worker执行完成,执行结果会被存到backend中

image.png

  • 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

需要注意:

  1. 启动命令的执行位置,如果是包结构,一定要在包这一层
  2. 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>

秒杀任务.gif
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

image.png

双写一致性

当我们将数据加入缓存中时,缓存中有数据,先去缓存拿数据,但是如果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)

我们也可以通过编写装饰器来给接口增加缓存的设置

APSchudler使用

posted @ 2023-03-08 19:14  dd随风  阅读(421)  评论(0编辑  收藏  举报