在与各种网络服务进行通信的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秒?
答案 0 :(得分:8)
答案 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
)会掩盖但不能完全解决问题。