Celery的介绍与使用
一、Celery介绍
1、Celery是什么?
Celery翻译过来是芹菜【不要问我跟芹菜有什么关系,问就是你直接去问作者】
框架:
服务,python的框架,跟django无关
能用来做什么?
<1>、异步任务
<2>、定时任务
<3>、延时任务
理借Celery的运行原理
<1>、可以不依赖任何服务器,通过自身命令,启动服务
<2>、celery服务为为其他项目服务提供异步解决任务需求的
注:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求
人是一个独立运行的服务 | 医院也是一个独立运行的服务 正常情况下,人可以完成所有健康情况的动作,不需要医院的参与;但当人生病时,就会被医院接收,解决人生病问题 人生病的处理方案交给医院来解决,所有人不生病时,医院独立运行,人生病时,医院就来解决人生病的需求
2、Celery架构(Broker,backend都用redis)
<1>、任务中间件Broker(中间件),其他服务提交的异步任务,放在里面排队
需要借助于第三方redis、rabbitmq
<2>、任务执行单元worker,真正执行异步任务的进程
celery提供的
<3>、结果存储,backend,结果存储,函数的返回结果,存到backend中
需要借助于第三方:redis,mysql
3、使用场景
异步执行:解决耗时任务 延迟执行:解决延迟任务 定时执行:解决周期任务
二、Celery安装与快速使用
celery不支持win,通过eventlet支持在win上运行
安装:
其他操作系统:pip install celery 在windows上安装:pip install eventlet
快速使用:
-
第一步:
新建main.py
from celery import Celery # 提交的异步任务,放在里面 broker = 'redis://127.0.0.1:6379/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 -
第二步:
其他程序,提交任务
res = add.delay(5, 6) # 原来add的参数,直接放在delay中传入即可 print(res) # f150d8a5-c955-478d-9343-f3b60d0d5bdb -
第三步:
启动worker命令,win需要安装eventlet
-A后面跟的是celery对象所在的文件,-P是指定我们需要使用eventlet,-l后面的info是用于指定打印信息的级别(类似异常处理的提示)
win: 4.x之前版本 celery worker -A main -l info -P eventlet 4.x之后 celery -A main worker -l info -P eventlet mac: -
第四步:
worker会执行消息中间件中的任务,把结果存到我们指定的redis的第二个库中
只要我们不主动关闭,celery worker就会一直运行
-
第五步:
查看执行结果,拿到执行的结果
上图是我们通过图形化界面看到的结果,正常来说我们需要用代码来获得运行的结果
这里我们创建一个新的py文件来获取结果,并且需要用到之前获取到的id来查询结果
get_result.py
from main import app from celery.result import AsyncResult id = '21325a40-9d32-44b5-a701-9a31cc3c74b5' if __name__ == '__main__': ans = AsyncResult(id=id, app=app) if ans.successful(): result = ans.get() print(result) elif ans.failed(): print('任务失败') elif ans.status == 'PENDING': print('任务等待中被执行') elif ans.status == 'RETRY': print('任务异常后正在重试') elif ans.status == 'STARTED': print('任务已经开始被执行') ps:将来我们在任意位置需要查看结果的时候,导入上述代码使用即可
三、celery包结构
包结构目录
project ├── celery_task # celery包 │ ├── __init__.py # 包文件 │ ├── celery.py # celery连接和配置相关文件,且名字必须交celery.py │ └── tasks.py # 所有任务函数 ├── add_task.py # 添加任务 └── get_result.py # 获取结果
-
步骤一
创建包celery_task,并且报下必须有一个叫celery.py的py文件
celery.py
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('test', broker=broker, backend=backend, include=['celery_task.order_task', 'celery_task.user_task']) -
步骤二
接着我们我们在保内编写task文件,编写任务函数,我们可以根据用途分成不同的task文件
order_task
from .celery import app import time @app.task def add(a, b): print('-----', a + b) time.sleep(2) return a + b user_task
from .celery import app import time @app.task def send_sms(phone, code): print("给%s发送短信成功,验证码为:%s" % (phone, code)) time.sleep(2) return True -
步骤三
提交任务到中间件,等待worker执行
handup.py
from celery_task.user_task import send_sms from celery_task.order_task import add '异步调用' res = send_sms.delay(199999999, '88888') print(res) # res = add.delay(19, 3) # print(res) -
步骤四
包所在目录下,启动worker
celery -A celery_task worker -l info -P eventlet -
步骤五
worker执行完,结果会被存到backend中
-
步骤六
使用代码查看结果
依旧是使用跟上面一样的代码,导入的东西需要改一下
get_result.py
from celery_task.celery import app from celery.result import AsyncResult id = '9a2070f7-34d0-421e-836e-f6c664967e3f' if __name__ == '__main__': ans = AsyncResult(id=id, app=app) if ans.successful(): result = ans.get() print(result) elif ans.failed(): print('任务失败') elif ans.status == 'PENDING': print('任务等待中被执行') elif ans.status == 'RETRY': print('任务异常后正在重试') elif ans.status == 'STARTED': print('任务已经开始被执行')
四、celery 执行异步任务,延迟任务,定时任务
异步任务
任务函数的后面点delay就是异步执行任务
任务.delay(参数)
延迟任务
任务.apply_async(args=[参数],eta=时间对象)
这里的eta是一个时间对象,需要用datetime模块创建
### 延迟任务 # 需要传入时间对象 from datetime import datetime, timedelta # 拿到utc时间 datetime.utcnow() # print(type(datetime.now())) # print(datetime.now()-timedelta(days=3)) # print(datetime.utcnow()) # print(type(timedelta(days=10))) # 1 分钟之后的时间 eta = datetime.utcnow() + timedelta(seconds=20) # 立即异步执行 res = send_sms.delay('1923333', '8888') print(res) # 1分钟后执行这个任务 res = send_sms.apply_async(args=['18922345353', '8888'], eta=eta) # 延迟一分钟执行,通过时间对象来控制 '这样设置之后,在我们执行任务的时候,当时间到达时间对象对应的时间后就会提交任务,没到达对应的时间之前就相当于处于阻塞态' print(res) '打印出的res仍然是任务的id' ### 定时任务 每隔多长时间, 每天执行某个任务
datetime.now()与datetime.utcnow()
# datetime.utcnow() from datetime import datetime, timedelta eta = datetime.utcnow() + timedelta(seconds=20) print(eta) '2023-03-09 07:10:23.437327' # datetime.now() from datetime import datetime, timedelta eta = datetime.now() + timedelta(seconds=20) print(eta) '2023-03-09 15:24:51.385495'
-
datetime.utcnow()获取的是当前的格林威治时间
-
datetime.now()获取的是当前地区的时间,需要我们在配置文件中更改配置才能获取到本地时间
配置文件中的配置项如下: LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False -
timedelta(seconds=20)是给他增加一些时间,根据关键参数的值和类型,可以添加不同的时间量,具体名称可以去源码查看
class timedelta(SupportsAbs[timedelta]): min: ClassVar[timedelta] max: ClassVar[timedelta] resolution: ClassVar[timedelta] if sys.version_info >= (3, 6): def __init__( self, days: float = ..., seconds: float = ..., microseconds: float = ..., milliseconds: float = ..., minutes: float = ..., hours: float = ..., weeks: float = ..., *, fold: int = ..., ) -> None: ... else: def __init__( self, days: float = ..., seconds: float = ..., microseconds: float = ..., milliseconds: float = ..., minutes: float = ..., hours: float = ..., weeks: float = ..., ) -> None: ...
定时任务
定时任务需要两个进程才能实现,其中的beat进程是定时提交任务的,worder是执行任务的
-需要启动beat和启动worker -beat 定时提交任务的进程---》配置在app.conf.beat_schedule的任务 -worker 执行任务的
-
步骤一
使用定时任务需要在celery的py文件中写入下列配置,这样beat就会根据配置去提交任务
'定时任务' from celery.schedules import crontab from datetime import datetime, timedelta 'celery的配置文件' '时区设置,这里配置成上海' app.conf.timezone = 'Asia/Shanghai' '是否使用utc时间(格林威治时间),这里选择不使用' app.conf.enable_utc = False '任务的定时配置' app.conf.beat_schedule = { '这里的名字没有什么意义,这样的任务配置可以配置多个' 'send_sms': { 'task': 'celery_task.user_task.send_sms', # 'schedule': timedelta(seconds=3), # 存放时间对象的话,就表示每隔这样一段时间,执行一次任务 # 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点执行任务 'schedule': crontab(hour=9, minute=43), # 每天的9点43分执行任务 'args': ('19999999', '6666') # 这里的args是放置参数的,有参数就写,没参数就可以不写args }, } -
步骤二
启动beat
celery -A celery_task beat -l info -
步骤三
启动worker
celery -A celery_task worker -l info -P eventlet -
注意事项
- 1 启动命令的执行位置,如果是包结构,一定在包这一层
- 2 include=['celery_task.order_task'],路径从包名下开始导入,因为我们在包这层执行的命令
-
结果展示
启动beat
启动worker
五、django中使用celery
1、定时任务推荐使用的框架(了解)
APSchedule(更简单):https://blog.csdn.net/qq_41341757/article/details/118759836
2、秒杀功能
这里我们使用celery编写一个秒杀功能(还可以把发送短信,封装成用celery异步发送)
-
秒杀功能逻辑分析
这里我们只是简单模仿秒杀的功能,相关的数据库和数据,以及一些相关的代码,就简单编写了
# 秒杀逻辑分析 1 前端秒杀按钮,用户点击---》发送ajax请求到后端 2 视图函数---》提交秒杀任务---》借助于celery,提交到中间件中了 3 当次秒杀的请求,就回去了,携带者任务id号在前端(在任务完成前,可以用模态框播放正在秒杀的动画,查到结果后秒杀动画关闭) 4 前端开启定时任务,每隔3s钟,带着任务,向后端发送请求,查看是否秒杀成功 5 后端的情况 1 任务还在等待被执行----》返回给前端,前端继续每隔3s发送一次请求 2 任务执行完了,秒杀成功了---》返回给前端,恭喜您秒杀成功--》关闭前端定时器 3 任务执行完了,秒杀失败了---》返回给前端,秒杀失败--》关闭前端定时器 -
视图和路由
urls.py
from rest_framework.routers import SimpleRouter from . import views router = SimpleRouter() # 访问 http://127.0.0.1:8000/api/v1/user/userinfo/send_msg/ ---->get 请求就可以查询所有轮播图 router.register('userinfo', views.UserView, 'userinfo') # http://127.0.0.1:8000/api/v1/user/register/ --->post 请求 router.register('register', views.RegisterUserView, 'register') # http://127.0.0.1:8000/api/v1/user/sckill/ --->post 请求 router.register('sckill', views.SckillView, 'sckill') urlpatterns = [ ] urlpatterns += router.urls views.py
# 秒杀的cbv from rest_framework.viewsets import ViewSet from celery_task.order_task import sckill_task from celery.result import AsyncResult from celery_task.celery import app class SckillView(ViewSet): '这里我们规定使用get接受,并且用户的秒杀任务的id放在路由中携带' @action(methods=['GET'], detail=False) def sckill(self, request): good_id = request.query_params.get('id') # 使用异步,提交秒杀任务 res = sckill_task.delay(good_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') ans = AsyncResult(id=task_id, app=app) if ans.successful(): result = ans.get() print(result) if result: return APIResponse(msg='秒杀成功') else: return APIResponse(code=101, msg='秒杀失败') elif ans.status == 'PENDING': print('任务等待中被执行') return APIResponse(code=666, msg='秒杀进行中') else: '这里写个else,接收其他情况的结果,防止报错' return APIResponse() -
任务 order_task.py
任务创建好后,需要去celery文件的app中注册
app = Celery('test', broker=broker, backend=backend, include=['celery_task.order_task', 'celery_task.user_task', 'celery_task.sckill_task']) order_task.py
import random from .celery import app import time '秒杀任务' @app.task def sckill_task(good_id): print('秒杀开始') time.sleep((random.choice([6, 8, 10]))) '这里我们模仿秒杀的过程,经过一段时间后才能完成秒杀' print('秒杀结束') '这里我们用random来模仿秒杀成功的概率' return random.choice([True, False]) -
前端Sckill.vue
<template> <div class="sckill"> <button @click="handleSckill">秒杀</button> </div> </template> <script> export default { name: 'Sckill', data() { return { task_id: '', t: null, } }, methods: { handleSckill() { this.$axios(this.$settings.BASE_URL + '/user/sckill/sckill/?id=1').then(res => { this.task_id = res.data.task_id // 获取到 了秒杀任务的id,就创建定时器,不断查询秒杀任务的结果 this.t = setInterval(() => { this.$axios.get(this.$settings.BASE_URL + '/user/sckill/get_result/?task_id=' + this.task_id).then(res => { if (res.data.code == 666) { // 这里是表示任务还在等待执行,定时任务需要继续执行 console.log(res.data.msg) } else { // 这里是秒杀结束了,但是结果不一定的,我们需要结束定时器 clearInterval(this.t) this.t = null this.$message(res.data.msg) } } ) }, 2000) }).catch(res => { }) } }, } </script>
3、django 中使用celery总结
步骤一
把咱们写的包,复制到项目目录下
project ├── celery_task # celery包 │ ├── __init__.py # 包文件 │ ├── celery.py # celery连接和配置相关文件,且名字必须交celery.py │ └── tasks.py # 所有任务函数 ├── add_task.py # 添加任务 └── get_result.py # 获取结果
步骤二
在celery.py文件中添加一行代码(manage.py中的导入环境变量的代码)
原因:celery中使用djagno,有时候,任务函数或类中会使用django的orm,缓存,表模型,因此一定要添加这行代码
这里的导入环境变量的代码,运行后就相当于加载了配置文件
这样我们在task文件中编写代码如果用到了一些项目中的表,就可以顺利的查找到(因为现在这些配置已经在项目中注册了)
如果我们不注册,就会提示找不到这个被使用的表,就算我们把导入的语句放到函数中去,在代码运行函数的时候仍然会报错
import os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
步骤三
在需要使用celery提交任务的配置,导入包使用即可
-视图函数中使用,导入任务 -任务.delay() # 提交异步任务 定时任务 延时任务
步骤四
启动worker执行任务,如果有定时任务,启动beat
步骤五
等待任务被worker执行
步骤六
在视图函数中查询任务结果的返回给前端
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现