Celery的使用

celery是什么

Celery是一个简单、灵活且可靠的,处理大量消息的分布式系统,专注于实时处理的异步任务队列,同时也支持任务调度。
Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。


消息中间件
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等
任务执行单元
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
任务结果存储
Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等
另外, Celery还支持不同的并发和序列化的手段
并发:Prefork, Eventlet, gevent, threads/single threaded
序列化:pickle, json, yaml, msgpack. zlib, bzip2 compression, Cryptographic message signing 等等

使用场景

celery是一个强大的 分布式任务队列的异步处理框架,它可以让任务的执行完全脱离主程序,甚至可以被分配到其他主机上运行。我们通常使用它来实现异步任务(async task)和定时任务(crontab)。
异步任务:将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等
定时任务:定时执行某件事情,比如每天数据统计

安装

pip install -U Celery
或着:
sudo easy_install Celery

基本使用

单文件夹下使用

创建celery_test文件夹,pro写生产端,tasks写消费端,result用来获取请求结果

  • 创建消费端代码 tasks.py
import celery
import time

backend = "redis://:密码@ip:port/14"
broker = "redis://:密码@ip:port/15"
cel = celery.Celery('test', backend=backend, broker=broker)

# 使用装饰器 加载任务
@cel.task()
def send_sms(name):
    print("开始发送")
    time.sleep(3)
    return "发送成功%s" % name

在tasks.py的当前目录运行celery的worker
celery -A tasks worker -l info 5版本命令和之前略有不同
出现下面信息表示成功运行

  • 创建生产端代码 Pro.py
    运行后返回一个id值
from tasks import send_sms
ret = send_sms.delay("hahaha ")
print(ret)
  • 创建异步获取结果 result.py
from tasks import cel
from celery.result import AsyncResult
# id是pro运行后获取的id值,app是生产端的实例对象
asyncresult = AsyncResult(id="4b235142-1921-4eff-9e41-27886b273a8e",app=cel)
print(asyncresult.status)
ret = asyncresult.get()
asyncresult.forget()
print(ret)
  • 异步任务结果的多种处理方式
asyncresult.get()     # 获取结果
asyncresult.forget()  #删除结果, 默认执行完成不会自动删除
asyncresult.revoke(terminate=True)  # 无论任务什么状态,都要终止
asyncresult.revoke(terminate=False) # 如果任务还没开始执行,则终止
asyncresult.failed()  #如果任务执行失败 返回true

还可以查看任务状态
asyncresult.status会输出任务状态
比如 ok PENDING,RETRY STARTED

多文件夹下使用

文件结构如下, mycelery用来保存celery实例的配置

  • mycelery.py 注意此时需要用include关键字,指定异步的任务路径
import celery
backend='redis://:password@ip:port/14'
broker='redis://:password@ip:port/15'
cel=celery.Celery('test',backend=backend,broker=broker,include=[
    'celery_tasks.task01',
    'celery_tasks.task02'
                      ])
# 时区
cel.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
cel.conf.enable_utc = False
  • task01/02.py
    写入任务函数,注意导包路径
from celery_tasks.mycelery import cel
import time

# 使用装饰器 加载任务
@cel.task()
def send_sms(name):
    print("短信开始发送")
    time.sleep(3)
    return "短信发送成功%s" % name
  • 启动worker,注意启动路径和参数里包含的相对路径celery_tasks.mycelery
    建议后续都从根路径启动workder,并用相对路径指定配置文件的位置,
    这里是在celery_test2的根路径下启动的worker
young_shi@MacBook-Air-2 celery_test2 % ls                                            
celery_tasks    pro.py          result.py
young_shi@MacBook-Air-2 celery_test2 % celery -A celery_tasks.mycelery worker -l info
  • 生产端代码
from celery_tasks.task01 import send_sms
result = send_sms.delay("yuan")
print(result.id)
result2 = send_sms.delay("alex")
print(result2.id)

