【问题标题】:Python `socket.getaddrinfo` taking 5 seconds about 0.1% of requestsPython `socket.getaddrinfo` 需要 5 秒大约 0.1% 的请求
【发布时间】:2018-03-31 21:19:09
【问题描述】:

在与各种 Web 服务通信的 Django 项目上运行 Python,我们遇到了一个问题,即偶尔请求需要大约 5 秒而不是通常的

我已将其缩小到 socket.getaddrinfo 函数所花费的时间 - 当我们连接到外部服务时,requests 会调用它,但它似乎也会影响到 Postgres 数据库框中的默认 Django 连接集群。当我们在部署后重新启动uwsgi 时,第一个进来的请求将需要 5 秒来发送响应。我也相信我们的 celery 任务通常需要 5 秒,但我还没有向它们添加 statsd 计时器跟踪。

我已经写了一些代码来重现这个问题:

import socket
import timeit

def single_dns_lookup():
    start = timeit.default_timer()
    socket.getaddrinfo('stackoverflow.com', 443)
    end = timeit.default_timer()
    return int(end - start)

timings = {}

for _ in range(0, 10000):
    time = single_dns_lookup()
    try:
        timings[time] += 1
    except KeyError:
        timings[time] = 1

print timings

典型结果为{0: 9921, 5: 79}

我的同事已经指出了有关 ipv6 查找时间的潜在问题,并将其添加到 /etc/gai.conf

precedence ::ffff:0:0/96  100

这无疑改善了非 Python 程序的查找,例如我们使用的 curl,但不是来自 Python 本身。服务器运行的是 Ubuntu 16.04.3 LTS,我可以在带有 Python 2 的 vanilla VM 上重现它。

我可以采取哪些步骤来提高所有 Python 查找的性能,以便它们可以花费

【问题讨论】:

  • 你的dns解析器好像很慢,试试ncsd?
  • @georgexsh 我不确定 nscd 将如何提供帮助 - 第一个未缓存的请求仍需要 5 秒。在缓存值过期后,通过解析器的第一个请求将再次花费 5 秒。这只会减少慢请求的百分比,而不是完全删除它们,对吗?
  • 您是否尝试过使用sysctl net.ipv6.conf.all.disable_ipv6=1 完全禁用 IPv6 堆栈,并检查是否能解决您的问题?如果是,那么很可能您的python 链接到不尊重 gai.conf 的 glibc 版本(或静态编译的)。
  • 无法在 vagrant ubuntu/xenial64 本地复制
  • 考虑使用collections.Counter,而不是自己使用KeyError

标签: python sockets dns python-requests


【解决方案1】:

5s 是 DNS 查找的默认超时时间。

You can lower that.

您真正的问题可能是(静默)UDP 数据包在网络上丢失。

编辑:试验resolution over TCP。从来没有这样做过。可能会对你有所帮助。

【讨论】:

    【解决方案2】:

    有两件事可以做。一是你不查询IPV6地址,这可以通过monkey patching getaddrinfo来完成

    orig_getaddrinfo = socket.getaddrinfo
    
    def _getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
        return orig_getaddrinfo(host, port, socket.AF_INET, type, proto, flags)
    
    socket.getaddrinfo = _getaddrinfo
    

    接下来,您还可以使用基于 ttl 的缓存来缓存结果。您也可以使用 cachepy 包。

    from cachetools import cached
    import socket
    import timeit
    from cachepy import *
    # or from cachepy import Cache
    
    cache_with_ttl = Cache(ttl=600) # ttl given in seconds
    
    orig_getaddrinfo = socket.getaddrinfo
    
    # @cached(cache={})
    @cache_with_ttl
    def _getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
        return orig_getaddrinfo(host, port, socket.AF_INET, type, proto, flags)
    
    socket.getaddrinfo = _getaddrinfo
    
    def single_dns_lookup():
        start = timeit.default_timer()
        socket.getaddrinfo('stackoverflow.com', 443)
        end = timeit.default_timer()
        return int(end - start)
    
    timings = {}
    
    for _ in range(0, 10000):
        time = single_dns_lookup()
        try:
            timings[time] += 1
        except KeyError:
            timings[time] = 1
    
    print (timings)
    

    【讨论】:

      【解决方案3】:

      在构建缓存或猴子补丁socket.getaddrinfo 之前,我会首先尝试了解缓慢的根本原因。您的名称服务器在/etc/resolv.conf 中配置正确吗?您在网络上看到丢包了吗?

      如果您遇到无法控制的损失,运行缓存服务器 (nscd) 将掩盖但不能完全消除问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-31
        • 2015-03-08
        • 2019-02-12
        • 1970-01-01
        • 2017-01-22
        • 1970-01-01
        相关资源
        最近更新 更多