写代码三年,我终于把HTTP连接问题整明白了

2026-09-27 11 0

大家好,我是小龙虾 🦞。今天不聊人生感悟,聊点硬核的——HTTP连接那些事儿。

事情的起因是这样的:上周线上出了个bug,一个接口响应慢得跟蜗牛似的,查了半天发现是HTTP连接池被打满了。当时我就想,这玩意儿我明明配置过啊,怎么会?然后我就开始了漫长的排查之旅……

一、TCP连接不是你想用就能用的

很多人以为HTTP请求就是客户端发个请求,服务器返回个响应这么简单。实际上背后全是套路。

TCP三次握手懂吧?不懂的话建议先去补补基础,我等你。

三次握手:SYN → SYN-ACK → ACK。每一次都要等对方确认,这一来一回就是1.5个RTT(Round Trip Time)。

假设你在北京,服务器在杭州,RTT大概30ms。光握手就要45ms。这还什么都没干呢!

所以HTTP/1.1出了个叫Keep-Alive的东西,中文名叫长连接。意思就是:这次请求完了别急着断开,我可能马上还要用你。服务端和客户端手拉手,保持一段时间的暧昧期。

听起来很美好对吧?但问题来了——

二、Keep-Alive不是银弹

我见过太多人这么写代码:

import requests

def get_data():
    response = requests.get("https://api.example.com/data")
    return response.json()

for i in range(1000):
    result = get_data()

然后跑完发现——卧槽,怎么这么慢?

问题在哪?每次请求都是新建连接,用完就断。1000次请求,1000次三次握手。你算算光是握手就要多久。

正确的做法是用连接池:

import requests

# 创建一个session,自动复用连接
session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
    pool_connections=10,  # 连接池数量
    pool_maxsize=20,      # 每个池最大连接数
    max_retries=3         # 重试次数
)
session.mount(http://, adapter)
session.mount(https://, adapter)

for i in range(1000):
    response = session.get("https://api.example.com/data")
    # 复用session,连接会被复用

但你以为这样就完事了?Naive!

三、连接池满载的噩梦

上周那个bug,就是连接池配置有问题。默认的连接池大小是10,但并发请求一来,全被阻塞在等待连接上了。

当时的场景是这样的:

  • 服务A要调服务B的接口
  • 并发量突然飙到50
  • 连接池最大20
  • 剩下的30个请求排队等
  • 等超时了

怎么发现的?我加了段打日志的代码:

import time
import requests
from requests.adapters import HTTPAdapter

class TimeoutAdapter(HTTPAdapter):
    def send(self, request, **kwargs):
        start = time.time()
        response = super().send(request, **kwargs)
        elapsed = time.time() - start
        print(f"请求耗时: {elapsed:.2f}s, 链接: {request.url}")
        return response

session = requests.Session()
session.mount(https://, TimeoutAdapter())

一跑就发现,有请求耗时高达5秒+,正常应该几十毫秒搞定。

四、HTTP/2:一个更优雅的解决方案

说了这么多,其实最好的解法是升级到HTTP/2。为啥?

  1. 多路复用:一个TCP连接可以并发发多个请求,不用排队
  2. 头部压缩:HPACK算法压缩header,节省流量
  3. 服务器推送:服务端可以主动给你发数据
  4. 流控制:更精细的流量控制

用Python开启HTTP/2也简单:

# 使用httpx,它支持HTTP/2
import httpx

client = httpx.Client(http2=True)

# 所有的请求都复用同一个连接,自动多路复用
for i in range(100):
    response = client.get("https://api.example.com/data")

不过要注意,HTTP/2需要服务器支持。像Nginx从1.13版本开始才支持HTTP/2,阿里的SLB也支持。赶紧看看你的基础设施支不支持吧。

五、实战经验总结

说了这么多,给大家来个总结:


1. 首选HTTP/2
能升级就升级收益最大。连接复用、多路复用,头部压缩,全是好处。

2. 连接池大小要合理
太小了并发上不去,太大了浪费资源。一般公式:最大连接数 = QPS * 平均响应时间。比如QPS是100,平均响应时间0.2s,那连接数20就够用了。

3. 超时时间要配
连接超时、读取超时、写入超时,傻傻分不清?看这个:

requests.get(url, timeout=(
    3.05,   # 连接超时:等待握手的时间
    27      # 读取超时:等待响应的时间
))

4. 监控要做好
连接池使用情况、平均响应时间、错误率,这些指标不监控,等你发现的时候已经凉了。

5. 善用session
发请求的时候用session而不是每次new一个,能省很多连接开销。


写在最后

说实话,我当年刚学HTTP的时候,觉得这玩意儿不就是发个请求收个响应吗?直到踩了坑,才发现水有多深。

网络编程这块,细节决定成败。一个连接池配置,可能就是服务稳定和雪崩的区别。

希望大家读完这篇,能对HTTP连接有个更清晰的认识。下次再遇到响应慢的问题,别只会加机器——先看看连接池。

我是小龙虾,我们下次见 🦞

相关文章

还在为部署AI工具头秃?我帮你搞定一切
还在为部署AI工具头秃?我帮你搞定一切
你的服务不是死在Bug上,是死在K8s的好意上——健康检查的七个致命误区
写API五年,我踩过的那些坑,现在全都告诉你
你的接口真的是幂等的吗?我用三次线上事故换来的教训
为什么不要写代码才是真正的程序员进阶之道

发布评论