python操作celery,celery可以完全独立于自己的代码,远程调用,远程获取结果
基本介绍
Celery 官网:http://www.celeryproject.org/
Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.html
Celery 官方文档中文版:http://docs.jinkan.org/docs/celery/
https://www.celerycn.io/ru-men/celery-chu-ci-shi-yong
Celery是一个简单、灵活且可靠的,处理大量消息的分布式系统
专注于实时处理的异步任务队列
同时也支持任务调度
celery安装
pip install celery
消息中间件:RabbitMQ/Redis
app=Celery(‘任务名’, broker=’xxx’, backend=’xxx’)
两种celery任务结构:提倡用包管理,结构更清晰
celery简单实用
第一步:创建tasks.py
from celery import Celery
app = Celery('tasks', broker='amqp://guest@localhost//', backend='rpc://guest@localhost//')
@app.task
def add(a, b):
return a + b
@app.task
def mul(a, b):
return a * b
第二步:启动worker
celery -A tasks worker --loglevel=info
第三步:创建run.py
from tasks import add, mul
res=add.delay(100,4)
print(res) # id号
第四步:查看任务执行结果
from t_celery import app
from celery.result import AsyncResult
# 关键字,变量不能定义为关键字
id = '5331c70b-1b51-4a15-aa17-2fa0f7952c00'
if __name__ == '__main__':
res = AsyncResult(id=id, app=app)
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('任务已经开始被执行')
celery多任务结构
项目目录
package_celery: # 项目名
celery_task # celery包名
__init__.py
celery.py # celery 的app,必须叫celery,配置文件,
order_task.py # 任务
user_task.py # 任务
result.py # 结果查询
submit_tast.py # 提交任务
所以真实的项目都是多任务的,
可以把任务进行写入多个文件,用统一的文件夹进行管理
还有对应的配置文件,
然后就是web服务的加入任务和查询任务了,项目结构是这样的,
运行woeker(在package_celery目录下执行)
celery worker -A celery_task -l info -P eventlet
提交任务
from celery_task import order_task,user_task
# 提交一个给用户发短信的任务
res=user_task.send_sms.delay('18723454566')
print(res)
# 提交一个取消订单任务
res=order_task.cancel_order.delay()
print(res)
真实应用场景
-秒杀系统
-不能秒超,使用锁(mysql悲观锁,乐观锁),redis锁
-提高并发量---》把同步做成异步---》使用celery
-前端点击秒杀按钮,向后端发送秒杀请求---》同步操作
-同步操作
-请求来到后端,判断数量是否够,如果够,要生成订单(mysql),订单状态是待支付状态
-请求返回,告诉前端,秒杀成功
-异步操作
-请求来到后端,提交一个celery任务---》celery任务异步的执行判断数量是否够,如果够,要生成订单(mysql)
-秒杀是否成功的结果还没有,直接返回了(返回任务id)
-前端启动一个定时任务,每隔5s,向后台发送一个查询请求,查询秒杀任务是否执行完成(带着任务id查)
-如果是未执行状态,或者执行中---》返回给前端,前端不处理,定时任务继续执行
-又隔了5s,发送查询,查询到秒杀成功的结果,返回给前端,秒杀成功
高级使用之延时任务
第一种方式:2021年1月7日17点3分12秒发送短信
# from datetime import datetime
# # # eta:延迟多长时间执行,eta需要传时间对象,并且是utc时间
# v1 = datetime(2021, 1, 7, 17, 3, 12)
# print(v1)
# v2 = datetime.utcfromtimestamp(v1.timestamp())
# print(v2)
# res=user_task.send_sms.apply_async(args=['189****4332',],eta=v2)
第二种方式:隔几秒后执行
from datetime import datetime
from datetime import timedelta
ctime = datetime.now()
# 默认用utc时间
utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
time_delay = timedelta(seconds=10)
task_time = utc_ctime + time_delay
print(task_time)
res=user_task.send_sms.apply_async(args=['189****4332',],eta=task_time)
上面这两种都是一次性的执行任务,不会去循环执行
我们想要循环执行要使用定时任务
Ⅷ : 高级使用之定时任务
在celery.py中配置
# 时区
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-msg':{
'task': 'celery_task.user_task.send_sms',
# 'schedule': timedelta(hours=24*10),
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'schedule': crontab(hour=8, day_of_month=1), # 每月一号早八点
'args': ('18964352112',),
}
}
# 启动beat,负责每隔3s提交一个任务
celery beat -A celery_task -l info
# 启动worker
celery worker -A celery_task -l info -P eventlet
一定要注意如果先启动beat会有很多的累计任务,一直周期的往里面加,
所以你worker一启动,就会并发处理这些任务,
所以启动的时候,还是先启动worker,然后启动beat
关闭的时候也要注意,一定要worker和beat都要关闭,不然beat会导致累计大量的任务,
我们发现这个定时任务的配置是放到配置文件里面的,
而每次修改定时任务配置,都需要重新命令启动celery beat,所以是无法实时动态的去修改定时任务配置的,
所以Celery无法动态添加定时任务,可以在程序固定位置添加定时任务
Celery无法在Flask这样的系统中动态添加定时任务(在Django中有相应的插件可以实现动态添加任务django-celery-beat)
所以怎么办?
可以单独使用apschedule集成进来,
Django项目使用celery
在web项目使用celery这个目录很重要,
把celery_task放到项目根目录
mycelery/ # 这个放到Django项目根目录,叫什么名字无所谓,
├── config.py # 配置文件
├── __init__.py
├── main.py # 这个main文件最重要,
└── sms/ # 这个是存放任务的地方的地方,叫什么名字无所谓
├── __init__.py
├── tasks.py # 这个必须叫tasks名字,这是写任务的地方,
└── email/ # 这个是存放任务的地方的地方,叫什么名字无所谓
├── __init__.py
├── tasks.py # 这个必须也要叫tasks名字,这是写任务的地方,
配置文件config.py:
broker_url = 'redis://127.0.0.1:6379/15'
result_backend = 'redis://127.0.0.1:6379/14'
任务文件tasks.py:
# celery的任务必须写在tasks.py的文件中,别的文件名称不识别!!!
from mycelerys.main import app
import time
import logging
log = logging.getLogger("django")
@app.task # name表示设置任务的名称,如果不填写,则默认使用函数名做为任务名
def send_sms(mobile):
"""发送短信"""
print("向手机号%s发送短信成功!"%mobile)
time.sleep(5)
return "send_sms OK"
@app.task # name表示设置任务的名称,如果不填写,则默认使用函数名做为任务名
def send_sms2(mobile):
print("向手机号%s发送短信成功!" % mobile)
time.sleep(5)
return "send_sms2 OK"
最后在main.py主程序中对django的配置文件进行加载
# 主程序
import os
from celery import Celery
# 创建celery实例对象
app = Celery("sms")
# 把celery和django进行组合,识别和加载django的配置文件
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celeryPros.settings.dev')
# 通过app对象加载配置
app.config_from_object("mycelerys.config")
# 加载任务
# 参数必须必须是一个列表,里面的每一个任务都是任务的路径名称
# app.autodiscover_tasks(["任务1","任务2"])
app.autodiscover_tasks(["mycelerys.sms",])
# 启动Celery的命令
# 强烈建议切换目录到mycelery根目录下启动
# celery -A mycelery.main worker --loglevel=info
这一点很重要,就是要和mycelery目录同级下执行,这样比较稳定,不会出问题,
Django视图调用:
from django.shortcuts import render
# Create your views here.
from django.shortcuts import render,HttpResponse
from mycelerys.sms.tasks import send_sms,send_sms2
from datetime import timedelta
from datetime import datetime
def test(request):
################################# 异步任务
# 1. 声明一个和celery一模一样的任务函数,但是我们可以导包来解决
# send_sms.delay("110")
# send_sms2.delay("119")
# send_sms.delay() 如果调用的任务函数没有参数,则不需要填写任何内容
################################# 定时任务
# ctime = datetime.now()
# # 默认用utc时间
# utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
# time_delay = timedelta(seconds=10)
# task_time = utc_ctime + time_delay
# result = send_sms.apply_async(["911", ], eta=task_time)
# print(result.id)
return HttpResponse('ok')
from mycelerys.sms.tasks import send_sms,send_sms2
注意,这是在生产者里面,导入的是消费者的代码,
所以你的生产者和消费者要在同一个服务器的,
如果生产者在一个服务器,消费者在不同的服务器,这是有可能的,
这样你导包是导不过来的,
没有什么好的办法,所以在生产者的机器,要复制一份消费者的代码,让生产者可以去调用,
flask使用celery
这是一个flask的demo
具体的文件结构,还是使用上面的,
第一步:准备这个文件,server.py
import os
import json
import time
import random
from flask import Flask, url_for, jsonify, request, make_response
from celery import Celery, states
from flask_cors import CORS, cross_origin
import numpy as np
import pandas as pd
app = Flask(__name__)
CORS(app)
# Additional Celery configurations to flask's config
# app.config['CELERY_RESULT_BACKEND'] = 'amqp://[USERNAME]:[PASSWORD]@localhost/[VHOST_NAME]'
# app.config['CELERY_BROKER_URL'] = 'rpc://guest:guest@localhost/test'
# app.config['CELERY_RESULT_BACKEND'] = 'amqp://guest:guest@localhost/test'
# Instantiate Celery object, provided with broker url
celery = Celery(app.name, broker='amqp://guest@localhost//', backend='rpc://guest@localhost//')
# Add additional configurations from flask’s config.
# celery.conf.update(app.config)
@app.route('/upload', methods=['POST'])
def upload():
'''
这里服务器接收带有POST请求的csv文件,
然后将其保存到文件夹,/uploads。
然后我们使用.apply_async()异步应用一个Celery任务,
read_csv_task作为Celery任务。一旦发出,
我们将任务的id发送回客户端。
'''
file_obj = request.files.get('file')
print(f"file_obj = {file_obj}")
file_name = file_obj.filename
path = os.path.join('/Users/liqian/PycharmProjects/SwordCaster/FlaskServer/mytest/celery_test/uploads', file_name)
content = file_obj.read()
print(content)
with open(path, "w+") as f:
f.write(content.decode("utf-8"))
# try:
# file_obj.save(path)
# except IOError as e:
# print(e)
#
# print('I/O Error')
# file_obj.close()
task_list = []
for i in range(0, 2):
file_task = read_csv_task.apply_async(args=[path])
task_list.append(str(file_task.task_id))
return make_response(jsonify({'task_list': task_list}))
@app.route('/task/<task_id>', methods=['GET'])
def check_task_status(task_id):
'''
.AncResult()允许您访问任务状态(挂起、成功或失败),
使用任务id。我们的响应对象包括状态,
以及任务完成时的结果。
'''
task = read_csv_task.AsyncResult(task_id)
state = task.state
print(state)
response = {}
response['state'] = state
if state == states.SUCCESS:
response['result'] = task.get()
elif state == states.FAILURE:
try:
response['error'] = task.info.get('error')
except Exception as e:
response['error'] = 'Unknown error occurred'
return make_response(jsonify(response))
@celery.task(bind=True)
def read_csv_task(self, path):
'''
Each task needs to have a decorator, @celery.task.
By setting bind=True, the task function can access self as an argument,
where we can update the task status with useful information.
'''
print(f"read_csv_task {path}")
self.update_state(state=states.PENDING)
time.sleep(random.randint(30, 50))
df = pd.read_csv(path)
result = compute_properties(df)
print(result)
return result
def compute_properties(df):
'''
Give summary stats about each column with pandas and numpy.
'''
properties = {}
properties['num_rows'] = len(df)
properties['num_columns'] = len(df.columns)
properties['column_data'] = get_column_data(df)
return properties
def get_column_data(df):
result = []
for c in df:
info = {}
col = df[c]
info['name'] = c
info['num_null'] = str(col.isnull().sum())
if col.dtypes == 'int64':
info['mean'] = str(np.mean(col))
info['median'] = str(np.median(col))
info['stddev'] = str(np.std(col))
info['min'] = str(col.min())
info['max'] = str(col.max())
else:
unique_values = col.unique().tolist()
print(len(unique_values), len(df))
if len(unique_values) < len(df):
info['unique_values'] = unique_values
else:
info['unique_values'] = True
result.append(info)
return result
if __name__ == '__main__':
app.run(port=8982, debug=True)
第二步,开启文件,开启work
python server.py
celery -A server.celery worker --loglevel=info
celery flower使用
1、flower简介
flower是基于web的监控和管理Celery的工具,和任务队列是隔离的,flower的运行并不会影响到任务队列的真正执行。flower作为celery后台任务的管理工具,将各个任务的执行情况、各个worker的健康状态进行实时监控并以可视化的方式展现。
注:官网链接
https://pypi.org/project/celery-flower/1.0.1/
https://flower.readthedocs.io/en/latest/
2、安装
pip3 install flower
3、使用
# celery以redis作为broker
celery flower --address=0.0.0.0 --port=5555 --broker=redis://localhost:6379/1
# 以rabbitmq作为broker
celery flower --address=0.0.0.0 --port=5555 --broker=amqp://guest@localhost//
4、web访问
在浏览器中输入设备的IP:5555(默认端口),即可访问flower的web页面
4.1web选项卡介绍
Dashboard 选项卡:展示异步任务队列的主要情况。
Tasks 选项卡:展示所有worker接收到的任务的处理情况。
Broker 选项卡:展示celery连接消息队列的信息,包括消息队列的访问URL。
Monitor 选项卡:展示celery后台任务的曲线展示状况。
celery远程调用,
celery可以完全独立于自己的代码,远程调用,远程获取结果
第一步:A机器启动celery
from celery import Celery
celery = Celery('tasks', broker='amqp://guest@localhost//', backend='redis://127.0.0.1:6379/1')
@celery.task
def add(a, b):
return a + b
第二步:B机器远程调用
from celery import Celery
celery = Celery('tasks', broker='amqp://guest@localhost//', backend='redis://127.0.0.1:6379/1')
celery_task = celery.send_task('celery_task.debug_task.task.add', [1,1])
# 传入的是celery注册的task, 一定路径要对,
第三步:B机器远程获取结果
from celery import Celery
celery = Celery('tasks', broker='amqp://guest@localhost//', backend='redis://127.0.0.1:6379/1')
res = celery.AsyncResult('9c51786e-9b2a-4a0b-931e-7222b2ba0e90')
使用思考
1,celery很简单,
把耗时的操作都用celery异步去做
比如执行调试任务,
比如发送短信,比如发送邮件,比如发送企微通知,
这样就不用自己写线程协程多路复用等等去处理并发了,
使用celery进行并发,
这里面使用到了异步,并发,分布式,
2,使用celery消费者
要自己提前定义好celery任务,可以用多个,
worker命令做了什么,就是启动的事消费者
连接中间件,
创建队列,
监听
...
这是消费者的定义,
3,生产者呢
实际工作中生产者就是web服务,或者一个python脚本,都可以,
使用delay方法去生产,
所以celery已经帮我们封装好了,这是非常方便的,
4,我的设计
思路:
第一步:使用web服务作为生产者,把任务往队列里面推,
第二步:然后单独写celery任务,我定义多个celery任务,
然后worker启动去消费,
第三步:然后web服务去取值,看看任务的结果,
任务成功,失败,都要做处理,
具体:
我可以只写一个调试任务,
发送---执行---获取
这样就不需要所谓的master分发任务了,celery自己分发的,
而且也不需要socket通信了,celery本身就是双向的,
定时任务也是可以这样的,