1.1 Celery介绍
# Celery
1.celery 是一个用于实时处理和任务调度的分布式任务队列 同时提供操作和维护分布式系统所需的工具
2.celery 封装了操作常见任务队列的各种操作 能够快速进行任务队列的使用与管理
# Celery特征
1.celery 可选高并发执行任务模式:多进程 Eventlet Gevent
2.celery 提供错误处理机制
3.celery 支持多种消息代理(rabbitmq)和存储后端(redis)
4.celery 使用管理后台和命令对任务进行维护管理
5.celery 可查看定时任务的执行情况
# 生产者与消费者数据传输需要将数据进行序列化&&反序列化操作
1.通过特定模块实现-msgpack(二进制的类json序列化 推荐) pickle json yaml
2.安装celery工具集成包 pip install "celery[librabbitmq, redis, msgpack, gevent]"
# 小结:Celery核心组件: celery客户端(发布任务) + Celery worker(运行任务) + 消息代理(客户端与worker通信方式)

1.2 celery安装
# 默认python 环境
pip install -U Celery -i https://mirrors.aliyun.com/pypi/simple/
或
pip install "celery[librabbitmq, redis, msgpack, gevent]" -i https://mirrors.aliyun.com/pypi/simple/
1.3 celery 使用
###----task.py 发布任务
from celery import Celery
# 初始化默认celery
app = Celery('task', # 模块名
broker='amqp://guest@localhost', # 消息代理rqbbitmq(本地已安装rabbitmq或docker容器单开)
backend="redis://localhost:6379//0") # 结果存储redis/0(本地已安装redis或docker容器单开)
# 发布的任务
@app.task
def cal(x, y):
return x + y
#----worker.py 执行任务(可远程可本地)
from task import cal
###----worker.py 执行任务
ret = cal.delay(15, 10) # 携带参数较少
print('计算结果:', ret.get(timeout=1))
# cal.apply_async(args=(15, 10), kwargs={'a':1, 'b': 2}) # 可携带更多参数
###---terminal终端执行命令
celery -A task worker -l info (window10 加上参数-P threads)
-A:指定celery实例位置
-l:指定worker输出日志等级
-Q:可指定队列
### 小结:
1.app.task装饰cal函数成一个Task实例;cal.delay函数将task实例序列化后 发送到rabbitmq;
2.task.py执行这个过程中创建了一个Exchagne(direct):celery交换机;创建了一个queue(routing_key=celery):celery队列;
3.保证app.task装饰器在最外层
1.4 celery 配置
# 修改celery默认配置
1.app.conf.task_serializer = 'json' 或
app.conf.update(
task_serializer='json', # 任务序列化格式
accept_content=['json'], # Ignore other content
result_serializer='json', # 存储结果序列化格式
timezone='Europe/Oslo', # 时区
enable_utc=True,
)
2.通过配置文件初始化celery
#----celery_config.py(celery4+版本 可大小写)
BROKER_URL = "redis://localhost:6379/1" # broker(消息队列)地址(celery:4+)
CELERY_RESULT_BACKEND = "redis://localhost:6379/0" # 结果存储地址
CELERY_TASK_SERIALIZER = "json" # 任务序列化方式
CELERY_RESULT_SERIALIZER = "json" # 任务执行结果序列化方式
CELERY_TASK_RESULT_EXPIRES = 60 # 任务过期时间(second)
CELERY_ACCEPT_CONTENT = ['json', ] # 指定任务接受的内容类型-序列化
# 测试配置文件是否格式正确
python -m celery_config
# 加载配置文件
app = Celery(__name__, include=['task'])
app.config_from_object('celery_config')
# 配置文件详情:https://docs.celeryproject.org/en/latest/userguide/configuration.html#configuration
# 常用配置文件信息
# 指定消息代理
BROKER_URL = 'amqp://username:passwd@host:port/虚拟主机名'
# 指定结果的接受地址
CELERY_RESULT_BACKEND = 'redis://username:passwd@host:port/db'
# 指定任务序列化方式
CELERY_TASK_SERIALIZER = 'msgpack'
# 指定结果序列化方式
CELERY_RESULT_SERIALIZER = 'msgpack'
# 任务过期时间,celery任务执行结果的超时时间(s)
CELERY_TASK_RESULT_EXPIRES = 60
# 指定任务接受的序列化类型.
CELERY_ACCEPT_CONTENT = ["msgpack"]
# 任务发送完成是否需要确认,这一项对性能有一点影响
CELERY_ACKS_LATE = True
# 压缩方案选择,可以是zlib, bzip2,默认是发送没有压缩的数据
CELERY_MESSAGE_COMPRESSION = 'zlib'
# 规定完成任务的时间
CELERYD_TASK_TIME_LIMIT = 5 # 在5s内完成任务,否则执行该任务的worker将被杀死,任务移交给父进程
# celery worker的并发数,默认是服务器的内核数目,也是命令行-c参数指定的数目
CELERYD_CONCURRENCY = 4
# celery worker 每次去rabbitmq预取任务的数量
CELERYD_PREFETCH_MULTIPLIER = 4
# 每个worker执行了多少任务就会死掉,默认是无限的
CELERYD_MAX_TASKS_PER_CHILD = 40
# 设置默认的队列名称,如果一个消息不符合其他的队列就会放在默认队列里面,如果什么都不设置的话,数据都会发送到默认的队列中
CELERY_DEFAULT_QUEUE = "default"
# 设置详细的队列
CELERY_QUEUES = {
"default": { # 指定的默认队列
"exchange": "default",
"exchange_type": "direct",
"routing_key": "default"
},
"topicqueue": { # 通配符routing_key匹配队列 凡是topic开头的routing key都会被放到这个队列
"routing_key": "topic.#",
"exchange": "topic_exchange",
"exchange_type": "topic",
},
"task_eeg": { # 设置广播类型交换机
"exchange": "tasks",
"exchange_type": "fanout",
"binding_key": "tasks",
},
}
1.5 celery 注意点
# 指定任务使用特定队列
from kombu import Queue
app.conf.task_default_queue = 'default'
app.conf.task_queues = (
Queue('default', routing_key='task.#'),
Queue('web', routing_key='web.#'),
)
app.conf.task_default_exchange = 'topic_task'
app.conf.task_default_exchange_type = 'topic'
app.conf.task_default_routing_key = 'task.default'
----terminal
celery -A celery_app worker -Q web
2. 实例操作
2.1 短信异步服务
# 准备工作
# 1.redis服务开启
# 2.rabbitmq服务开启(docker run --rm -it --name mq -p 5672:5672 -p 15672:15672 rabbitmq:management)
# 3.阿里云短信服务 模板 签名 ak ak-sercet获取
# 4.pip 模块
#----requirements.txt
aliyun-python-sdk-core-v3==2.13.11
amqp==2.6.1
billiard==3.6.3.0
celery==4.4.7
importlib-metadata==1.7.0
jmespath==0.10.0
kombu==4.6.11
pika==1.1.0
pytz==2020.1
redis==3.5.3
vine==1.3.0
zipp==3.1.0
#----
#----task2.py
from celery import Celery
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest
# 创建celery实例
app = Celery('task2',
broker="amqp://guest@localhost//",
backend="redis://localhost:6379/1")
@app.task
def send(send_phone, code):
client = AcsClient('accessKeyId', 'accessSecret', 'cn-hangzhou')
request = CommonRequest()
request.set_accept_format('json')
request.set_domain('dysmsapi.aliyuncs.com')
request.set_method('POST')
request.set_protocol_type('https') # https | http
request.set_version('2017-05-25')
request.set_action_name('SendSms')
request.add_query_param('RegionId', "cn-hangzhou")
request.add_query_param('PhoneNumbers', send_phone)
request.add_query_param('SignName', "签名")
request.add_query_param('TemplateCode', "SMS_模板")
request.add_query_param('TemplateParam', "{\'code\':\'%s\'}" % code)
response = client.do_action(request)
response = str(response, encoding="utf-8")
return response
# if __name__ == '__main__':
# res = send("手机号", "短信验证码")
# print(res)
#----worker2.py
#coding=utf-8
from celery import Celery
# 远程需要配置celery信息
app = Celery("worker2",
broker="amqp://guest@localhost//",
backend="redis://localhost:6379/1")
app.send_task('task2.send', args=["手机号", "短信验证码"])
# win10终端
#$celery -A task2 worker -l info -P threads

