Celery实现异步任务和定时任务

概念

Celery:是一个分布式队列的管理工具。

提供了简便的接口管理任务队列

结构

生产者 user: 发布任务
任务队列 broker: 存储任务,常见的有redis,rabbitMQ
职程(工作者) worker: 处理任务
结果 backend 存储结果

使用

  1. 首先需要一个redis服务器用来做消息队列和存储任务的结果,这个redis可以在本地,也可以在远程,他可能是一个redis服务,也可能是redis集群。
    sudo apt-get install redis
    vim /etc/redis/redis.conf
    将保护模式关闭(protected mode: no), 注释掉(bind)。
    cd /usr/bin/
    ./redis-server /etc/redis/redis.conf

  2. 需要一个被发布的任务
    pip install celery 安装celery,注意版本可能与环境不切合,或者版本就是异常的,pip install --upgrade https://github.com/celery/celery/tarball/master,不是自己代码问题就把版本调低
    pip install django-redis redis支持

from celery import Celery
import time
app = Celery('tasks',broker='redis://ip:6379/0',backend='redis://ip:6379/1')

@app.task
def add(x,y):
	time.sleep(10)
	return x+y
  1. 需要一个任务的触发器,调用这个任务
# tasks.py
from somewhere import add
ret = add.delay(1,2)
# ret 就是一个id,可以凭借这个id去获取状态,如果是successful就可以获取结果信息。
  1. 启动Celery
    folder是文件夹的名字,tasks.py是文件夹下的触发器。
    celery worker -A folder.tasks -l info

取异步请求的结果

# result.py 等执行完上个程序在执行这个
from celery.result import AsyncResult
from folder.tasks import app  # 创建的Celery实例拿过来
# 上面的程序得到的ret是AsyncResult对象,调用.id方式得到他的id。
ret = AsyncResult(id =ret.id,app=app)
print(ret.state)
if ret.successful():
	print(ret.get())

ret.state
PENDING: 等待
STARTED: 开始
SUCCESS: 成功
RETRY: 重试
FAILURE: 失败
REVOKED:取消

遇到的问题:

连接redis的时候:目标服务器拒绝访问?
将防火墙关闭,或者打开6379端口,使用windows命令telnet ip 6379 还是无法连接,将redis的配置文件中的pretected mode设置为no,将bind取消注释,或者绑定本机ip,然后重启redis,ps aux|grep redis检查redis,如果是127.0.0.1:6379只能在本机使用,如果是 * :6379,或者本机ip:6379 成功,再试telnet ip 6379,空白表示成功。


Celery出现语法错误?
Celery版本与Python版本的冲突,主要是async关键字,可以试着将它改为别的;或者安装低版本的:pip install --upgrade https://github.com/celery/celery/tarball/master


Celery任务一直在pending,不执行?

  1. 听掉celery服务,这时就提示了,有几个任务已经存储(8),在redis没有数据的情况下,就简单的flushdb
  2. 在Windows中运行celery的时候加上 --pool=solo, -Pthreads -c 6效果更好-c指的是并发数,在这里是线程的个数,还可以是’eventlet’ 、‘gevent’,大概率是win10版本的问题。
  3. 重启redis也可能会解决问题,希望不大,在这不管用。

进阶

Celery多任务结构

  1. 创建一个python packagecelery_tasks ,创建一个celery.py文件用来创建celery配置文件,创建tasks01.py,tasks02.py用来定义不同类别的任务。
from celery import Celery
app = Celery('task',broker='redis://ip:6379/0',backend='redis://ip:6379/1',
			include('celery_tasks.tasks01','celery_tasks.tasks02')
			# include 包含的是绝对路径
			)
app.conf.timezone = 'Asia/Shanghai'
app.conf.enable_utc = False
# tasks01.py  tasks02.py
from celery_tasks.celery import app
@app.task
def add(x,y):
	return x+y

安装eventlet模块: pip install eventlet
运行celery:celery worker -A celery_tasks -l info -P eventlet

# ret.py
from celery.tasks.tasks01 import add

add.delay(1,5)

