【问题标题】:Python: retrieve several URLs via select.epoll()Python:通过 select.epoll() 检索多个 URL
【发布时间】:2018-01-02 18:50:44
【问题描述】:

我有一个已经使用select.epoll() 的面向事件的服务器。

现在应该解决一个新要求:应该获取 URL(异步)。

到目前为止,我一直使用 requests 库,而且我一直使用它同步,从不异步。

如何结合 linux epoll 使用 requests 库(或不同的 urllib)?

请求库文档对此有说明,但仅提及异步框架(未提及 select.epoll()):http://docs.python-requests.org/en/master/user/advanced/#blocking-or-non-blocking

我没有与 select.epoll() 结婚。它工作到现在。如果可行,我可以使用其他解决方案。

背景:更大的问题是“我应该使用 select.epoll() 还是 python 拥有的众多异步框架之一?”。但是 StackOverflow 上的问题不能太宽泛。这就是为什么这个问题的重点是“通过 select.epoll() 检索多个 URL”。如果您对更大的问题有提示,请发表评论。

如果你很好奇,我在业余时间开发的一个小项目需要这个问题:https://github.com/guettli/ipo(IPO 是一个基于 PostgreSQL 的开源异步作业队列。)

【问题讨论】:

  • 你必须展示你的事件循环是如何工作的。
  • @georgexsh 这是我的事件循环的工作方式:github.com/guettli/ipo/blob/master/ipo/management/commands/…
  • 对于你更大的问题,IO的原理是轮询高速IO,中断慢IO。
  • @obgnaw 你说“IO 的原理是轮询高速 IO,中断慢速 IO”。我想稍后优化。我如何知道与 URL 的连接是慢速还是高速?在我的情况下,URL 将来自非常接近守护进程的服务器。谢谢你的提示。我想我会先从 epoll() 开始。你怎么看?
  • @guettli 您的项目需要支持哪些 python 版本?

标签: python asynchronous python-requests urllib epoll


【解决方案1】:

如何结合 linux epoll 使用 requests 库(或不同的 urllib)?

不幸的是,除非在构建此类库时考虑到这种集成,否则您不能这样做。 epoll,以及select/poll/kqueue等都是I/O复用 em> 系统调用和整个程序架构都需要围绕它构建。

简单来说,一个典型的程序结构可以归结为以下几点

  • 需要有一堆文件描述符(在你的情况下是非阻塞模式的套接字)
  • 系统调用(ma​​n epoll_waitepoll 的情况下)会阻塞,直到一个或多个描述符上发生指定事件
  • 返回可用于 I/O 的描述符信息

之后,这是外部代码处理这些描述符的工作,即确定有多少数据可用,调用一些回调等。

如果库使用常规 阻塞 套接字,唯一的并行化方法是使用 threads/processes 这是一个很好的 article 主题,示例使用 C,这很好,因为它更容易理解引擎盖下实际发生的事情

异步框架和请求库

让我们看看有什么建议here

如果您担心阻塞 IO 的使用,有很多 将请求与 Python 之一结合起来的项目 异步框架。一些很好的例子是 requests-threads、grequests 和 requests-futures)。

请求线程 - 使用线程

grequests - 与 gevent 集成(这是另一回事,见下文)

requests-futures - 实际上也是线程/进程

它们都与真正的异步性无关

我应该使用 select.epoll() 还是 python 拥有的众多异步框架之一

请注意,epolllinux 特有的 野兽,它不会工作,即在具有不同机制的 OS X 上称为 kqueue .由于您似乎正在编写通用作业队列,因此这似乎不是一个好的解决方案。

现在回到 python。您有以下选择:

threads/processes/concurrent.futures - 这不太可能是您的目标,因为您的应用是典型的 C10K 服务器

epoll/kqueue - 你必须自己做所有事情。在获取 HTTP url 的情况下,您不仅需要处理 http/ssl,还需要处理异步 DNS 解析。还可以考虑使用提供一些基本基础设施的asyncore[]

twisted/tornado - 基于回调的框架已经为您完成所有低级工作

gevent - 如果您要重用现有的阻塞库(urllib、requests 等)并同时使用 python 2.x 和 python 3.x,您可能会喜欢这种方式。但是这个解决方案是设计的。对于您大小的应用程序,它可能没问题,但我不会将它用于任何应该坚如磐石并在 prod 中运行的更大的应用程序

异步

这个模块提供了编写单线程的基础设施 使用协程的并发代码,通过套接字多路复用 I/O 访问 和其他资源,运行网络客户端和服务器,以及其他 相关原语

它拥有您可能需要的一切。 还有一堆库使用流行的 RDBM 和 http https://github.com/aio-libs

但它缺乏对 python 2.x 的支持。有 ports 的 asyncio 到 python 2.x 但不确定它们有多稳定

终于

所以如果我可以牺牲 python 2.x,我个人会选择 asyncio 和相关库

如果您真的需要 python 2.x,请根据所需的稳定性和假定的峰值负载使用上述方法之一

【讨论】:

  • 感谢您的深入回答。我目前的感觉是使用 asyncio 并放弃 Python2 支持。
  • 我没有与 select.epoll() 结婚。它工作到现在。如果可行,我可以使用不同的解决方案。
