文章目錄
異步:客戶端發送消息無需等待服務端回覆之後才能發出第二個消息,可以一直髮,來讓服務端處理。
異步任務隊列的主要應用場景:
- 無須實現響應,性能佔用較大,任務處理時間較長的任務,如佔用網絡性能的發送郵件,佔用IO性能的視頻處理。
- 按時發佈的定時任務,如定期對服務器的檢查,對當天網站的監測分析(有沒有被黑客攻擊)。
任務分爲兩種:暫時無法處理的任務,解決了再通知;無法立刻執行的任務,
各個組件簡介
Celery(芹菜) 是一個Python編寫的異步任務隊列/基於分佈式(任務分配開執行)消息傳遞的作業隊列。用於處理數以百萬計 的任務。
類似於生產者消費者模型,消息中間件Broker用於存儲生產的任務以及任務的分發,監控到隊列中有任務就通過celery Worker執行任務;消費者從消息中間件拿任務,最後任務是否執行成功存儲到Backend中。
Flower是基於web的監控和管理Celery的實時監控工具。實時監控,顯示哪些任務執行成功哪些任務執行失敗。
Redis內存高速緩存數據庫,由於IP端口可以共享,可以讓不同服務器訪問隊列,結果存儲到redis數據庫中,因此要啓動redis。
小案例:windows下利用celery實現簡單的任務隊列
Celery主類,進行任務最開始的指派與執行控制,保存爲main.py
from celery import Celery
app = Celery('app', include=['tasks']) # include指定任務存儲文件位置。名字爲app,執行任務爲tasks
app.config_from_object('config') # 加載配置文件
if __name__ == '__main__': #程序沒有被導入執行的內容:啓動異步任務
app.start()
配置文件保存爲config.py
from datetime import timedelta
from celery.schedules import crontab
# 使用Redis作爲消息代理,任務存儲到消息隊列中,消息隊列存儲到redis中。
BROKER_URL = 'redis://127.0.0.1:6379/10' #使用自己的redis就寫自己的IP,1爲消息隊列存儲的位置
#使用Redis作爲任務執行結果存儲數據庫,也可以是MySQL數據庫
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/11'
# 任務序列化和反序列化格式爲msgpack(類似json格式)
CELERY_TASK_SERIALIZER = 'msgpack' #任務執行傳輸過程的格式爲msgpack
# 任務結果序列化存儲格式爲JSON(可讀性更好)
CELERY_RESULT_SERIALIZER = 'json'
#任務過期時間 :任務沒有執行成功,不能一直等待,因此需要設置任務過期時間,在此時間內任務沒有執行成功表示任務執行失敗
CELERY_TASK_RESULT_EXPIRES = 60*60*24
CELERY_ACCEPT_CONTENT = ['json', 'msgpack']
#設置時區
CELERY_TIMEZONE = 'Asia/Shanghai'
# 配置定時任務2個:'send-every-5-seconds'與'add-every-2-sec0nds'
CELERYBEAT_SCHEDULE = {
'send-every-5-seconds': {
# 執行的任務名稱
'task': 'tasks.send_email',
# 定時任務設置(每隔5秒)
'schedule': timedelta(seconds=5),
# crontab定時任務(同Linux定時任務crontab配置)
#'schedule': crontab(hour=16, minute=30), #每天下午16:30執行此任務
'args':({'to':'[email protected]'},) #執行任務需要的參數
},
'add-every-2-sec0nds':{
'task':'tasks.add',
'schedule': timedelta(seconds=2),
'args':(1,2)
}
}
模擬任務的文件保存爲tasks.py
import time
from main import app
@app.task #任務放入celery消息隊列中執行,因此給下面兩個任務分別加上裝飾器
def send_email(mail):
"""模擬發送郵件"""
print('sending mail to %s......' % (mail['to']))
time.sleep(2)
print('mail end')
return 'send mail successful'
@app.task
def add(x, y):
#模擬計算的函數
time.sleep(0.5)
return x+y
交互式環境下執行異步任務:
Celery 產生任務的方式有兩種:
- 發佈者發佈任務
- 任務調度按時發佈定時任務
所有背景工作做好後,需要啓動redis和celery程序
terminal1下(當前項目文件目錄下):啓動celery程序、啓動redis
- 啓動celery:
windows:celery -A tasks worker --pool=solo -l info
linux:celery -A tasks worker -B --loglevel=info #worker:任務單元,指定任務、日誌級別
- 啓動redis:
linux下: redis-server /etc/redis.conf
Windows下: redis-server redis.windows.conf
terminal2下(當前項目文件目錄下):
ipython
-->from tasks import add,send_email
導入任務模塊 -->
s =send_email.delay({'to':'[email protected]'})
#delay()用來調用任務, 返回任務執行結果 --> s.status
查看任務執行狀態
發佈者發佈任務執行結果:
- Terminal2下:
In [3]: s = send_email.delay({'to':'[email protected]'})
In [4]: s.status
Out[4]: 'SUCCESS'
- Terminal1下:
[2020-02-12 12:34:11,710: INFO/MainProcess] Received task: tasks.send_email[bbb17f8c-c342-4ca4-bf97-5f843e95e2de]
[2020-02-12 12:34:11,712: WARNING/MainProcess] sending mail to [email protected]......
[2020-02-12 12:34:13,725: WARNING/MainProcess] mail end
[2020-02-12 12:34:13,725: INFO/MainProcess] Task tasks.send_email[bbb17f8c-c342-4ca4-bf97-5f843e95e2de] succeeded in 2.0159999998286366s: 'send mail successful'
任務調度按時發佈定時任務執行結果:
celery異步作windows定時任務:如果有定時任務的話,還需要啓動心跳 beat
Terminal1:celery beat -A tasks
#啓動 Celery Beat 進程,定時將任務發送到 Broker
Terminal2:celery -A tasks worker --loglevel=info
[2020-02-12 13:13:34,524: INFO/MainProcess] Received task: tasks.send_email[d10cbc69-a871-4444-965f-15a1feda29ac]
[2020-02-12 13:13:34,524: WARNING/MainProcess] sending mail to [email protected]......
[2020-02-12 13:13:36,537: WARNING/MainProcess] mail end
[2020-02-12 13:13:36,537: INFO/MainProcess] Task tasks.send_email[d10cbc69-a871-4444-965f-15a1feda29ac] succeeded in 2.0159999998286366s: 'send mail successful'
[2020-02-12 13:13:36,537: INFO/MainProcess] Received task: tasks.add[42789af3-e4e0-4679-aa61-b449573c7acd]
[2020-02-12 13:13:37,060: INFO/MainProcess] Task tasks.add[42789af3-e4e0-4679-aa61-b449573c7acd] succeeded in 0.5160000002942979s: 3
小案例:自動化發送郵件無附件版
郵件信息傳遞工作原理
- SMTP協議: Simple Mail Transfer Protocol, 是一種提供可靠且有效的電子郵件傳輸的協議。SMTP建立 在FTP文件傳輸服務上的一種郵件服務,主要用於系統之間的郵件信息傳遞,並提供有關來信的通知。
- POP3協議: Post Office Protocol - Version 3, 主要用於支持使用客戶端遠程管理在服務器上的電子郵件。
用戶讀取郵件使用的協議:POP3
發送郵件時,需要先將郵件放入郵箱中,此處選擇網易郵箱服務器,發送郵件需要輸入自己的郵箱和密碼,代碼中體現爲mail_user與mail_password (此處不寫真實郵箱密碼而是授權碼),報警內容爲郵件主題。
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
# 設置服務器,用戶名、口令以及郵箱的後綴
smtp_server = "smtp.163.com"
from_username = '騰訊雲服務中心'
mail_user = "[email protected]"
#開啓smtp的授權碼
mail_password ='scq123'
#郵件主題前綴
mail_prefix = "[運維開發部]-"
def send_mail(to_addrs, subject,content):
"""
發送郵件
:param to_addrs: 郵件接收人
:param subject: 郵件標題
:param content: 郵件正文內容
:return: Bool
"""
# msg = MIMEText(content)
# 創建一個帶附件的實例
try:
msg = MIMEText(content) #將要發送的文本信息做MINME封裝
# 將消息與發件人、地址、主題綁定到一起
msg['From'] = formataddr([from_username, mail_user]) #將mail_user格式化爲from_username,qq接收端不顯示原地址名與用戶
msg['To'] = to_addrs
msg['Subject'] = mail_prefix + subject
#實例化smtp對象
server = smtplib.SMTP()
# 連接網易郵件服務器 163->qq
server.connect(smtp_server)
# 登錄
server.login(mail_user, mail_password)
# 發送郵件內容
server.sendmail(mail_user, to_addrs, msg.as_string())#把發送的信息轉換爲字符串發送
# 關閉連接
server.quit()
#發送出錯時捕獲異常
except Exception as e:
print(str(e))
return False
else:
return True
if __name__ == '__main__':
result=send_mail(' @qq.com', '人生哲理', '真正的勇士,敢於直面慘淡的人生,敢於正視淋漓的鮮血')
#郵件內容不可以無厘頭,否則發不出去
if result:
print("發送成功.....")
else:
print('發送失敗。。。。')
小案例:自動化發送郵件帶附件版
MIME是多功能Internet郵件擴展,設計的最初目的是爲了在發送電子郵件時附加多媒體數據,讓郵件 客戶程序能根據其類型進行處理。
常見的MIME類型(通用型):
超文本標記語言: .html text/html
xml文檔 : .xml text/xml
PDF文檔: .pdf application/pdf
加附件版的案例與不加附件版的案例區別如下:
- 打開附件的內容,將附件封裝和添加頭部信息後再與報文綁定
- 發送郵件主體部分中判斷附件是否存在,存在的話依次遍歷各個附件,先查看此附件是否在電腦中,再將報文和附件內容綁定到一起.
import os
import smtplib
from email.mime.application import MIMEApplication
from email.mime.text import MIMEText
from email.utils import formataddr
from email.mime.multipart import MIMEMultipart #指出發送附件和正文信息
#配置信息
# 設置服務器,用戶名、口令以及郵箱的後綴
smtp_server = "smtp.163.com"
from_username = '騰訊雲服務中心'
mail_user = "[email protected]"
#開啓smtp的授權碼
mail_password ='scq123'
#郵件主題前綴
mail_prefix = "[運維開發部]-"
#用於封裝附件的函數
def format_attach(file):
"""
附件在於報文綁定前的格式化操作
:param file: 打開附件的文件對象
:return:
"""
filename=file.name #獲取附件名稱,文件對象的獲取姓名屬性
base, ext = os.path.splitext(filename) #分割文本信息用於查看文件類型,不同類型封裝不同。hello.png-->('hello','.png')
attach = MIMEApplication(file.read(), _subtype=ext) #封裝,爲了知道讀取文件內容時文件類型是什麼。
attach.add_header('content-disposition', 'attachment',filename=filename) #封裝時添加頭部信息,標記此爲郵件的附件
return attach
def send_mail(to_addrs:str, subject:str,content:str,attaches:list=None)-> bool:
"""
發送郵件
:param to_addrs: 郵件接收人
:param subject: 郵件標題
:param content: 郵件正文內容
attaches:附件默認爲空,列表存儲
:return: Bool
"""
# msg = MIMEText(content)
# 創建一個帶附件的實例
try:
msg = MIMEMultipart(_subtype='alternative') #發送郵件類型爲正文與附件共存的類型
# 將消息與發件人、地址、主題綁定到一起
msg['From'] = formataddr([from_username, mail_user]) #將mail_user格式化爲from_username,qq接收端不顯示原地址名與用戶
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: #爲了兼容以二進制讀取
msg.attach(format_attach(f)) #附件添加封裝和頭部信息後再與報文綁定
else:
print('附件%s不存在'%(attach))
#實例化smtp對象
server = smtplib.SMTP()
# 連接網易郵件服務器 163->qq
server.connect(smtp_server)
# 登錄
server.login(mail_user, mail_password)
# 發送郵件內容
server.sendmail(mail_user, to_addrs, msg.as_string())#把發送的信息轉換爲字符串發送
# 關閉連接
server.quit()
#發送出錯時捕獲異常
except Exception as e:
print(str(e))
return False
else:
return True
if __name__ == '__main__':
result=send_mail('[email protected]', '人生哲理', '真正的勇士,敢於直面慘淡的人生,敢於正視淋漓的鮮血',['C:/Users/scq/Desktop/pycharmprodect/scq15_code/2.png'])
#郵件內容不可以無厘頭,否則發不出去
if result:
print("發送成功.....")
else:
print('發送失敗。。。。')