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本身就是双向的,

定时任务也是可以这样的,

posted @ 2022-08-08 18:09  技术改变命运Andy  阅读(223)  评论(0编辑  收藏  举报