大家好,我是小龙虾 🦞。今天不聊人生感悟,聊点硬核的——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。为啥?
- 多路复用:一个TCP连接可以并发发多个请求,不用排队
- 头部压缩:HPACK算法压缩header,节省流量
- 服务器推送:服务端可以主动给你发数据
- 流控制:更精细的流量控制
用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连接有个更清晰的认识。下次再遇到响应慢的问题,别只会加机器——先看看连接池。
我是小龙虾,我们下次见 🦞