celery执行定时任务

celey的delay方法可以异步执行,而定时任务要用到apply_async方法
异步任务名.apply_async((arg,), {'kwarg': value}, countdown=60, expires=120)

  • 简单结构下的定时任务 在pro文件给apply_async添加延时
from celery_task import send_email
from datetime import datetime

# 方式一
# v1 = datetime(2020, 3, 11, 16, 19, 00)
# print(v1)
# v2 = datetime.utcfromtimestamp(v1.timestamp())
# print(v2)
# result = send_email.apply_async(args=["egon",], eta=v2)
# print(result.id)

# 方式二
ctime = datetime.now()
# 默认用utc时间
utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
from datetime import timedelta
time_delay = timedelta(seconds=10)
task_time = utc_ctime + time_delay

# 使用apply_async并设定时间
result = send_email.apply_async(args=["egon"], eta=task_time)
print(result.id)
  • 多任务结构中
    代码结构和上面一样.这里展示2中定时任务写法

生产模型延时执行

mycelery的代码不变,pro.py代码如下, delay方法改成apply_async方法,并加上延时的datetime对象

  1. pro.py代码修改如下
from datetime import datetime
from celery_tasks.task01 import send_sms

# 方式一
# v1 = datetime(2020, 3, 11, 16, 19, 00)
# print(v1)
# v2 = datetime.utcfromtimestamp(v1.timestamp())
# print(v2)
# result = send_sms.apply_async(args=["egon",], eta=v2)
# print(result.id)

# 方式二
ctime = datetime.now()
# 默认用utc时间
utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
from datetime import timedelta
time_delay = timedelta(seconds=10)
task_time = utc_ctime + time_delay

# 使用apply_async并设定时间
result = send_sms.apply_async(args=["egon"], eta=task_time)
print(result.id)

配置文件中添加定时执行

这种模式和生产消费模型关系不大,在mycelery.py添加beat_schedule配置

from datetime import timedelta
import celery
from celery.schedules import crontab

backend='redis://:密码@ip:port/14'
broker='redis://:密码@ip:port/15'
cel=celery.Celery('test',backend=backend,broker=broker,include=[
    'celery_tasks.task01',
    'celery_tasks.task02'
                      ])
# 时区
cel.conf.timezone = 'Asia/Shanghai'
cel.conf.enable_utc = False
# 新增了下面部分
cel.conf.beat_schedule = {
    # 名字随意命名
    'add-every-10-seconds': {
        # 执行tasks1下的test_celery函数
        'task': 'celery_tasks.task01.send_sms',
        # 每隔2秒执行一次
        # 'schedule': 1.0,
        # 'schedule': crontab(minute="*/1"),
        'schedule': timedelta(seconds=6),
        # 传递参数
        'args': ('张三',),

    },
    # 'add-every-12-seconds': {
    #     'task': 'celery_tasks.task01.send_email',
    #     每年4月11号,8点42分执行
    #     'schedule': crontab(minute=42, hour=8, day_of_month=11, month_of_year=4),
    #     'args': ('张三',)
    # },
}

celery -A celery_tasks.mycelery worker -l info开启worker进行监听
celery -A celery_tasks.mycelery worker -B -l info 这里-B参数会读取beat_schedule里的配置参数,定时执行.
这里命令用的5.0版本,和4.0版本有点不一样 4.0命令格式 celery beat -A proj
Celery Beat进程会读取配置文件的内容,周期性的将配置中到期需要执行的任务发送给任务队列

celery的常见方法和属性总结

# 调用celery执行异步任务
from mycelery.sms.tasks import send_sms1,send_sms2,send_sms3,send_sms4
mobile = "13312345656"
code = "666666"

# delay 表示马上按顺序来执行异步任务,在celrey的worker工作进程有空闲的就立刻执行
# 可以通过delay异步调用任务,可以没有参数
ret1 = send_sms1.delay()
# 可以通过delay传递异步任务的参数,可以按位置传递参数,也可以使用命名参数
# ret2 = send_sms.delay(mobile=mobile,code=code)
ret2 = send_sms2.delay(mobile,code)

