python項目-------基於Celery、Redis和Flower的異步郵件報警與監控


異步:客戶端發送消息無需等待服務端回覆之後才能發出第二個消息,可以一直髮,來讓服務端處理。

異步任務隊列的主要應用場景:

  • 無須實現響應,性能佔用較大,任務處理時間較長的任務,如佔用網絡性能的發送郵件,佔用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('發送失敗。。。。')

小案例:基於Celery、Redis和Flower的異步郵件報警與監控

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章