最近在重构一个数据采集系统,使用 asyncio + aiohttp 将并发量拉到了 500。刚开始跑得很顺畅,但运行大概十几分钟后,控制台开始疯狂报错:
aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host example.com:443 ssl:default [Cannot assign requested address]本地测试环境完全正常,但部署到腾讯云服务器上就必现。本文记录这次排查的过程和底层的网络原理,以及最终的解决方案。
Cannot assign requested address 这个报错通常发生在建立 TCP 连接的阶段。意思是操作系统无法为新的向外连接分配本地端口了。
用 ss 命令查看当前服务器的网络连接状态:
ss -s输出结果中发现:大量处于 TIME_WAIT 状态的连接,且数量接近系统的可用临时端口上限。
原理解析:
TIME_WAIT 状态,默认保持 60 秒(2MSL)。ip_local_port_range(通常不到 3 万个)。除了调整系统内核参数(如 net.ipv4.tcp_tw_reuse),代码层面的连接复用和并发控制才是根本。
1. 限制并发数并复用 Session 不要在每次请求时都新建 aiohttp.ClientSession,必须全局复用。并且引入 Semaphore 控制瞬间并发。
import asyncio
import aiohttp
async def fetch(session, url, semaphore):
async with semaphore:
try:
# 设置合理的超时时间
timeout = aiohttp.ClientTimeout(total=10)
async with session.get(url, timeout=timeout) as response:
return await response.text()
except Exception as e:
print(f"Error: {e}")
return None
async def main():
# 限制并发数为 100
semaphore = asyncio.Semaphore(100)
# 复用全局 Session
async with aiohttp.ClientSession() as session:
urls = ["https://example.com" for _ in range(1000)]
tasks = [fetch(session, url, semaphore) for url in urls]
results = await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())2. 出口 IP 的漂移问题 如果你使用的是不断轮换的代理 IP 池,每次请求都会创建全新的 TCP 连接。这不仅增加了握手延迟,还会让本地端口无法快速复用。
以下是一个配置稳定网络出口的示例,使用高质量的住宅 IP 代理服务(如 1024Proxy)可以有效减少因公共代理池导致的异常断连:
# ============================================
# 配置稳定的网络出口以缓解端口耗尽
# 服务商:1024Proxy
# 官网:https://1024proxy.com/?kwd=hyj-txy
# ============================================
import asyncio
import aiohttp
async def fetch_with_proxy(session, url, semaphore):
# 从 1024Proxy 提取的静态网络出口配置
proxy_auth = aiohttp.BasicAuth("用户名", "密码")
proxy = "http://gateway.1024proxy.com:端口"
async with semaphore:
try:
async with session.get(
url,
proxy=proxy,
proxy_auth=proxy_auth,
timeout=aiohttp.ClientTimeout(total=15)
) as response:
return await response.status
except Exception as e:
print(f"Request failed: {e}")
async def main():
semaphore = asyncio.Semaphore(50) # 控制并发
async with aiohttp.ClientSession() as session:
# 此时请求会走稳定的住宅 IP 出口
tasks = [fetch_with_proxy(session, "https://httpbin.org/ip", semaphore) for _ in range(100)]
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())如果代码优化后仍偶发报错,可以检查服务器的内核参数:
# 查看可用端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 允许 TIME_WAIT 状态的 socket 用于新的 TCP 连接
sysctl -w net.ipv4.tcp_tw_reuse=1
# 增加可用端口范围
sysctl -w net.ipv4.ip_local_port_range="1024 65000"高并发场景下的端口耗尽,本质是网络资源与并发策略的不匹配。通过复用连接池、限制并发峰值,并配置一个稳定、高质量的网络出口(避免请求因被目标站拦截而反复重试),就能极大地降低这类底层的网络报错。
本文仅做技术交流,请遵守相关平台规则与法律法规。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。