uwsgi+django+gmail+threading多线程发邮件

1,466 阅读3分钟

uwsgi+django+gmail+threading多线程发邮件

最近负责公司的金融平台、其中在审核人员通过提现审核之后需要给用户发邮件进行通知。

废话不多说、直接上代码:

# coding: utf-8

import threading
import datetime
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email import encoders
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
import os
import logging

logger = logging.getLogger('django')

class EmailSend(threading.Thread):
    '''
    发送邮件
    '''
    def __init__(self, msg, title, receivers):
        self.msg = msg
        self.title = title
        self.receivers = receivers
        self.img = os.path.join(os.path.abspath('.'), 'static/img/liveme.png')
        threading.Thread.__init__(self)

    def run(self):
        EMAIL_LIST = self.receivers
        EMAIL_HOST = 'smtp.gmail.com'
        EMAIL_HOST_USER = '****'
        EMAIL_HOST_PASSWORD = '****'


        message = MIMEMultipart('related')
        message['Subject'] = Header(self.title, 'utf-8')

        msg_text = MIMEText(self.msg, 'html', 'utf-8')
        message.attach(msg_text)

        with open(self.img, 'rb') as f:
            msg_image = MIMEImage(f.read())
            msg_image.add_header('Content-Disposition', 'attachment', filename='liveme.png')
            msg_image.add_header('Content-ID', '<0>')
            msg_image.add_header('X-Attachment-Id', '0')
            message.attach(msg_image)
        logger.info('begin to send email, params: %s, time: %s' % (locals(), str(datetime.datetime.now())))
        smtpObj = smtplib.SMTP()
        smtpObj.connect(EMAIL_HOST, 25)
        smtpObj.starttls()
        smtpObj.login(EMAIL_HOST_USER, EMAIL_HOST_PASSWORD)
        smtpObj.sendmail(EMAIL_HOST_USER, EMAIL_LIST, message.as_string())
        smtpObj.quit()
        logger.info('end to send email, params: %s, time: %s' % (locals(), str(datetime.datetime.now())))

def send(msg, title, receivers):
    '''
    发送邮件
    :param msg:
    :param title:
    :param receivers:
    :return:
    '''
    EmailSend(msg, title, receivers).start()

本地是用pycharm起的开发环境、OK、代码在本地完全没问题、可以很好的把邮件发出去。但是把代码部署到测试环境的时候发现不行了(测试环境为uwsgi)、TMD、邮件根本发不出去、也不报错。此刻表示很桑心😭。

于是乎开始了漫长的排查过程。。。

  1. 测试服务器用的是aws,会不会是aws对服务器做了什么限制?

    开始找运维、经过运维同学的一番折腾、发现aws没有做任何限制。不对啊、那为毛在本地就可以很好的发出去而到了aws就挂了、不科学啊。难道是网络原因?于是又开始折腾搞网络的同学、最后发现网络也没有问题😭

  2. 发现一个很怪异的问题:每次触发邮件操作之后都好比石沉大海似的没有了下文,但是一重启服务立马就能收到之前的邮件。

    这是什么逻辑,难道每次发完邮件都需要重启服务?显然不可能啊、没人这么干啊。算了,继续排查。

  3. 排查中。。。

    发现代码里面少了一个操作smtpObj.quit()、恍然大悟、难不成不执行smtpObj.quit()的话程序就会hang住?满怀期待的加上了这句代码、结果除了失望还是失望😔。

  4. 继续排查。。。

    这环境和代码都明明是一模一样的、为啥啊、难道是多线程的问题?可是代码和环境都是一样的啊、想不通、开始走到崩溃的边缘。静下心来仔细想想,不对、还有一个地方不一样。本地开发环境是用python manage.py runserver方式启动的、而测试环境是用uwsgi方式启动的。于是抱着试试看的心态去Google了一下。TMD、还真是uwsgi搞的鬼、之前的uwsgi.ini配置如下:

    [uwsgi]
    socket=127.0.0.1:8000
    #http= :8000
    master=true
    vhost=true
    gid=nobody
    uid=nobody
    module=uwsgi
    workers=24
    max-requests=1000
    limit-as=1024
    pidfile=/data/app/cms-finance/uwsgi.pid
    daemonize=/data/app/cms-finance/uwsgi.log
    log-x-forwarded-for
    harakiri=1800
    buffer-size=16384
    

    后来增加了俩配置:

    enable-threads=true
    lazy=true
    

    再次触发邮件操作、万幸、这次可算是收到了。

    官方文档是这么说明的

    Python 线程小贴士
    
    如果你没有使用线程启动 uWSGI,Python 的 GIL
    将不会被开启,所以你的应用产生的线程
    将永远不会运行。你可能不会喜欢这个选择,但是记住 uWSGI
    是一个语言无关的服务器,所以它的大部分选择都是尽可能维持它 “agnostic”。
    
    但是不用担心,基本上不存在不能通过选项来改变的由 uWSGI 开发者决定的选项。
    
    如果你想维持 Python 的线程支持同时应用又不启动多个线程,只需要加上
    --enable-threads 选项 (或者 enable-threads = true 在 ini 风格配置文件中)。
    

最后

只想告诫自己

有些坑只有踩过之后才会记忆深刻😭