为何AWS Lambda使用requests.get请求无法解析域名时延迟10秒?
问题:AWS Lambda中requests请求无法解析域名时的10秒延迟问题
我在使用Python的requests库从AWS Lambda函数向无法解析的域名发起HTTP请求时遇到异常:
- 执行代码:
response = requests.get('https://benandjerry.com', timeout=(1,1)) - 在Lambda环境中,请求需要约10秒才抛出错误,但本地环境报错即时
- 已排除Lambda冷启动、运行时差异、VPC配置等问题;Lambda中用
curl访问这些域名会即时返回Could not resolve host: benandjerry.com - 仅特定无法解析的域名出现该问题,例如:seen.ma、benandjerry.com、clenyabeauty.com、gong.com
可通过Python3.9版本的AWS Lambda复现,代码如下:
import json from botocore.vendored import requests import urllib.request import os def lambda_handler(event, context): url = 'http://benandjerry.com' try: response = requests.get(url, proxies=None,verify=False) except Exception as e: print(e) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
疑问:
- 为何AWS Lambda使用
requests.get请求无法解析的域名时会出现持续10秒的延迟? - 如何让AWS Lambda像本地环境一样即时识别域名无法解析?
解答
1. 延迟原因
核心原因在于Lambda环境中requests的底层DNS解析逻辑和本地存在差异:
requests基于urllib3运行,而Lambda自带的botocore.vendored版本requests/urllib3,在遇到部分特殊域名(比如存在NS记录但无法返回A/AAAA记录的域名)时,会触发内置的重试逻辑或等待系统DNS resolver的默认超时,这个周期刚好约10秒。- 你设置的
timeout=(1,1)仅控制连接和读取阶段的超时,不覆盖DNS解析阶段的超时,这就是为什么明明设置了1秒超时却还是会等待10秒的关键。 - 而
curl直接调用系统DNS解析逻辑,没有urllib3层的额外重试或等待,所以能即时返回解析失败的结果。
2. 解决方法
方法一:提前手动做DNS解析
用socket模块先单独解析域名,设置短超时,解析失败直接抛出异常,避免进入requests的等待流程:
import socket import json import requests from urllib.parse import urlparse def lambda_handler(event, context): url = 'http://benandjerry.com' parsed_url = urlparse(url) domain = parsed_url.netloc or parsed_url.path.split('/')[0] try: # 手动解析域名,设置1秒超时 socket.setdefaulttimeout(1) socket.gethostbyname(domain) # 解析成功再发起请求 response = requests.get(url, proxies=None, verify=False, timeout=(1,1)) except socket.gaierror: print(f"无法解析域名:{domain}") except Exception as e: print(e) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
方法二:替换为新版requests
botocore.vendored的requests版本老旧,DNS处理逻辑存在缺陷。可以通过Lambda层或部署包引入最新版requests和urllib3:
- 本地打包依赖:
mkdir python pip install requests urllib3 -t python/ zip -r requests_layer.zip python/ - 在AWS控制台创建Lambda层,上传上述zip包,并关联到目标Lambda函数
- 修改代码导入新版
requests:import json import requests # 使用自己安装的新版本 def lambda_handler(event, context): url = 'http://benandjerry.com' try: response = requests.get(url, proxies=None, verify=False, timeout=(1,1)) except Exception as e: print(e) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
方法三:补丁urllib3的DNS解析逻辑
如果必须使用旧版requests,可以重写urllib3的连接逻辑,强制设置DNS解析超时:
import json from botocore.vendored import requests from botocore.vendored.requests.packages.urllib3.util.connection import create_connection import socket # 重写create_connection,添加DNS超时控制 def patched_create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None, socket_options=None): host, port = address try: # 设置DNS解析超时为1秒 socket.setdefaulttimeout(1) ip = socket.gethostbyname(host) except socket.gaierror: raise return create_connection((ip, port), timeout, source_address, socket_options) # 替换urllib3的默认实现 requests.packages.urllib3.util.connection.create_connection = patched_create_connection def lambda_handler(event, context): url = 'http://benandjerry.com' try: response = requests.get(url, proxies=None, verify=False, timeout=(1,1)) except Exception as e: print(e) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
内容的提问来源于stack exchange,提问作者jjyoh
相关产品推荐
相关产品推荐

