celery介绍
# celery:翻译过来叫芹菜,它是一个 分布式的异步任务 框架
# celery有什么用?
1 完成异步任务:可以提高项目的并发量,之前开启线程做,现在使用celery做
2 完成延迟任务
3 完成定时任务
# 架构
-消息中间件:broker 提交的任务(函数)都放在这里,celery本身不提供消息中间件,需要借助于第三方:redis,rabbitmq
-任务执行单元:worker,真正执行任务的地方,一个个进程,执行函数
-结果存储:backend,函数return的结果存储在这里,celery本身不提供结果存储,借助于第三方:redis,数据库,rabbitmq
![image]()
celery快速使用
# 1 celery官网:http://www.celeryproject.org/
# 2 介绍:Celery is a project with minimal funding, so we don’t support Microsoft Windows. Please don’t open any issues related to that platform
# 3 celery是独立的服务
"""
1)可以不依赖任何服务器,通过自身命令,启动服务
2)celery服务为为其他项目服务提供异步解决任务需求的
注:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求
人是一个独立运行的服务(django) | 医院也是一个独立运行的服务(celery)
正常情况下,人可以完成所有健康情况的动作,不需要医院的参与;但当人生病时,就会被医院接收,解决人生病问题
人生病的处理方案交给医院来解决,所有人不生病时,医院独立运行,人生病时,医院就来解决人生病的需求
"""
1 安装:pip3 install celery
2 使用步骤
mac启动执行任务命令
- 执行定时任务需要同时执行两句,普通异步执行第一句,每次修改代码后都需要重新执行一次
- celery -A celery_task worker -l info
- celery -A celery_task beat -l info
写一个main.py :实例化得到app对象,写函数,任务,注册成celery的任务
-再别的程序中:提交任务--->提交到broker中去了
add.delay(3,4)
-启动worker,从broker中取任务执行,执行完放到backend中
win:
celery worker -A main -l info -P eventlet # 4.x及之前用这个
celery -A main worker -l info -P eventlet # 5.x及之后用这个
lin,mac:
celery worker -A main -l info
celery -A main worker -l info
-再backend中查看任务执行的结果
-直接看
-通过代码查看
from main import app
from celery.result import AsyncResult
id = '7bef14a0-f83e-4901-b4d8-e47aaac09b39'
if __name__ == '__main__':
res = AsyncResult(id=id, app=app)
if res.successful():
result = res.get() #7
print(result)
elif res.failed():
print('任务失败')
elif res.status == 'PENDING':
print('任务等待中被执行')
elif res.status == 'RETRY':
print('任务异常后正在重试')
elif res.status == 'STARTED':
print('任务已经开始被执行')
封装包使用
project
├── celery_task # celery包
│ ├── __init__.py # 包文件
│ ├── celery.py # celery连接和配置相关文件,且名字必须叫celery.py
│ └── tasks.py # 所有任务函数
├── add_task.py # 添加任务
└── get_result.py # 获取结果
核心实例化文件celery.py
from celery import Celery
backend = 'redis://127.0.0.1:6379/1' # 如果有密码broker='redis://:123456@127.0.0.1:6379/2'
broker = 'redis://127.0.0.1:6379/0'
app = Celery(__name__, broker=broker, backend=backend, include=['celery_task.home_task', 'celery_task.user_task'])
# 时区配置项 修改东八区时间
# app.conf.timezone = 'Asia/ShangHai'
# app.conf.enable_utc = False
# 编写定时任务每隔五秒执行 可以配置多个
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
'send_sms_task': {
'task': 'celery_task.user_task.send_sms', # 指定执行的任务
'schedule': timedelta(seconds=3), # 时间对象每隔三秒执行一次
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'args': (300, 150), # 执行函数的参数
}
}
被管理的执行函数文件celery_task.home_task.py,配置在核心文件 include中
from .celery import app
import time
@app.task
def send_sms(mobile, code):
time.sleep(20)
# print('计算结果是:%s' % (mobile+code))
return mobile+code
提交立即执行异步任务文件写在包外面add.task中编写
from celery_task import send_sms
# 提交计算任务,返回任务id
res = send_sms.delay(11,22)
print(res) # 打印任务id用于查询返回结果
提交延时任务add.task中编写
- 任务.apply_async(args=[参数,参数],eta=时间对象(utc时间))
from celery_task import send_sms
# utc的时间在核心文件修改时区
from datetime import datetime, timedelta
eta = datetime.utcnow() + timedelta(seconds=10)
res = send_sms.apply_async(args=(11,11), eta=eta)
print(res)
定时任务编写
# 编写定时任务每隔五秒执行 可以配置多个
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
'send_sms_task': {
'task': 'celery_task.user_task.send_sms', # 指定执行的任务
'schedule': timedelta(seconds=3), # 时间对象每隔三秒执行一次
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'args': (300, 150), # 执行函数的参数
}
}
# 定时任务配置
-1 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': ('1897334444', '7777'),
},
'add_task': {
'task': 'celery_task.home_task.add',
'schedule': crontab(hour=12, minute=10, day_of_week=3), # 每周一早八点
'args': (10, 20),
}
}
-2 启动worker :干活的人
celery -A celery_task worker -l info -P eventlet
-3 启动beat :提交任务的人
celery -A celery_task beat -l info
获取执行结果文件get_result.py
from celery_task.celery import app
from celery.result import AsyncResult
id = '753e14db-6aa3-41f6-b13a-710f6056e2bf' # 任务的id
if __name__ == '__main__':
res = AsyncResult(id=id, app=app) # res获取执行的结果
if res.successful():
result = res.get()
print(result)
elif res.failed():
print('任务失败')
elif res.status == 'PENDING':
print('任务等待中被执行')
elif res.status == 'RETRY':
print('任务异常后正在重试')
elif res.status == 'STARTED':
print('任务已经开始被执行')
django中使用celery
# 使用步骤:
1 把写好的包复制到项目路径下
2 在包内的celery.py 的上面加入代码
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
import django
django.setup()
3 在django的视图类中,导入,提交任务
4 启动worker,beat
轮播图接口加缓存
- 首页轮播图接口,加缓存
提交了接口的响应速度
提高并发量
class BannerView(GenericViewSet, CommonListModelMixin):
# 类实例化时会执行
queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[
:settings.BANNER_COUNT]
serializer_class = BannerSerializer
# 重写获取轮播图对象方法
# 如果缓存中有值则直接使用缓存的数据如果没有才走数据库查询并且将查询之后的数据存入缓存中下次来判断是否有值有值则直接使用
# 过滤没有被删除的,并且可以显示的,按orders做排序
# def get_queryset(self):
# res = cache.get('banner')
# if res:
# print('没走数据库')
# queryset = res
# else:
# print('我走了数据库')
# queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[
# :settings.BANNER_COUNT]
# cache.set('banner', queryset)
# if isinstance(queryset, QuerySet):
# queryset = queryset.all()
# return queryset
# 视图类对象来调list方法 匹配视图函数的时候就执行
def list(self, request, *args, **kwargs):
result = cache.get('banner')
if result:
print('没走数据库,速度快')
return APIResponse(result=result)
else:
res = super().list(request, *args, **kwargs)
print('我走了数据库,速度慢')
result = res.data.get('result')
cache.set('banner', result)
return res
![image]()
加缓存出问题
# 加了缓存,如果mysql数据变了,由于请求的都是缓存的数据,导致mysql和redis的数据不一致
# 双写一致性问题
-1 修改mysql数据库,删除缓存 【缓存的修改是在后】
-2 修改数据库,修改缓存 【缓存的修改是在后】
-3 定时更新缓存 ---》针对于实时性不是很高的接口适合定时更新
# 给首页轮播图接口加入了缓存,出现了双写一致性问题,使用定时更新来解决双写一致性的问题【会存在不一致的情况,我们可以忽略】---》定时任务,celery的定时任务
celery定时任务实现双写一致性
home_task.py
from django.core.cache import cache
from home.models import Banner
from django.conf import settings
from home.serializer import BannerSerializer
from .celery import app
# 编写执行函数
# 更新缓存
# 查询数据
# 序列化之后将序列化之后的数据设置为缓存
# 由于序列化出来的结果少了路由前缀需要手动修改/media/banner/banner1.png循环出结果然后手动修改地址
# 修改之后存入缓存返回True
@app.task
def update_banner():
queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[
:settings.BANNER_COUNT]
ser = BannerSerializer(instance=queryset, many=True)
for i in ser.data:
i['image'] = settings.HOST_URL+i['image']
cache.set('banner', ser.data)
return True
celery.py
from celery import Celery
from datetime import timedelta
# django中集成需要导入下面四句
"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
django.setup()
"""
backend = 'redis://127.0.0.1:6379/1' # 如果有密码broker='redis://:123456@127.0.0.1:6379/2'
broker = 'redis://127.0.0.1:6379/0'
app = Celery(__name__, broker=broker, backend=backend, include=['celery_task.home_task', 'celery_task.user_task'])
# 时区配置项 修改东八区时间
# app.conf.timezone = 'Asia/ShangHai'
# app.conf.enable_utc = False
# 编写定时任务每隔五秒执行 可以配置多个定时更新轮播图
app.conf.beat_schedule = {
'send_sms_task': {
'task': 'celery_task.home_task.update_banner', # 指定执行的任务
'schedule': timedelta(seconds=3), # 时间对象每隔三秒执行一次
'args': (), # 执行函数的参数
}
}
# 启动django
# 启动worker
# 启动beat
# 第一次访问:查的数据库,放入了缓存
# 以后再访问,走缓存
# 一旦mysql数据改了,缓存可能不一致
# 当时我们定时更新,最终保持了一致