2.2 邮件异步服务
#----task.py
#encoding=utf8
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.utils import parseaddr, formataddr
from celery import Celery
# 邮件配置host port
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
# 发送方的邮箱
EMAIL_HOST_USER = 'yyy@163.com'
# 163邮箱授权密码
EMAIL_HOST_PASSWORD = '授权密码'
# 发件人头信息
EMAIL_FROM = '测试邮件发送<yyy@163.com>'
app = Celery(
'task',
#broker='amqp://guest@localhost//',
broker='redis://localhost:6379/1',
backend='redis://localhost:6379/2'
)
# 头信息中文处理
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))
# 任务
@app.task
def send(receivers, content, subject, mail_from=None):
# 编辑邮件信息
message = MIMEText(content) # 邮件正文内容
message['from'] = _format_addr(EMAIL_FROM if not mail_from else mail_from) # 发件人标题
message['to'] = ','.join(receivers) # 接收方
message['subject'] = Header(subject, 'utf-8') # 主题
# 第三方登录163 发送邮件
smtp_obj = smtplib.SMTP()
smtp_obj.connect(EMAIL_HOST, EMAIL_PORT)
smtp_obj.set_debuglevel(1)
smtp_obj.login(EMAIL_HOST_USER, EMAIL_HOST_PASSWORD)
smtp_obj.sendmail(EMAIL_FROM, receivers, message.as_string())
smtp_obj.quit()
if __name__ == '__main__':
send(['xxx@qq.com'], '你好,邮件发送测试成功', '测试邮件发送')
#----worker.py
#coding=utf8
from celery import Celery
app = Celery(
'worker',
#broker='amqp://guest@localhost//',
broker='redis://localhost:6379/1',
backend='redis://localhost:6379/2'
)
# 执行任务
app.send_task('task.send', args=[['xxx@qq.com'], '你好,邮件发送测试成功', '测试邮件发送'])
# terminal
# celery -A task worker -l info -P threads

5. 帮助文档