celery介绍架构和安装
介绍
1.celery :分布式的异步任务框架,主要用来做:
1.异步任务
2.延迟任务
3.定时任务--->如果只想做定时任务,可以不使用celery,有别的选择(apscheduler)
2.celery 框架,原理
1.可以不依赖任何服务器,通过自身命令,启动服务(内部支持socket)
2.celery服务为为其他项目服务提供异步解决任务需求的
ps:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求
"""
人是一个独立运行的服务 | 医院也是一个独立运行的服务
正常情况下,人可以完成所有健康情况的动作,不需要医院的参与;但当人生病时,就会被医院接收,解决人生病问题
人生病的处理方案交给医院来解决,所有人不生病时,医院独立运行,人生病时,医院就来解决人生病的需求
"""
3.celery架构
1.消息中间件(broker):消息队列:可以使用redis,rabbitmq,咱们使用redis
-Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等
2.任务执行单元(worker):真正的执行 提交的任务
-Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
3.任务执行结果存储(banckend):可以使用mysql,redis,咱们使用redis
-Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等
4.使用场景
1.异步执行:解决耗时任务
2.延迟执行:解决延迟任务
3.定时执行:解决周期(周期)任务
安装
1.pip install Celery
释放出可执行文件:celery,由于 python解释器的script文件夹在环境变量中,任意路径下执行celery都能找到
2.celery不支持win,所以想再win上运行,需要额外安装eventlet
windows系统需要eventlet支持:pip3 install eventlet
celery -A 模块名 worker -l info -P eventlet
ps:celery -A demo worker -l info -P eventlet
Linux与MacOS直接执行:
3.x、4.x版本:celery worker -A demo -l info
5.x版本:celery -A 模块名 worker -l info
两种celery任务结构
提倡用包管理,结构更清晰
1.如果 Celery对象:Celery(...) 是放在一个模块下的
1.终端切换到该模块所在文件夹位置:scripts/t_celery_task
2.执行启动worker的命令:celery -A 模块名 worker -l info -P eventlet
ps:windows系统需要eventlet支持,Linux与MacOS直接执行:celery -A 模块名 worker -l info
ps:模块名随意
2.如果 Celery对象:Celery(...) 是放在一个包下的
1.必须在这个包下建一个celery.py的文件,将Celery(...)产生对象的语句放在该文件中
2.执行启动worker的命令:celery -A 包名 worker -l info -P eventlet
ps:windows系统需要eventlet支持,Linux与MacOS直接执行:celery -A 包名 worker -l info
ps:包名随意
celery执行异步任务
基本使用
1.在虚拟环境中装celery和eventlet
pip install Celery
pip3 install eventlet
2.写个py文件,实例化得到app对象,注册任务(demo.py)
from celery import Celery
import time
broker = 'redis://127.0.0.1:6379/1' # 消息中间件 redis
backend = 'redis://127.0.0.1:6379/2' # 结果存储 redis
app = Celery(__name__, broker=broker, backend=backend)
@app.task # 变成celery的任务了
def add(a, b):
print('运算结果是',a + b)
time.sleep(1)
return a + b
3.启动worker(worker监听消息队列,等待别人提交任务,如果没有就卡再这)
1.切换终端路径-->demo.py上的文件夹
cd scripts/t_celery_task
2.执行:
celery -A demo worker -l info -P eventlet
4.别人提交任务,提交完成会返回一个id号,后期使用id号查询,至于这个任务有没有被执行,取决于worker有没有启动
from demo import add
res=add.delay(77,66) # f3c11f0d-874f-45b7-b890-1f769b01504e
5.提交任务的人,再查看结果
from demo import app
# celery的包下
from celery.result import AsyncResult
id = '9e893a37-0b1b-43ef-b5a4-4c24e1b53eab'
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('任务已经开始被执行')
代码
scripts
-t_celery_task
--add_task.py
--add_task1.py
--demo.py
--get_result.py
demo.py
from celery import Celery
import time
broker = 'redis://127.0.0.1:6379/1' # 消息中间件redis
backend ='redis://127.0.0.1:6379/2' # 消息中间件redis
app = Celery(__name__,broker=broker,backend=backend)
@app.task # 变成celery任务
def add(a,b):
print('运算结果',a+b)
time.sleep(1)
return a+b
@app.task
def send_sms(mobile,code):
time.sleep(1)
print('%s手机好码,发送短信成功,验证码是:%s'%(mobile,code))
return True
add_task.py
from demo import add
# 同步运行,同步调用
# res = add(4,5)
# print(res)
# 1.一步调用
# 一步调用--->执行下面,实际是把任务提交到消息队列中,返回一个id号,后期通过id查询任务执行结果,并没有执行,需要等待worker执行
res = add.delay(2,11)
print(res) # 9e893a37-0b1b-43ef-b5a4-4c24e1b53eab
# 2.启动worker
# 启动了celery服务,以后不用启动了,只需要提交任务,workr会自动执行
# celery -A demo worker -l info
# 3.取结果中查看是否执行完成
add_task1.py
from demo import send_sms
res1 = send_sms.delay(18081245209,8888)
print(res1)
get_result.py
from demo import app
# celery的包下
from celery.result import AsyncResult
id = '9e893a37-0b1b-43ef-b5a4-4c24e1b53eab'
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
1.新建包:celery_task
2.在包下新建 celery.py 必须叫它,里面实例化得到app对象
from celery import Celery
broker = 'redis://127.0.0.1:6379/1' # 消息中间件 redis
backend = 'redis://127.0.0.1:6379/2' # 结果存储 redis
app = Celery(__name__, broker=broker, backend=backend, include=['celery_task.course_task','celery_task.home_task','celery_task.user_task'])
3.新建任务py文件:user_task.py course_task.py home_task.py
ps:以后跟谁相关的任务,就写在谁里面
from .celery import app # 不要用 from celery import app
"""
from .celery import app 表示从当前路径下的celery.py 中导入app
from celery import app 表示从celery第三方包包下面导入app,没有
"""
import time
@app.task
def add_course():
print('课程增加成功')
time.sleep(5)
return '课程增加成'
4.启动worker,以包启动,来到包所在路径下
1.切换到包的上一层:eg:cd scripts
2.执行:celery -A 包名 worker -l info -P eventlet
ps:celery -A celery_task worker -l info -P eventlet
5.其它程序,导入任务,提交任务即可(add_task.py)
from celery_task.user_task import send_sms
res = send_sms.delay(1999999333, 8888)
print(res) # f33ba3c5-9b78-467a-94d6-17b9074e8533
6.其它程序,查询结果(get_result.py)
from celery_task.celery import app
# celery的包下
from celery.result import AsyncResult
id = '51a669a3-c96c-4f8c-a5fc-e1b8e2189ed0'
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('任务已经开始被执行')
代码
scripts
-celery_task
--__init__.py
--celery.py
--course_task.py
--home_task.py
--user_task.py
-add_task.py
-get_result.py
celery.py
from celery import Celery
broker = 'redis://127.0.0.1:6379/1' # 消息中间件 redis
backend = 'redis://127.0.0.1:6379/2' # 结果存储 redis
app = Celery(__name__,broker=broker,backend=backend,include=["celery_task.course_task",'celery_task.home_task','celery_task.user_task'])
course_task.py
from .celery import app # 不要用 from celery import app
"""
from .celery import app 表示从当前路径下的celery.py 中导入app
from celery import app 表示从celery第三方包包下面导入app,没有
"""
import time
@app.task
def add_course():
print('课程增加成功')
time.sleep(5)
return '课程增加成'
user_task.py
from .celery import app
import time
@app.task
def send_sms(mobile,code):
time.sleep(2)
print('%s手机号,发送短信成功,验证码是:%s' % (mobile, code))
return True
add_task.py
from celery_task.user_task import send_sms
# 1.异步任务
res = send_sms.delay(18081245209,888)
print(res) # 34f5e0ec-bb35-49b8-8946-7cec7ad17844
get_result.py
from celery_task.celery import app
# celery的包下
from celery.result import AsyncResult
id = '34f5e0ec-bb35-49b8-8946-7cec7ad17844'
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执行延迟任务和定时任务
1.celery 可以做
1.异步任务
2.延迟任务--->延迟多长时间干任务
3.定时任务:每天12点钟,每隔几秒
如果只做定时任务,不需要使用celery这么重,apscheduler(自己去研究)
异步任务
1.导入异步任务的函数
from celery_task.user_task import send_sms
2.函数.delay(参数)
res = send_sms.delay(18081245209,888)
add_task.py
from celery_task.user_task import send_sms
# 1.异步任务
res = send_sms.delay(18081245209,888)
print(res) # 34f5e0ec-bb35-49b8-8946-7cec7ad17844
延迟任务
1.导入异步任务的函数
from celery_task.user_task import send_sms
2.函数.apply_async(kwargs={'mobile':'1896334234','code':8888},eta=时间对象)
add_task.py
# 2.延迟任务,延迟5s后,在发短信
# send_sms.apply_async(args=['18081245209',8888])
# res = send_sms.apply_async(kwargs={'mobile':'18081245209','code':8888}) #args或kwargs都可以
from celery_task.user_task import send_sms
from datetime import datetime,timedelta
"""
print(datetime.now()) # 2023-06-29 16:35:51.262542
print(datetime.utcnow()) # 2023-06-29 08:35:51.262555
print(type(datetime.utcnow())) # <class 'datetime.datetime'>-->它可以跟datetime.datetime 做相加
"""
eta = datetime.utcnow()+timedelta(seconds=5)
res = send_sms.apply_async(kwargs={'mobile':'18081245201','code':8888},eta=eta)
print(res) # e1eabc1b-6d0c-4fab-a337-7b0e9e349998
定时任务
1.配置,在app所在的文件下配置--(celery.py)
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
'send_sms': {
'task': 'celery_task.user_task.send_sms',
'schedule': timedelta(seconds=5),
'args': ('1822344343', 8888),
},
'add_course': {
'task': 'celery_task.course_task.add_course',
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'schedule': crontab(hour=11, minute=38), # 每天11点35,执行
'args': (),
}
}
2.启动beat,启动worker
celery -A celery_task worker -l info # beat帮忙提交了任务
celery -A celery_task beat -l info
3.到了时间,beat进程负责提交任务到消息队列--->worker执行
celery.py
from celery import Celery
broker = 'redis://127.0.0.1:6379/1' # 消息中间件 redis
backend = 'redis://127.0.0.1:6379/2' # 结果存储 redis
app = Celery(__name__,broker=broker,backend=backend,include=["celery_task.course_task",'celery_task.home_task','celery_task.user_task'])
# 3 定时任务 1 每隔5s干一次 2 每天的几点几分几秒干一次
# 时区
app.conf.timezone='Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False
# 任务的定时配置
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
'send_sms': {
'task': 'celery_task.user_task.send_sms',
'schedule': timedelta(seconds=5), # 每个五秒
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'args': ('1822344343', 8888),
},
'add_course': {
'task': 'celery_task.course_task.add_course',
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'schedule': crontab(hour=16, minute=48), # 每天11点35,执行
'args': (),
}
}
django中使用celery
使用步骤
1.把之前写好的包,copy到项目根路径下
2.在xx_task.py 中写任务
from .celery import app
@app.task
def add_banner():
from home.models import Banner
Banner.objects.create(title='测试', image='/1.png', link='/banner', info='xxx',orders=99)
return 'banner增加成功'
3.在celery.py 中加载django配置
# 一、加载django配置环境
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffy_api.settings.dev")
4.视图函数中,导入任务,提交即可
class CeleryView(APIView):
def get(self, request):
res = add_banner.delay()
return APIResponse(msg='新增banner的任务已经提交了')
5.启动worker,等待运行即可
解释
1.celery中要使用djagno的东西,才要加这句话
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffy_api.settings.dev")
代码演示
luffy_api
-celery_task
--__init__.py
--celery.py
--course_task.py
--home_task.py
--user_task.py
-luffy_api
--apps
---home
----views.py
----urls.py
home_task.py
from .celery import app
@app.task
def add_banner():
from home.models import Banner
Banner.objects.create(title='yyyy', image='/1.png', link='/banner', info='xxxx',orders=99)
return 'banner添加成功'
celery.py
from celery import Celery
# 一、加载django配置环境
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
# 如果没有此时没有加载django配置环境,在terminal中启动cerely的时候,会报错,ps: from home.models import Banner ,ModuleNotFoundError: No module named 'home'
broker = 'redis://127.0.0.1:6379/1' # 消息中间件 redis
backend = 'redis://127.0.0.1:6379/2' # 结果存储 redis
app = Celery(__name__,broker=broker,backend=backend,include=["celery_task.course_task",'celery_task.home_task','celery_task.user_task'])
Views.py
from celery_task.home_task import add_banner
class CeleryView(APIView):
def get(self,request):
# 同步用
# add_banner()
# 异步用
res = add_banner.delay()
print(res) # 1e5c8b71-acfb-4a84-92be-235d1fffaf6d
return APIResponse(msg='新增banner的任务已经提交了')
接口缓存
1.所有接口都可以改造,尤其是查询所有的这种接口,如果加入缓存,会极大的提高查询速度
2.首页轮播图接口:获取轮播图数据,加缓存--->咱们只是以它为例
3.公司里可能会这么写
写一个查询所有带缓存的基类
写个装饰器,只要一配置,就自动带缓存
4.双写一致性问题:缓存数据和数据库数据不一致了
1.写入数据库,删除缓存
2.写入数据库,更新缓存
3.定时更新缓存
代码演示
from rest_framework.viewsets import GenericViewSet
from .models import Banner
from django.conf import settings
from .serializer import BannerSerializer
from utils.common_mixin import CommonListModelMixin as ListModelMixin
from django.core.cache import cache
class BannerView(GenericViewSet,ListModelMixin):
queryset = Banner.objects.filter(is_delete=False,is_show=True)[:settings.BANNER_COUNT]
serializer_class = BannerSerializer
def list(self,request,*args,**kwargs):
"""
1.先去缓存中查一下有没有数据
2.如果有,直接返回,不走父类的list了(list再走数据库)
3.如果没有,走父类list,查询数据库
4.把返回的数据,翻到缓存中
"""
data = cache.get('home_banner_list')
if not data:
# 取数据库中查询
print('数据库')
res =super().list(request,*args,**kwargs)
# 返回的数据,放到缓存中
data = res.data.get('data')
cache.set('home_banner_list',data)
return APIResponse(data=data)
作业
写一个查询所有带缓存的基类
utils/ [common_mixin.py]
from utils.common_mixin import CacheCommonListModelMixin as CacheListMOdelMIxin
class BannerView1(GenericViewSet,CacheListMOdelMIxin):
queryset = Banner.objects.filter(is_delete=False,is_show=True)[:settings.BANNER_COUNT]
serializer_class = BannerSerializer
# 写一个查询所有带缓存的基类
cache_backends = "home_banner_list"
views.py
class CacheCommonListModelMixin(ListModelMixin):
cache_backends=None
def list(self, request, *args, **kwargs):
if not self.cache_backends:
raise APIException('视图类中必须有cache_backends类属性')
data =cache.get(self.cache_backends)
if not data:
# 取数据库中查询
print('数据库----')
res =super().list(request,*args,**kwargs)
print(res.data)
# 返回的数据,放到缓存中
data = res.data
cache.set(self.cache_backends,data)
return APIResponse(data=data)
写个装饰器,只要一配置,就自动带缓存
Utils/ [commin_wrapers.py]
from utils.common_response import APIResponse
from django.core.cache import cache
def outerest(name):
def cache_show_list(func_name):
def inner(request,*args,**kwargs):
# 需要判断cache中有没有
print(name)
data = cache.get(name)
if not data:
# 如果没有,就执行原来的list函数
res = func_name(request,*args,**kwargs)
data = res.data.get('data')
# 存到缓存中
cache.set(name,data)
return res
return APIResponse(data=data)
return inner
return cache_show_list
Views.py
from utils.commin_wrapers import outerest
class BannerView2(GenericViewSet,ListModelMixin):
queryset = Banner.objects.filter(is_delete=False,is_show=True)[:settings.BANNER_COUNT]
serializer_class = BannerSerializer
@outerest('home_banner_list') # list = cache_show_list(list)
def list(self, request, *args, **kwargs):
print(1111)
res = super().list(request, *args, **kwargs)
return APIResponse(data=res.data)