运行这个函数,celery收到了任务,但是没有执行,原因未知,还是之前的样子,使用多线程threads或者单进程solo就可以运行。可能跟系统有关系

定时任务

1. 简单:
delay()在使用时更加的简单,将参数放里面就可以执行实时任务。
apply_async():有更多的参数:args=[] ,eta=国标时间
定时:

from datetime import datetime
from datetime import timedelta
v1 = datetime(2020, 3, 11, 16, 19, 00)  # 2020年3月11日16点19分00秒
v2 = datetime.utcfromtimestamp(v1.timestamp())  # 转换为国标时间
ret = add.apply_async(args=[1,2],eta=v2)

延时:

from datetime import datetime 
from datetime import timedelta
ntime = datetime.now()
utc_time = datatime.utcfromtimestamp(ntime.timestamp)
delta = timedelta(seconds = 6)
ret = add.apply_async(args=[1,2],eta=(dalta+utc_time))

2. 多任务:

# celery.py
from datetime import timedelta
from celery import Celery
from celery.schedules import crontab

app = Celery('tasks', broker='redis://ip:6379/1', backend='redis://ip:6379/2', include=[
    'celery_tasks.task01',
    'celery_tasks.task02',
])

app.conf.timezone = 'Asia/Shanghai'
app.conf.enable_utc = False

cel.conf.beat_schedule = {
    # 名字随意命名,但是最好见名知意
    'add-every-10-seconds': {
        # 执行tasks1下的test_celery函数
        'task': 'celery_tasks.task01.send_email',
        
        # 'schedule': 1.0,  # 和timedelta(seconds=1) 相同
        'schedule': timedelta(seconds=6),
        # 'schedule': crontab(minute="*/1"),
        
        # 传递参数
        'args': (1,2)
    },
    # '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': ('张三',)
    # },
}
  • 启动beat任务周期性的向任务队列添加任务 celery beat -A celery_tasks:会读配置信息
  • 启动celery celery -A celery_tasks worker -l info
    beat进程周期性的把任务放到任务队列redis中,而启动worker时就从任务队列取出任务并发执行,两者互不干扰

带来的问题:历史遗留
之前执行过这两条命令,然后停掉。
再次执行worker时,应该等待任务,但是却执行了很多任务

  • 原因:beat进程插入任务太快,没有执行完就遗留在了redis中,再次开启worker时,会立刻并发执行。
  • 解决:连接redis,找到key为’celery’的键,他的值是一个列表(redis-cli可以用type查看),
    删除celery的键即可。
  • 预防:先开启worker,在开启beat,先关闭beat,在关闭worker。

Django中使用celery

首先创建一个python package my_celery,下面有多个python package用来为任务分类,smspackage email package,在任务包中必须有tasks.py文件用于定义任务,必须是tasks.py,在最大的包 my_celery中还有config.py:Celery的配置信息。main.py:创建celery对象,启动Django环境,扫描任务。
Celery实现异步任务和定时任务_第1张图片

# config.py
broker = 'redis://ip:6379/0'
backend = 'redis://ip:6379/1'
# main.py
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celeryPros.settings.dev')

# import django
# # os.environ.setdefault('DJANGO_SETTINGS_MODULE','dailyfresh.settings')
# # django.setup()


from celery import Celery
app = Celery('demo')

-------------两种方式获取配置信息-----------------------
app.config_from_object("mycelery.config")
app.config_from_object('django.conf:settings')


app.autodiscover_tasks(['my_celery.sms',],related_name='tasks')  # 默认就是tasks
# 里面是package的列表,默认扫描tasks,当指定related_name时,可以是其他名字

  • 也可以扫描app中的任务,将app package加里面,如果有tasks.py就可以自动扫描
# tasks.py
from my_celery.main import app

@app.task
def add(x,y):
	return x+y
  • 异步任务调用时,直接导入包然后调用delay方法
  • 定时任务,apply_async(agrs=[1,2],eta = time)

消息中间件:因为网络抖动的原因放入多次数据,可以通过set保证幂等性,或者使用setnx命令保证幂等性,或者使用id保证唯一性。

你可能感兴趣的:(Celery)