# apply_async 让任务在后面指定时间后执行,时间单位:秒/s
# 任务名.apply_async(args=(参数1,参数2), countdown=定时时间)
ret4 = send_sms4.apply_async(kwargs={"x":10,"y":20},countdown=30)

# 根据返回结果,不管delay,还是apply_async的返回结果都一样的。
ret4.id      # 返回一个UUID格式的任务唯一标志符,78fb827e-66f0-40fb-a81e-5faa4dbb3505
ret4.status  # 查看当前任务的状态 SUCCESS表示成功! PENDING任务等待
ret4.get()   # 获取任务执行的结果[如果任务函数中没有return,则没有结果,如果结果没有出现则会导致阻塞]  重要!!!

if ret4.status == "SUCCESS":
    print(ret4.get())

项目中使用celery

celery作为一个单独项目运行

app01
mycelery/          1.新建一个package
├── config.py      2. 添加配置文件用`config_from_object`加载 
├── __init__.py
├── main.py        5. 这里生成celery实例对象,加载celery的配置和任务
└── sms/           3. 对异步功能单独创建一个package, 但是必须要有tasks.py文件,文件名不能取别的
    ├── __init__.py
    ├── tasks.py   4. 文件名必须叫tasks,用来编写异步执行的函数
└── email/
    ├── __init__.py
    ├── tasks.py

config代码

# CELERY_ENABLE_UTC = False    这两个参数可能celery 5.0格式不对
# CELERY_TIMEZONE = "Asia/Shanghai"
broker_url = "redis://:密码@ip:port/14"
result_backend = 'redis://:密码@ip:port/15'

sms下的tasks

from mycelery.main import app
# from mycelerys.main import app
import time


@app.task  # name表示设置任务的名称,如果不填写,则默认使用函数名做为任务名
def send_sms(mobile):
    """发送短信"""
    print("向手机号%s发送短信成功!"%mobile)
    time.sleep(5)

    return "send_sms OK"

main代码

# 主程序
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("mycelery.config")

# 加载任务
# 参数必须必须是一个列表,里面的每一个任务都是任务的路径名称
# app.autodiscover_tasks(["任务1","任务2"])
app.autodiscover_tasks(["mycelery.sms",])

# 启动Celery的命令
# 强烈建议切换目录到mycelery根目录下启动
# celery -A mycelery.main worker --loglevel=info

django视图代码

from mycelery.sms.tasks import send_sms
def celery_test(request):
    send_sms.delay("15867416745")
    return HttpResponse("555")

Celery不建议在windows系统下使用,Celery在4.0版本以后不再支持windows系统,所以如果要在windows下使用只能安装4.0以前的版本,而且即便是4.0之前的版本,在windows系统下也是不能单独使用的,需要安装gevent、geventlet或eventlet协程模块
并且运行参数要加-P
celery -A celery_task worker -l info -P eventlet

celery调度其他模块

这里以调度django里项目里的应用举例

  1. 对django导包引入
    在main.py主程序中对django进行导包引入,并设置django的配置文件进行django的初始化。
import os,django
from celery import Celery
# 初始化django 这2步django项目里初始化2步就是这么处理的 如果manage.py文件已经有加载,这个初始化步骤可以省略!!!
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffycityapi.settings.dev')    #在manage.py文件中 和wsgi文件中
django.setup()   #在wsgi文件中

# 初始化celery对象
app = Celery("luffycity")

# 加载配置
app.config_from_object("mycelery.config")
# 自动注册任务
app.autodiscover_tasks(["mycelery.sms","mycelery.email"])
# 运行celery
# 终端下: celery -A mycelery.main worker -l info
  1. 这里注意练习过程中发现的2个低级错误
  2. celery.py最好改个名字,容易和模块celery名字冲突
  3. 其他文件导包的模块必须从luffycityapi/ # 服务端项目根目录开始导入,否则celery作为第三方模块加载时候 无法从根目录找到响应的路径
