项目背景
异步任务队列的主要应用场景在:
- 无须实现响应,性能占用较大,任务处理时间较长的任务,如占用网络性能的发送邮件,占用IO性能的视频处理。
- 按时发布的定时任务,如定期对服务器的检查,对当天网站的监测分析。
Celery组件简介
Celery(芹菜)是一个Python编写的异步任务队列/基于分布式消息传递的作业队列。用于处理数以百万计的任务。
- 三大组件: 消息中间件 (Broker),任务执行单元(Worker)和 任务执行结果存储(Backend)。
- Celery支持RabbitMQ、Redis、ZoopKeeper等作为Broker,而对这些消息队列的抽象,都是通过Kombu实现的
Flower是基于web的 监控 和管理Celery的实时监控工具。
Redis(Remote Dictionary Server)远程字典数据服务的缩写,由意大利人开发的是一款内存高速缓存数据库
link
项目代码
项目所需安装的软件
$ pip install celery
$ pip install flower
#源码安装Redis软件(pip install -i https://pipy.douban.com/simple/ celery)
项目结构
celery/
├── config.py #项目配置文件:存储配置信息
├── main.py #主程序代码
└── tasks.py #异步要执行的任务程序:发送邮件,发送短信
项目代码
main.py
from celery import Celery # Celery用python编写的异步任务队列
# include指定任务存储文件的位置
app = Celery('app',include=['tasks']) # include=[要处理的任务]
# 加载配置文件
app.config_from_object('config')
if __name__ == '__main__':
# 启动异步任务
app.run()
# task.py
import time
from main import app
@app.task # 通过装饰器把任务和celery队列绑定在一起,将任务存储在消息队列中,消息队列存储在redis中
def send_email(mail): #mail时以恶字典
"""模拟发送邮件,便于以后真实发送"""
print('sending mail to %s.....' %(mail['to']))
time.sleep(2)
print('mail end')
return 'send mail successful'
@app.task
def add(x,y):
"""模拟计算机函数"""
print('add:%d+%d' %(x,y))
time.sleep(0.5)
return x+y
====
# config.py
from datetime import timedelta
from celery.schedules import crontab
# 使用Redis作为消息代理
BROKER_URL = 'redis://127.0.0.1:6379/3' # 将消息中间件存储在本地redis数据库中,执行任务是要把redis服务器先启动
# 使用Redis作为任务执行结果存储数据库,也可以是MySQL数据库
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/4'
# 任务序列化和反序列化格式为msgpack(类似json格式) celery任务执行时的传输方式,‘msgpack’一种可移植的数据格式
CELERY_TASK_SERIALIZER = 'msgpack'
# 任务结果序列化存储格式为JSON(可读性更好)
CELERY_RESULT_SERIALIZER = 'json'
# 任务过期时间
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 #
CELERY_ACCEPT_CONTENT = ['json', 'msgpack'] # 任务接收内容的格式
# 设置时区
CELERY_TIMEZONE = 'Asia/Shanghai'
# 配置定时任务
CELERYBEAT_SCHEDULE = {
'send-every-3-seconds': {
# 执行的任务名称
'task': 'tasks.send_email',
# 定时任务设置(每隔5秒)
'schedule': timedelta(seconds=5),
# crontab定时任务(同Linux定时任务crontab配置)
# 'schedule': crontab(hour=16, minute=30),
'args':({'to':'hello@qq.com'},)
},
# 定时任务的名称
'add-every-10-seconds':{
'task':'tasks.add',
'schedule':timedelta(seconds=10),
'args':(1,2)
}
}
- 启动celery程序
celery -A tasks worker -B --loglevel=info # -A指定任务,-B显示高于info的日志信息
因为只是开启了celery队列,并没有产生任务,因此不会有执行结果
-
交互式环境异步任务
Celery 产生任务的方式有两种- 发布者发布任务
- 任务调度按时发布定时任务
$ ipython
In [1]: from tasks import send_email
In [2]: r = send_email.delay({‘to’:‘hello@qq.com’})
In [3]: r.status
Out[3]: ‘SUCCESS’
In [5]: r.ready()
Out[5]: True
r.result # 查看任务执行的结果
-
批量异步任务
Celery 产生任务的方式有两种- 发布者发布任务
- 任务调度按时发布定时任务
方法一:在交互式界面中传递任务
import time
from tasks import send_email, add
#delay()用来调用任务, 返回任务执行结果
answer = send_email.delay(dict(to=‘windard@windard.com’))
while True:
print(‘wait for ready’)
#任务完成,跳出循环.
if answer.ready():
break
time.sleep(0.5)
print(answer.get())
方法二:在配置文件中进行配置定时任务
CELERYBEAT_SCHEDULE = {}
“”"
crontab方式
分钟 小时 日 月 星期
*/5 * * * * 每五分钟执行xxx
00 */2 * * * 每隔两小时执行xxx
00 12 */2 * * 每隔两天中午12点执行xxx
00 12 1 */2 * 每隔两个月1号执行xxx
00 23 * * 6 每周六晚上11点执行xxx
“”"
#定时任务的名称
CELERYBEAT_SCHEDULE = {
‘add-every-10-seconds’:{
‘task’:‘tasks.add’, # 要执行的任务
‘schedule’:timedelta(seconds=10), # 每10秒执行一次任务
# crontab定时任务(同Linux定时任务crontab配置)
# ‘schedule’: crontab(hour=16, minute=30),
‘args’:(1,2) # 要执行的任务需要传入的参数,以元组方式
},# 可以往后增加多个定时任务
}
Flower监控Celery
$ celery -A main flower --port=5556
访问网站: http://127.0.0.1:5556
邮件报警
邮件信息传递工作原理
- SMTP协议: Simple Mail Transfer Protocol, 是一种提供可靠且有效的电子邮件传输的协议。SMTP建立在FTP文件传输服务上的一种邮件服务,主要用于系统之间的邮件信息传递,并提供有关来信的通知。
- POP3协议: Post Office Protocol - Version 3, 主要用于支持使用客户端远程管理在服务器上的电子邮件。
MIME是多功能Internet邮件扩展,设计的最初目的是为了在发送电子邮件时附加多媒体数据,让邮件客户程序能根据其类型进行处理。
常见的MIME类型(通用型): - 超文本标记语言: .html text/html
- xml文档 : .xml text/xml
- PDF文档: .pdf application/pdf
不含附件邮件邮件报警代码
SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。
Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件。
# 设置服务器,用户名、口令以及邮箱的后缀
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
smtp_server = "smtp.163.com"
from_username = '西部开源技术中心'
mail_user = "yw17392517656@163.com"
# 是开启smtp的授权吗不是真实的密码。
mail_password = "yw17392517656"
# 邮件主题的前缀
mail_prefix = "[运维开发部]-"
def send_email(to_addrs, subject, msg):
try:
# 将要发送的文本信息做MIME封装
msg = MIMEText(msg)
# 格式化发件人名称
msg['From'] = formataddr([from_username, mail_user])# 发送者的信息
msg['To'] = to_addrs # 接收者的信息
msg['Subject'] = mail_prefix + subject
# 1. 实例化smtp对象
server = smtplib.SMTP()
# 2. 连接邮件服务器
server.connect(smtp_server)
# 3. 登录
server.login(mail_user, mail_password)
# 4. 发送邮件内容
server.sendmail(mail_user, to_addrs, msg.as_string())
# 5. 关闭连接
server.quit()
except Exception as e:
print(str(e))
return False
else:
return True
if __name__ == '__main__':
send_email('741047561@qq.com','寒假作业', '请与2月2日之前提交')
print("发送成功.....")
注意:用qq邮箱和163邮箱发送邮件有所不同,实例化smtp对象时要做如下的处理:
含附件邮件报警代码
# 设置服务器,用户名、口令以及邮箱的后缀
import os
import smtplib
from email.mime.application import MIMEApplication
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart # 支持发送附件和正文信息
from email.utils import formataddr
# 设置服务器,用户名、口令以及邮箱的后缀
smtp_server = "smtp.163.com"
from_username = '西部开源技术中心'
mail_user = "yw17392517656@163.com"
# 是开启smtp的授权吗不是真实的密码。
mail_password = "yw17392517656"
# 邮件主题的前缀
mail_prefix = "[运维开发部]-"
def format_attach(file):
"""
附件处理
:param file: 文件对象
:return:
"""
filename = file.name # 获取文件对象名称
# 分割文本信息:分离文件名和后缀名并以元组方式返回 hello.png ==> ('hello','png')
base, ext = os.path.splitext(filename)
# 读取文件的时候必须知道类型是哪种扩展png、pdf
# 通过MIMEApplication封装附件,需要知道拓展名
attach = MIMEApplication(file.read(), _subtype=ext)
# 封装的时候添加头部信息,告诉解释器这是附件信息
attach.add_header('content-disposition', 'attachment', filename=filename)
return attach # 返回封装好的附件
def send_email(to_addrs:str , subject:str, content:str, attaches:list=None)->bool:
"""
发送邮件
:param to_addrs: 邮件接收人
:param subject: 邮件标题
:param content: 邮件正文内容
:return bool
"""
# 将要发送的文本信息做MIME封装 MIME是一种发送邮件的数据格式
# msg = MIMEText(content) # MIMEText将文本信息封装为MIME格式
msg = MIMEMultipart(_subtype='alternative')
# 格式化发件人名称
msg['From'] = formataddr([from_username, mail_user]) # 把你的邮箱来一个别名
msg['To'] = to_addrs
msg['Subject'] = mail_prefix + subject
msg.attach(MIMEText(content)) # 把报文和正文绑定在一起,将正文封装进报文里边
# 把附件封装进报文
if attaches:
for attach in attaches: # 遍历每个附件
if os.path.exists(attach): # 判断附件是否存在
# 绑定在一起的不是附件的名称,而是附件的内容
with open(attach,'rb') as f:
# ‘rb’以二进制的方式去读取文件的内容因为文件可能是图片
result = f.read()
print(result)
msg.attach(format_attach(f))
else:
print('附件%s不存在' %(attach))
try:
# 1. 实例化smtp对象
server = smtplib.SMTP() # smtplib简单邮件协议的封装
# 2. 连接邮件服务器
server.connect(smtp_server)
# 3. 登录
server.login(mail_user, mail_password)
# 4. 发送邮件内容
server.sendmail(mail_user, to_addrs, msg.as_string()) # 将封装的信息转化成字符串发送出去
# 5. 关闭连接
server.quit()
except Exception as e:
print(str(e))
return False
else:
return True
if __name__ == '__main__':
result = send_email('741047561@qq.com','寒假作业', '请与2月2日之前提交',attaches=['/etc/passwd'])
if result:
print("发送成功.....")
else:
print('发送失败')