【解决方案2】:

在做高性能开发的时候,我们总是根据自己的情况来选择武器。所以还是太宽泛了。

但您的更大问题是一个更简单的问题。只有 IO 绑定程序适合异步。

epoll和异步的目的是什么?避免CPU等待IO而无所事事。CPU等待IO块,IO块是因为NO DATA TO READ或NO space to write。

引入缓冲区是为了减少系统调用。当你在一个流上调用read时,你实际上是从缓冲区中读取的。(概念,不是很准确)

Select 或 epoll 是非阻塞繁忙轮询(epoll 通过底层中断实现)。它本质上类似于下面

while true {
  for i in stream[]{
    if i has data
          read until unavailable
    }
}

这很傻,所以有select和epoll。 每次从缓冲区读取,都有数据在等着你,是高速IO,那么epoll/select是你最好的选择情况。

我对异步不是很了解,对我来说只是内部的软中断和很多回调。

【讨论】:

    【解决方案3】:

    上面的要点是正确的,从技术上讲,您不能通过用于多路复用 I/O 的阻塞调用来执行此操作,例如 select()epoll() 和 BSD/iOS、Windows 变体。这些调用允许指定超时,因此您可以通过在短时间内重复轮询来接近,然后将工作传递给主线程之外的异步处理程序。在这种情况下,读取是在主线程上完成的,多次读取可以表明它们已准备就绪,并且主线程主要致力于该任务。

    如果您的问题规模小到中等,那么没有什么能比epoll()...read() 甚至select()...read() 更好。如果您的问题(读取通道数)很小。所以我鼓励你考虑一下——从可以专门用于请求的主线程中获得尽可能多的工作。

    如果您正在寻找异步解决方案,您最好的选择之一是grequests 库,既易于使用又性能卓越。要获得一个想法,请运行以下客户端-服务器对。请注意,tornado 的使用在这里无关紧要,仅在服务器端,而您关心的是客户端。

    试试这个 - 性能差异是白天和黑夜。

    下面的 client.py 类代表您的解决方案;它使用grequests 异步发出get() 请求。

    server.py

    from tornado import (httpserver, options,
                         ioloop, web, gen)
    import time
    
    import ujson as json
    from collections import defaultdict
    
    class Check(web.RequestHandler):
    
        @gen.coroutine
        def get(self):
            try:
                data = int(self.get_argument('data'))
            except ValueError:
                raise web.HTTPError(400, reason='Invalid value for data')
    
            delay = 100
            start = time.time()
            print('Processed: {!r}'.format(data))
    
           yield gen.Task(ioloop.IOLoop.instance().add_timeout, start + delay / 1000.)
    
            self.write('.')
            end = time.time()
            self.finish()
    
    
    if __name__ == '__main__':
        port = 4545
    
        application = web.Application([
            (r'/get', Check)
            ])
    
        http_server = httpserver.HTTPServer(application)
        http_server.listen(port)
        print('Listening on port: {}'.format(port))
        ioloop.IOLoop.instance().start()
    

    client.py

    import grequests
    from tornado.httpclient import HTTPClient
    import time
    
    def call_serial(num, httpclient):
        url = 'http://127.0.0.1:4545/get?data={}'.format(num)
        response = httpclient.fetch(url)
        print('Added: {!r}'.format(num))
    
    def call_async(mapper):
        futures = (grequests.get(url) for url,_ in mapper)
        responses = grequests.map(futures)
        for response, (url,num) in zip(responses, mapper):
            print('Added: {!r}'.format(num))
    
    def check(num):
        if num % 2 == 0:
            return False
        return True
    
    def serial_calls(httpclient, up_to):
        for num in range(up_to):
            if check(num):
                call_serial(num, httpclient)
    
    def async_calls(httpclient, up_to):
        mapper = []
    
        for num in range(up_to):
            if check(num):
                url = 'http://127.0.0.1:4545/get?data={}'.format(num)    
                mapper.append((url,num))
    
        call_async(mapper)
    
    
    if __name__ == '__main__':
    
        httpclient = HTTPClient()
    
        print('SERIAL CALLS')
        serial_calls(httpclient, 100)
    
        print('ASYNC CALLS')
        async_calls(httpclient, 100)
        httpclient.close()
    

    这是一个真正的异步解决方案,或者尽可能接近 CPython/python。没有使用轮询器。

    【讨论】:

    • 只是为了确保 - Tornado 在服务器端,只有服务器-客户端对的服务器需要。发出 get() 请求的逻辑使用 grequests,我发现它是一个很好的选择,如果不是最好的选择。
    • 关于 grequests:“注意:您可能应该使用 requests-threads 或 requests-futures。”来自github.com/kennethreitz/grequests。为什么作者建议使用别的东西?
    • 当我使用pip 安装时,我没有注意到这一点。让我考虑一下。
    猜你喜欢
    • 2013-04-14
    • 2015-10-20
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 2021-05-26
    • 2012-10-01
    • 2014-03-02
    • 1970-01-01
    相关资源
    最近更新 更多