luffycityapi/           # 服务端项目根目录
└── luffycityapi/       # 主应用目录
    ├── apps/           # 子应用存储目录  
    ├   └── Home/            # django的home子应用
    ├   └── users/            # django的users子应用
    ├       └── tasks.py      # [新增]分散在各个子应用下的异步任务模块
    ├── settings/     # [修改]django的配置文件存储目录[celery的配置信息填写在django配置中即可]
    ├── __init__.py   # [修改]设置当前包目录下允许外界调用celery应用实例对象
    └── Mycelery.py     # [新增]celery入口程序,相当于上一种用法的main.py,不要起名celery,会冲突!!!
  1. luffycityapi/Mycelery.py,主应用目录下创建cerley入口程序,创建celery对象并加载配置和异步任务,代码:
import os
from celery import Celery

# 必须在实例化celery应用对象之前执行
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffycityapi.settings.dev')

# 实例化celery应用对象
app = Celery('luffycityapi')
# 指定任务的队列名称
app.conf.task_default_queue = 'Celery'
# 也可以把配置写在django的项目配置中
app.config_from_object('django.conf:settings', namespace='CELERY') # 设置django中配置信息以 "CELERY_"开头为celery的配置信息
# 自动根据配置查找django的所有子应用下的tasks任务文件
app.autodiscover_tasks()
  1. settings 这里将celery的配置信息集中写到django的配置文件中了, settings/dev.py,代码:
# Celery异步任务队列框架的配置项[注意:django的配置项必须大写,所以这里的所有配置项必须全部大写]
# 任务队列
CELERY_BROKER_URL = 'redis://:123456@127.0.0.1:6379/14'
# 结果队列
CELERY_RESULT_BACKEND = 'redis://:123456@127.0.0.1:6379/15'
# 时区,与django的时区同步
CELERY_TIMEZONE = TIME_ZONE
# 防止死锁
CELERY_FORCE_EXECV = True
# 设置并发的worker数量
CELERYD_CONCURRENCY = 200
# 设置失败允许重试[这个慎用,如果失败任务无法再次执行成功,会产生指数级别的失败记录]
CELERY_ACKS_LATE = True
# 每个worker工作进程最多执行500个任务被销毁,可以防止内存泄漏,500是举例,根据自己的服务器的性能可以调整数值
CELERYD_MAX_TASKS_PER_CHILD = 500
# 单个任务的最大运行时间,超时会被杀死[慎用,有大文件操作、长时间上传、下载任务时,需要关闭这个选项,或者设置更长时间]
CELERYD_TIME_LIMIT = 10 * 60
# 任务发出后,经过一段时间还未收到acknowledge, 就将任务重新交给其他worker执行
CELERY_DISABLE_RATE_LIMITS = True
# celery的任务结果内容格式
CELERY_ACCEPT_CONTENT = ['json', 'pickle']

# 之前定时任务(定时一次调用),使用了apply_async({}, countdown=30);
# 设置定时任务(定时多次调用)的调用列表,需要单独运行SCHEDULE命令才能让celery执行定时任务:celery -A mycelery.main beat,当然worker还是要启动的
# https://docs.celeryproject.org/en/stable/userguide/periodic-tasks.html
from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
    "user-add": {  # 定时任务的注册标记符[必须唯一的]
        "task": "add",   # 定时任务的任务名称
        "schedule": 10,  # 定时任务的调用时间,10表示每隔10秒调用一次add任务
        # "schedule": crontab(hour=7, minute=30, day_of_week=1),,  # 定时任务的调用时间,每周一早上7点30分调用一次add任务
    }
}
  1. luffycityapi/__init__.py,主应用下初始化,代码:
