首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在Python中使单例smtp客户端线程安全?

如何在Python中使单例smtp客户端线程安全?
EN

Stack Overflow用户
提问于 2022-07-06 11:10:51
回答 1查看 134关注 0票数 1

我不确定我是否问的是正确的问题,所以请容忍我。

我有一个Azure函数应用程序,运行python代码。它接收一个JSON有效负载,执行一些转换,并发送电子邮件。

一切正常工作,直到该函数开始接收到大量请求。这导致了异常:smtplib.SMTPDataError: (432, b'4.3.2 Concurrent connections limit exceeded. Visit https://aka.ms/concurrent_sending for more information. [Hostname=xxxxxxxx]')

我决定为连接客户端创建一个单例来解决这个问题:

代码语言:javascript
复制
class EmailSingleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls.__login(*args, **kwargs)
        else:
            try:
                status = cls._instances[cls].noop()[0]
            except smtplib.SMTPServerDisconnected:
                status = -1
            if status != 250:
                log.info("SMTP Client disconnected")
                cls.__login(*args, **kwargs)
        return cls._instances[cls]

    def __login(cls, host, port, user, password, timeout):
        log.info("Refreshing SMTP client")
        cls._instances[cls] = super(EmailSingleton, cls).__call__(host=host, port=port, timeout=timeout)
        cls._instances[cls].starttls()
        cls._instances[cls].login(user=user, password=password)

class EmailConnectionClientSingleton(smtplib.SMTP, metaclass=EmailSingleton):
    pass

并简单地通过调用:

代码语言:javascript
复制
EmailConnectionClientSingleton(
    host=host,
    port=port,
    timeout=timeout,
    user=user,
    password=password
).send_message(msg)

当请求一个接一个地出现时,这很好,但当请求突然到达时,我会得到各种有趣的例外情况:

  • smtplib.SMTPDataError: (503, b'5.5.1 Bad sequence of commands')
  • smtplib.SMTPDataError: (250, b'2.1.5 Recipient OK')
  • smtplib.SMTPDataError: (250, b'2.0.0 OK')
  • smtplib.SMTPRecipientsRefused: {'recipient@domain.com': (503, b'5.5.1 Bad sequence of commands')}

看起来,以上所有这些都是由于握手、重叠和反应不正常造成的。只有1/3的电子邮件成功。

我应该如何处理这个问题?我是否应该使用单例客户机(这告诉我应该使用:https://learn.microsoft.com/en-us/azure/azure-functions/manage-connections?tabs=csharp#static-clients)?我是否试图以某种方式使我的单例客户端线程安全?是否为send_message调用创建队列?我是否创建了一个从不同功能发送电子邮件的员工?

我尝试在创建客户机时使用_lock = threading.Lock(),但这并没有帮助,因为问题似乎在于使用客户机而不是创建客户机。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-07-08 13:37:17

最后我使用了queue.Queue

代码语言:javascript
复制
class Singleton(type):
    _instance = None
    _lock = Lock()

    def __call__(cls, *args, **kwargs):
        if not cls._instance:
            with cls._lock:
                # another thread could have created the instance
                # before we acquired the lock. So check that the
                # instance is still nonexistent.
                if not cls._instance:
                    cls._instance = super().__call__(*args, **kwargs)
        return cls._instance


class QueueSingleton(Queue, metaclass=Singleton):
    pass

我没有立即发送电子邮件,而是把它放在队列中:

代码语言:javascript
复制
queue = QueueSingleton()
queue.put(msg)
log.info("Email put in the queue")

然后我创建了一个新的Azure函数,它使用队列发送电子邮件。

代码语言:javascript
复制
queue = QueueSingleton()

while start_time + FUNCTION_MAX_LIFE > datetime.now():
    try:
        msg = queue.get(timeout=300)
        log.info("Got an email to send!")
    except Empty:
        # If we didn't get anything to send during timeout window
        # end the execution and allow TimeTrigger to spawn a new worker
        log.info("No emails to send. Quitting.")
        break

    try:
        email_connection.send_message(msg)
        queue.task_done()
        log.info(f'Email sent: {msg["To"]}')

这感觉很烦人,但看起来很管用。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72882557

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档