Python`socket.getaddrinfo`占用了大约0.1%的请求5秒

时间:2017-10-19 18:31:58

标签: python sockets dns python-requests

在与各种网络服务进行通信的Django项目上运行Python,我们遇到一个问题,偶尔请求大约需要5秒而不是通常的< 100毫秒。

我已将此缩小到socket.getaddrinfo函数中的时间 - 当我们连接到外部服务时,requests会调用此函数,但它似乎也会影响默认的Django连接到集群中的Postgres数据库框。当我们在部署后重新启动uwsgi时,第一个进入的请求将花费5秒钟来发送响应。我也相信我们的芹菜任务定期需要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

这肯定改进了我们使用的非curl非Python程序的查找,但不是来自Python本身。服务器盒运行的是Ubuntu 16.04.3 LTS,我可以在带有Python 2的vanilla虚拟机上重现这一点。

我可以采取哪些步骤来提高所有Python查找的性能,以便它们可以采用< 1秒?

3 个答案:

答案 0 :(得分:8)

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

You can lower that.

你的真正问题可能是(无声)UDP数据包在网络上丢失了。

修改:试用resolution over TCP。从未这样做过。可以帮到你。

答案 1 :(得分:2)

有两件事可以做。一个是你不查询IPV6地址,这可以通过猴子修补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)

答案 2 :(得分:2)

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

如果您遇到了无法控制的丢失,运行缓存服务器(nscd)会掩盖但不能完全解决问题。