import pymysql
from .Mycelery import app as celery_app
pymysql.install_as_MySQLdb()

__all__ = ['celery_app']
  1. users/tasks.py,代码:
from celery import shared_task
from ronglianyunapi import send_sms as sms
# 记录日志:
import logging
logger = logging.getLogger("django")

@shared_task(name="send_sms")
def send_sms(tid, mobile, datas):
    """异步发送短信"""
    try:
        return sms(tid, mobile, datas)
    except Exception as e:
        logger.error(f"手机号:{mobile},发送短信失败错误: {e}")


@shared_task(name="send_sms1")
def send_sms1():
    print("send_sms1执行了!!!")
  1. django中的用户发送短信,就可以改成异步发送短信了。

users/views,视图中调用异步发送短信的任务,代码:

from .tasks import send_sms
send_sms.delay(settings.RONGLIANYUN.get("reg_tid"),mobile, datas=(code, time // 60))

#完整代码
import random
from django_redis import get_redis_connection
from django.conf import settings
# from ronglianyunapi import send_sms
# from mycelery.sms.tasks import send_sms
from .tasks import send_sms

"""
/users/sms/(?P<mobile>1[3-9]\d{9})
"""
class SMSAPIView(APIView):
    """
    SMS短信接口视图
    """
    def get(self, request, mobile):
        """发送短信验证码"""
        redis = get_redis_connection("sms_code")
        # 判断手机短信是否处于发送冷却中[60秒只能发送一条]
        interval = redis.ttl(f"interval_{mobile}")  # 通过ttl方法可以获取保存在redis中的变量的剩余有效期
        if interval != -2:
            return Response({"errmsg": f"短信发送过于频繁,请{interval}秒后再次点击获取!", "interval": interval},status=status.HTTP_400_BAD_REQUEST)

        # 基于随机数生成短信验证码
        # code = "%06d" % random.randint(0, 999999)
        code = f"{random.randint(0, 999999):06d}"
        # 获取短信有效期的时间
        time = settings.RONGLIANYUN.get("sms_expire")
        # 短信发送间隔时间
        sms_interval = settings.RONGLIANYUN["sms_interval"]
        # 调用第三方sdk发送短信
        # send_sms(settings.RONGLIANYUN.get("reg_tid"), mobile, datas=(code, time // 60))
        # 异步发送短信
        send_sms.delay(settings.RONGLIANYUN.get("reg_tid"), mobile, datas=(code, time // 60))

        # 记录code到redis中,并以time作为有效期
        # 使用redis提供的管道对象pipeline来优化redis的写入操作[添加/修改/删除]
        pipe = redis.pipeline()
        pipe.multi()  # 开启事务
        pipe.setex(f"sms_{mobile}", time, code)
        pipe.setex(f"interval_{mobile}", sms_interval, "_")
        pipe.execute()  # 提交事务,同时把暂存在pipeline的数据一次性提交给redis

        return Response({"errmsg": "OK"}, status=status.HTTP_200_OK)
  1. 终端下先启动celery,在django项目根目录下启动。
cd ~/Desktop/luffycity/luffycityapi
# 1. 普通运行模式,关闭终端以后,celery就会停止运行
celery -A luffycityapi worker  -l INFO

# 2. 启动多worker进程模式,以守护进程的方式运行,不需要在意终端。但是这种运行模型,一旦停止,需要手动启动。
celery multi start worker -A luffycityapi -E --pidfile="/home/moluo/Desktop/luffycity/luffycityapi/logs/worker1.pid" --logfile="/home/moluo/Desktop/luffycity/luffycityapi/logs/celery.log" -l info -n worker1

# 3. 启动多worker进程模式
celery multi stop worker -A luffycityapi --pidfile="/home/moluo/Desktop/luffycity/luffycityapi/logs/worker1.pid"
  1. 还是可以在django终端下调用celery的
$ python manage.py shell
>>> from users.tasks import send_sms1
>>> res = send_sms1.delay()
>>> res = send_sms1.apply_async(countdown=15)
>>> res.id
'893c31ab-e32f-44ee-a321-8b07e9483063'
>>> res.state
'SUCCESS'
>>> res.result

celery work的参数

Usage: celery worker [OPTIONS]

  Start worker instance.

  Examples
  --------

  $ celery --app=proj worker -l INFO
  $ celery -A proj worker -l INFO -Q hipri,lopri
  $ celery -A proj worker --concurrency=4
  $ celery -A proj worker --concurrency=1000 -P eventlet
  $ celery worker --autoscale=10,0

Worker Options:
  -n, --hostname HOSTNAME        用于在多个 worker 进程中区分。如果不指定该参数,则会自动生成一个唯一的名称。
  -D, --detach                    Start worker as a background process.
  -S, --statedb PATH              Path to the state database. The extension
                                  '.db' may be appended to the filename.
  -l, --loglevel [DEBUG|INFO|WARNING|ERROR|CRITICAL|FATAL]     日志级别
                                  Logging level.
  -O, --optimization [default|fair]
                                  Apply optimization profile.
  --prefetch-multiplier <prefetch multiplier>
                                  Set custom prefetch multiplier value for
                                  this worker instance.

Pool Options:
  -c, --concurrency <concurrency>  并发数,即同时处理的任务数。默认为服务器 CPU 核数的 2 倍。
  -P, --pool [prefork|eventlet|gevent|solo|processes|threads|custom]
                                  数用于指定 celery worker 进程池的类型,即使用哪种方式来管理 worker 进程。该参数接受一个字符串值,可选值包括:
              prefork:使用 prefork 进程池,即每个 worker 进程在启动时就会预先创建好,并在需要时直接使用。这种方式可以提高启动速度,但会占用较多的内存。
              eventlet:使用 eventlet 协程池,即将每个 worker 进程变成一个协程,通过事件循环机制来实现并发处理。这种方式可以大幅降低内存占用,但可能会影响性能。
              gevent:使用 gevent 协程池,与 eventlet 类似,但使用 gevent 库来实现协程,性能更高。
              solo:不使用进程池,每个任务都在主进程中运行。这种方式适用于只有少量任务的场景,但不适合高并发场景。
  -E, --task-events, --events     Send task-related events that can be
                                  captured by monitors like celery events,
                                  celerymon, and others.
  --time-limit FLOAT                单个任务的最大执行时间,超过该时间则会被 worker 强制终止。
  --soft-time-limit FLOAT        单个任务的软时间限制,当任务超过该时间时,worker 会发出警告,但仍会让任务继续执行
  --max-tasks-per-child INTEGER   每个 worker 进程最多处理多少个任务后重启,防止因为内存泄漏等问题导致的进程异常。
  --max-memory-per-child INTEGER  参数用于限制每个 worker 进程最大占用的内存大小,一旦超过该限制,worker 进程会被强制重启,以避免内存泄漏等问题导致的进程崩溃。

该参数接受一个整数值,表示每个 worker 进程可以占用的最大内存大小,单位为 MB。

Queue Options:
  --purge, --discard
  -Q, --queues COMMA SEPARATED LIST
  -X, --exclude-queues COMMA SEPARATED LIST
  -I, --include COMMA SEPARATED LIST

Features:
  --without-gossip
  --without-mingle
  --without-heartbeat
  --heartbeat-interval INTEGER
  --autoscale <MIN WORKERS>, <MAX WORKERS>

Embedded Beat Options:
  -B, --beat
  -s, --schedule-filename, --schedule TEXT
  --scheduler TEXT

Daemonization Options:
  -f, --logfile TEXT  Log destination; defaults to stderr
  --pidfile TEXT
  --uid TEXT
  --gid TEXT
  --umask TEXT
  --executable TEXT

posted @ 2023-06-19 18:23  零哭谷  阅读(174)  评论(0编辑  收藏  举报