用于长延期任务的扭曲反应器块

时间:2016-09-20 07:55:07

标签: twisted twisted.web twisted.internet

我正在开展一个涉及向api发出许多请求的项目,以及每个反馈我正在做出决定并保存在数据库中。我正在使用adbapi与mysql进行通信。

我收到的请求是一个POST,其中包含要推送到远程api并保存的项目列表。

我已经注意到,在延迟所有其他操作块中处理项目时,直到完成一个部分。

以下示例显示了与我正在做的事情类似的事情。

#!/usr/bin/python2.7

from twisted.web.server import Site
from twisted.web.resource import Resource
from twisted.internet import reactor, defer
from twisted.web.server import NOT_DONE_YET

from utils import send_mail, save_in_db


def get_params(request):
    params = {}
    for k, v in request.args.items():
        if k and v:
            params[k] = v[0]
    return params


class SendPage(Resource):

    def render_POST(self, request):
        params = get_params(request)
        emails = params['emails']
        message = params['message']
        self.process_send_mail(message, emails)
        request.write('Received')
        request.finish()
        return NOT_DONE_YET

    def process_send_mail(self, message, emails):
        defs = []
        for email in emails:
            d = send_mail(email, message)
            defs.append(d)
        d1 = defer.DeferredList(defs)
        d1.addCallback(self.process_save)

    def process_save(self, result):
        defs = []
        for r in result:
            d = save_in_db(r)
            defs.append(d)
        d1 = defer.DeferredList(defs)
        d1.addCallback(self.post_save)

    def post_save(self, result):
        print "request was completed"


root = Resource()
root.putChild("", SendPage())
factory = Site(root)
reactor.listenTCP(8880, factory)
reactor.run()

在上面的示例中,当我在列表中有大量电子邮件时(例如100,000),当我正在执行send_mail时,它会阻止其他操作直到完成。如果我在发生这种情况时尝试发送另一个请求,它会在完成后阻塞。

我的问题是,有没有办法让操作同时发生?我可以send_mail并以并发的方式save_in_db吗?我可以这样做,因为我收到其他请求和处理而不必等待彼此完成?

3 个答案:

答案 0 :(得分:0)

您可以省略等待结果或等待所有结果:发送和保存到数据库,如下所示:

def process_send_mail(self, message, emails):
    defs = []
    for email in emails:
        d = send_mail(email, message)
        defs.append(d)
        d = save_in_db(email)
        defs.append(d)

    d1 = defer.DeferredList(defs)
    d1.addCallback(self.post_save)      

def post_save(self):
    print "request was completed"

答案 1 :(得分:0)

我过去使用的一个技巧是inlineCallbacksyield的组合。基本上,您可以迭代n个元素然后yield或以给定间隔暂停,以便反应堆可以执行其他一些任务。因此,在您的情况下,您将装饰所有可能阻塞循环的函数,其中@inlineCallbacksenumerate循环,然后yield /暂停某一点以将控制权交还给反应堆

@defer.inlineCallbacks
def process_send_mail(self, message, emails):
    defs = []
    for i, email in enumerate(emails):    # enumerate
        d = send_mail(email, message)
        defs.append(d)
        if i % 1000 == 0:
            yield    # pause every 1000 elements
    d1 = defer.DeferredList(defs)
    d1.addCallback(self.process_save)

您必须调整间隔值以满足您的需求,因为该值取决于结果的产生速度。希望这会有所帮助。

答案 2 :(得分:0)

实际上有两个问题;我会另外解决。

首先是: “有没有办法可以同时进行操作?我可以send_mail并以并发方式save_in_db”吗?

答案是:是和否。你不能同时做到这一点,因为据我所知,保存数据库中的数据需要一些来自邮件发送的结果。但是,如果你的意思是:我可以在收到第一个邮件结果后立即开始在数据库中保存,而不是在将数据保存到数据库之前等待所有邮件结果 - 是的,你可以这样做;只需将两个处理功能合并为一个:

def process_send_mail_and_save(self, message, emails):
    defs = []
    for email in emails:
        d = send_mail(email, message)
        # might require tuning for save_in_db parameters if not matching send_mail callback output
        d.addCallback(save_in_db)
        defs.append(d)
    d1 = defer.DeferredList(defs)
    d1.addCallback(self.post_save)

2)“我可以这样做吗,因为我收到其他请求并处理而无需等待彼此完成?”

当然你可以在Twisted中做到这一点。但是你必须以正确的方式编写代码。你没有告诉我们send_mail或save_in_db是做什么的 - 我想你是在编写它们,而且我认为这些功能是阻塞并导致你的大多数问题 - 也许 send_mail 所有的SMTP工作,只有当它完成后返回?它应该立即返回延迟,并在作业完成时回调:

http://twistedmatrix.com/documents/16.4.0/core/howto/clients.html

我建议你在send_mail和save_in_db函数周围放置带有时间戳的日志记录调用 - 在你调用它们的那一刻,而不是它们延迟触发的那一刻。

请记住:Twisted延迟的全部内容是延迟会立即返回而不会阻塞,而与之关联的回调会在以后执行某些操作时触发。如果ANYTHING阻止任何地方,Twisted什么都不做 - 它是单线程的,基本上是一个合作的多任务处理。但Twisted无法将你的代码神奇地变成非阻塞 - 你必须这样做。

旁注:您使用server.NOT_DONE_YET的方式毫无意义。只需将“已接收”作为字符串返回并忘记请求对象。在其他地方调用request.finish()时,你使用NOT_DONE_YET,而不是立即。