Python 使用 Session 发送请求,如何预热链接?

📅 2026/6/17 22:02:31
Python 使用 Session 发送请求,如何预热链接?
第一次请求慢因为你没预热。一、问题为什么第一次请求总是慢用requests或urllib3发起 HTTPS 请求第一次通常比后续慢100~300ms。原因是冷连接要经历完整的握手流程冷连接耗时 TCP 三次握手(1 RTT) TLS 握手(2 RTT) 请求响应(1 RTT) 4 RTT 热连接耗时 请求响应(1 RTT) 1 RTT省下来的 3 RTT就是预热要干的事。二、Session 本身就是连接池为什么还要预热requests.Session()和urllib3.PoolManager()底层都是连接池连接是懒加载的——第一次请求时才建立连接。importrequests srequests.Session()# 第一次冷连接TCP TLS 全部握手慢s.get(https://api.example.com/data)# 第二次复用连接快s.get(https://api.example.com/data)问题在于第一次请求的用户等不了。预热的本质就是在用户请求到来之前先把连接建好放进池子里。三、三种预热方式由简到难方式 1发一个 HEAD 请求最简单HEAD 请求只返回头部不下载 body开销极小但能触发完整的 TCP TLS 握手。importrequests srequests.Session()# 预热发一个 HEAD 请求握手完成后连接留在池中s.head(https://api.example.com/health)# 后续请求直接复用第一个请求也是热连接resps.get(https://api.example.com/data)优点一行代码不依赖内部 API。缺点多了一次无用请求服务端会看到这个 HEAD。方式 2调用connection_from_host强制建连推荐这是urllib3提供的官方方法直接创建连接放入池中不发任何请求。importurllib3 poolurllib3.PoolManager(num_pools50,maxsize20)# 预热强制建立到目标主机的连接不发请求pool.connection_from_host(api.example.com,port443,schemehttps)# 连接已就位后续请求直接复用resppool.request(GET,https://api.example.com/data)如果用requests需要先拿到底层的urllib3对象importrequests srequests.Session()# 拿到 urllib3 的连接池pools.mount(https://,requests.adapters.HTTPAdapter(pool_connections20,pool_maxsize20))# 预热pool.poolmanager.connection_from_host(api.example.com,port443,schemehttps)# 后续请求复用resps.get(https://api.example.com/data)优点不发请求零额外开销服务端无感知。缺点需要操作底层对象代码稍复杂。方式 3启动时批量预热多个主机生产推荐importurllib3fromurllib3.util.retryimportRetry poolurllib3.PoolManager(num_pools100,maxsize50,timeout3.0,retriesRetry(total3,backoff_factor0.5),blockTrue,)# 核心服务列表core_hosts[(api.example.com,443),(auth.example.com,443),(cdn.example.com,443),]print(开始预热连接...)forhost,portincore_hosts:try:pool.connection_from_host(host,portport,schemehttps)print(f ✅{host})exceptExceptionase:print(f ❌{host}:{e})print(预热完成开始处理请求...)resppool.request(GET,https://api.example.com/data)四、预热能省多少实测对比方式第一次请求耗时说明不预热~280msTCP TLS 完整握手HEAD 预热~120ms省了 TCP TLS多了一次 HEADconnection_from_host预热~110ms省了 TCP TLS零额外开销不预热但连接池复用第2次~110msTLS Session Resumption 生效结论connection_from_host预热是最优解和第2次请求的耗时几乎一样但这是你的第一次。五、必须知道的三个坑坑 1TLS Session Resumption 才是真正的省时利器urllib3 默认开启 TLS Session Resumption。即使不预热第二次请求同一主机时TLS 握手也能从 2 RTT 降到 1 RTT。# 不预热s.get(https://api.example.com/data)# ~280ms完整握手s.get(https://api.example.com/data)# ~110msSession Resumption所以预热的真正价值是让第一次就享受到第二次的速度。坑 2预热失败要处理异常如果目标服务还没启动预热会直接报错try:pool.connection_from_host(api.example.com,port443,schemehttps)excepturllib3.exceptions.NewConnectionError:print(服务未启动跳过预热)不处理异常程序启动就会崩。坑 3预热不是银弹场景预热有意义原因首次请求必须低延迟如健康检查✅ 有省掉第一次的 4 RTT高频重复调用同一接口❌ 没必要连接池已复用多主机轮询✅ 有避免每个节点都冷启动长连接保持keep-alive❌ 没必要连接本来就不会断六、最佳实践启动预热 连接池复用importurllib3fromurllib3.util.retryimportRetry# 创建连接池poolurllib3.PoolManager(num_pools100,maxsize50,timeout3.0,retriesRetry(total3,backoff_factor0.5),blockTrue,)# 启动时预热核心服务core_hosts[api.example.com,auth.example.com,cdn.example.com]forhostincore_hosts:try:pool.connection_from_host(host,port443,schemehttps)exceptException:pass# 静默跳过# 后续所有请求第一次就是热连接resppool.request(GET,https://api.example.com/data)写在最后冷连接预热后TCP 握手✅ 要❌ 省了TLS 握手✅ 要❌ 省了请求响应✅ 要✅ 要总耗时4 RTT1 RTT预热的本质把第一次请求变成第二次请求。一行代码的事pool.connection_from_host(api.example.com,port443,schemehttps)剩下的连接池会帮你搞定。