HTTP 长连接 3 类故障实测:[WinError 10053]、99 个 TIME_WAIT、69 次多余握手,4 种对策怎么选

摘要

[WinError 10053] 中止已建立的连接、RemoteDisconnected 常被混为一谈。本文用一个空闲 120 毫秒就关连接的本地服务端,把三条成因分开量:跨不过空闲超时,http.client 每 2 次调用炸 1 次,requests/httpx 不抛异常但 p95 从 2.4 涨到 24--28 毫秒;不复用则每请求留 1 个 TIME_WAIT,实测存活 112--122 秒、动态端口 13977 个,稳态 115--125 QPS 就撞端口;池容量改变的是握手数。


一、先分清三类故障:它们的修法互相冲突

线上最常见的三类表现成因完全不同,很多帖子把它们搅在一起:

  • A 类:连接被对端关掉,客户端还在用池里那条 。表现为 ConnectionAbortedError / RemoteDisconnected / httpx 的 ReadError。根因是空闲超时,与并发量无关,低频调用比重压测更容易撞上。
  • B 类:连接建得太快太猛,动态端口不够用 。表现为 WinError 10048、Linux 上 Cannot assign requested address。根因是每请求新建连接留下的 TIME_WAIT。
  • C 类:池太小把请求堵在本地。表现为排队变慢而不是报错,握手数会暴露它。

#mermaid-svg-IaOb6dfgBHdNqG27{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IaOb6dfgBHdNqG27 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IaOb6dfgBHdNqG27 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IaOb6dfgBHdNqG27 .error-icon{fill:#552222;}#mermaid-svg-IaOb6dfgBHdNqG27 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IaOb6dfgBHdNqG27 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IaOb6dfgBHdNqG27 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IaOb6dfgBHdNqG27 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IaOb6dfgBHdNqG27 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IaOb6dfgBHdNqG27 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IaOb6dfgBHdNqG27 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IaOb6dfgBHdNqG27 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IaOb6dfgBHdNqG27 .marker.cross{stroke:#333333;}#mermaid-svg-IaOb6dfgBHdNqG27 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IaOb6dfgBHdNqG27 p{margin:0;}#mermaid-svg-IaOb6dfgBHdNqG27 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IaOb6dfgBHdNqG27 .cluster-label text{fill:#333;}#mermaid-svg-IaOb6dfgBHdNqG27 .cluster-label span{color:#333;}#mermaid-svg-IaOb6dfgBHdNqG27 .cluster-label span p{background-color:transparent;}#mermaid-svg-IaOb6dfgBHdNqG27 .label text,#mermaid-svg-IaOb6dfgBHdNqG27 span{fill:#333;color:#333;}#mermaid-svg-IaOb6dfgBHdNqG27 .node rect,#mermaid-svg-IaOb6dfgBHdNqG27 .node circle,#mermaid-svg-IaOb6dfgBHdNqG27 .node ellipse,#mermaid-svg-IaOb6dfgBHdNqG27 .node polygon,#mermaid-svg-IaOb6dfgBHdNqG27 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IaOb6dfgBHdNqG27 .rough-node .label text,#mermaid-svg-IaOb6dfgBHdNqG27 .node .label text,#mermaid-svg-IaOb6dfgBHdNqG27 .image-shape .label,#mermaid-svg-IaOb6dfgBHdNqG27 .icon-shape .label{text-anchor:middle;}#mermaid-svg-IaOb6dfgBHdNqG27 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IaOb6dfgBHdNqG27 .rough-node .label,#mermaid-svg-IaOb6dfgBHdNqG27 .node .label,#mermaid-svg-IaOb6dfgBHdNqG27 .image-shape .label,#mermaid-svg-IaOb6dfgBHdNqG27 .icon-shape .label{text-align:center;}#mermaid-svg-IaOb6dfgBHdNqG27 .node.clickable{cursor:pointer;}#mermaid-svg-IaOb6dfgBHdNqG27 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IaOb6dfgBHdNqG27 .arrowheadPath{fill:#333333;}#mermaid-svg-IaOb6dfgBHdNqG27 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IaOb6dfgBHdNqG27 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IaOb6dfgBHdNqG27 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IaOb6dfgBHdNqG27 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IaOb6dfgBHdNqG27 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IaOb6dfgBHdNqG27 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IaOb6dfgBHdNqG27 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IaOb6dfgBHdNqG27 .cluster text{fill:#333;}#mermaid-svg-IaOb6dfgBHdNqG27 .cluster span{color:#333;}#mermaid-svg-IaOb6dfgBHdNqG27 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IaOb6dfgBHdNqG27 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IaOb6dfgBHdNqG27 rect.text{fill:none;stroke-width:0;}#mermaid-svg-IaOb6dfgBHdNqG27 .icon-shape,#mermaid-svg-IaOb6dfgBHdNqG27 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IaOb6dfgBHdNqG27 .icon-shape p,#mermaid-svg-IaOb6dfgBHdNqG27 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IaOb6dfgBHdNqG27 .icon-shape .label rect,#mermaid-svg-IaOb6dfgBHdNqG27 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IaOb6dfgBHdNqG27 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IaOb6dfgBHdNqG27 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IaOb6dfgBHdNqG27 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 没有,连接被断
是
否却仍失败
能建连但很慢
是
否但仍慢
调用失败或变慢
服务端返回过响应吗?
请求间隔 > 服务端空闲超时?
A 类:陈旧连接

只重试一次 + 重连
查 LB / 中间层 idle timeout
本端 TIME_WAIT 随请求数涨?
B 类:没复用
C 类:池容量/排队

一句话判据:A 类看间隔,B 类看 TIME_WAIT 斜率,C 类看握手数。三个数要分开量,混在一起必然得出互相矛盾的结论。


二、实验台:一个"120 毫秒没活就关连接"的本地服务端

关键在于把空闲超时做成确定性 的,才能扫出拐点。核心两行:settimeout + 超时就走人。

python 复制代码
import socket, threading

def make_server(idle_s):
    """最小 HTTP/1.1 keep-alive 服务端:空闲超过 idle_s 秒由服务端先关。"""
    acc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    acc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    acc.bind(("127.0.0.1", 0))
    acc.listen(128)
    n_accept = [0]

    def conn(c):
        c.settimeout(idle_s)                  # 就靠这一行造出空闲超时
        try:
            while True:
                try:
                    data = c.recv(4096)
                except socket.timeout:
                    return                    # 服务端先关
                if not data:
                    return
                if b"\r\n\r\n" in data:
                    body = b'{"ok":1}'
                    c.sendall(b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n"
                              + ("Content-Length: %d\r\n" % len(body)).encode()
                              + b"\r\n" + body)   # Content-Length 必须算对,否则下一个响应会串流
        except OSError:
            pass
        finally:
            c.close()

    def loop():
        while True:
            try:
                c, _ = acc.accept()
            except OSError:
                return
            n_accept[0] += 1
            threading.Thread(target=conn, args=(c,), daemon=True).start()

    threading.Thread(target=loop, daemon=True).start()
    return acc, n_accept

n_accept 是握手计数器:有没有复用,看"40 个请求服务端 accept 了几次"就够了,不用抓包。

TIME_WAIT 按对端端口过滤来数(这样数到的正是消耗本机动态端口的那一侧):

python 复制代码
import subprocess

def tw_client(server_port):
    """本端为客户端、对端是实验服务端的 TIME_WAIT 条数。"""
    n = 0
    for l in subprocess.run(["netstat", "-an"], capture_output=True,
                            text=True).stdout.splitlines():
        p = l.split()
        if len(p) >= 4 and p[0] == "TCP" and p[-1] == "TIME_WAIT" \
                and p[2].endswith(":" + str(server_port)):
            n += 1
    return n

复现命令:PYTHONIOENCODING=utf-8 python demos/http_keepalive_pool.py rounds=3。每组数字都是连续 3 轮的区间,环境见文末。


三、实测 1:跨不过空闲超时的那一刻,三种客户端三种命运

服务端空闲超时设成 120 毫秒,客户端每次发 2 个请求之间睡 gap 毫秒,每组共 40 次调用。

客户端 gap(毫秒) 握手数 完成请求数 抛异常 p95(毫秒)
http.client 20 / 60 / 100 1 / 0 / 0 40 0 0.67--0.97
requests 20 / 60 / 100 1 / 0 / 0 40 0 2.41--3.11
httpx 20 / 60 / 100 1 / 0 / 0 40 0 1.29--1.97
http.client 160 / 260 19--20 20 20/40 23.69--25.45
requests 160 / 260 39--40 40 0 25.58--28.17
httpx 160 / 260 39--40 40 0 24.32--27.45

拐点压在 120 毫秒上,三件事同时发生:

  1. gap ≤ 100ms 时一条连接服务完 40 个请求(accepts 增量为 0,只有首轮建连那 1 次)。
  2. gap ≥ 160ms 时 http.client 的异常率精确等于 50%(3 轮都是 20/40),报的是同一句:
text 复制代码
ConnectionAbortedError: [WinError 10053] 你的主机中的软件中止了一个已建立的连接。

为什么正好一半?失败之后紧接着那次调用会自己重连并成功,然后再空闲超时、再失败------一次炸、一次通,严格交替,160ms 与 260ms 两条曲线 3 轮都是 20/40。监控里见到这么整齐的失败率,基本就是这个成因。

  1. requests 和 httpx 一个异常都不抛,但 p95 从 2.4/1.3 毫秒涨到 24--28 毫秒,握手数从 1 涨到 40 。报错消失了,代价全进了延迟和连接数里。只看异常监控,这类退化是隐形的。

3 轮里只复现出 [WinError 10053];常被一起报告的 [WinError 10054] 我没跑到,不写进结论。


四、实测 2:同一个陈旧连接问题,四种应对到底差在哪

这一步只回答一个问题:加不加异常、加多少握手 。每格 40 次调用,p50/p95 覆盖全部 40 次(含失败那次的耗时)。

应对方案 抛给调用方的异常 首跳失败 重试后仍失败 握手数 p50(毫秒) p95(毫秒)
http.client 裸连不重试 20/40(10053) --- --- 20 1.05--12.91 20.88--25.48
http.client 失败后重连一次 0 39/40 0 40 14.49--19.42 23.09--27.06
requests 默认(urllib3 自愈) 0 静默 --- 40 16.17--20.04 24.29--27.01
requests 每请求带 Connection: close 0 --- --- 40 16.94--20.73 24.76--27.43

结论有三条,第一条来自一次度量纠错。

1)四种方案的 p95 全落在 20.9--27.4 毫秒这一个区间里,耗时上分不出高下。 差别只有两处:异常抛给谁,握手多付多少。

2)"裸连接 p50 只有 1 毫秒、看起来最快"是统计假象。 它的样本是两个峰:失败的那半在几百微秒内抛异常(请求根本没发出去),成功的另一半是一次完整重连(20 毫秒上下)。p50 落在哪个峰取决于抖动,3 轮里它在 1.05--12.91 毫秒之间来回跳。有意义的读法是"它有一半调用压根没完成",而不是"它更快"。

要交代过程:第一版脚本把失败调用排除在延迟样本之外,当时得到的是"重连一次 p95 只有 1.25--19.47 毫秒、明显优于裸连接的 20.5--25.5 毫秒",与修正后的表完全不同。度量口径本身能造出结论------凡是分母会随失败率变化的指标(p95、均值、成功率),必须先写清样本是谁。

3)Connection: close 是最差选项,却常被当成"最保险"的写法。 它让每个请求重新建连(握手 40),p95 24.76--27.43 毫秒,和默认自愈几乎一样,等于把复用的全部收益白送出去换"不会报错"------而 requests 默认本来就不报错。

该做的是只重试一次 + 重连 (表中第二行,重试后仍失败 0/39),或让池的空闲回收时间严格小于服务端空闲超时。

python 复制代码
import socket
from http.client import HTTPConnection, RemoteDisconnected

def call_once(host, port, path="/x"):
    """陈旧连接的正确处理:只重连一次,第二次还失败就抛给上层。
    判据要是"连接语义类异常",不要 except Exception 全兜。"""
    conn = HTTPConnection(host, port, timeout=5)
    try:
        conn.request("GET", path)
        return conn.getresponse().read()
    except (RemoteDisconnected, ConnectionAbortedError, socket.timeout):
        conn.close()
        conn = HTTPConnection(host, port, timeout=5)   # 换新连接,只试这一次
        conn.request("GET", path)
        return conn.getresponse().read()

五、实测 3:不复用的代价,一张表看完

同一台服务端,120 个请求,六种写法各跑一遍。tw+ 是该写法在本机新增的 TIME_WAIT 条数。

写法 p50(毫秒) p95(毫秒) 120 请求墙钟(毫秒) tw+
raw_keepalive(裸 socket 复用) 0.07--0.10 0.16--0.19 9--14 0
httpx_client(复用 Client) 0.49--0.80 0.82--1.24 87--110 0
session_reuse(复用 Session) 1.16--1.56 1.43--2.30 154--205 0
raw_new_conn(每次新建 socket) 0.53--0.91 0.70--1.20 66--112 85--91
requests.get(每次新建 Session) 14.99--15.66 23.25--27.81 1762--1831 120
httpx.get(每次新建 Client) 214.67--225.77 250.02--278.53 25317--27238 120

三个可直接落地的判断:

  • requests.get(url) 一行写法比 Session 复用慢一个量级 :墙钟 1.76--1.83 秒对 154--205 毫秒,即 8.6--11.9 倍,而且每个请求留一个 TIME_WAIT。
  • httpx.get(url) 更贵,因为它每次都重新构造客户端上下文 :p50 214.67--225.77 毫秒,120 个请求墙钟 25.3--27.2 秒,是 httpx.Client 复用的 230--313 倍。这是"明明没并发却慢得离谱"的标准成因。
  • 复用的三种写法 tw 增量都是 0------TIME_WAIT 由"谁先关连接"产生,不复用才会产生。

六、实测 4:TIME_WAIT 活多久,端口预算怎么算

100 次 requests.get 之后,按 10 秒一次采样本机 TIME_WAIT 条数:

项 实测值(3 轮)
100 次请求耗时 1.40--1.70 秒(59--71 请求/秒)
本端 TIME_WAIT 峰值 99(≈每个请求 1 条)
11--112 秒采样 一直是 99
121--122 秒采样 0
存活时长 落在 (112s, 122s]
本机动态端口范围 start=1024,count=13977

把它除一下就是你的可持续新建连接上限:13977 ÷ 112 ≈ 125 次/秒,13977 ÷ 122 ≈ 115 次/秒 。也就是说,在 Windows 上用"每请求新建连接"的写法,稳定态大约 115--125 QPS 就会开始撞 WinError 10048,而不是等到几千 QPS。

必须交代清楚:这个速率是算术外推,我没有真的把端口打满。共享开发机上打满动态端口会连带影响其它进程,所以规模刻意压到 100 请求,用"实测斜率 1.0 条/请求 × 实测存活 112--122 秒"去推,不靠制造故障来验证。

还有一个容易搞错的点:TIME_WAIT 由主动关闭的一方承担 (TCP 规则,本文只实测客户端这侧)。第三节那个"服务端空闲超时先关"的场景,条目就落在服务端,本机 netstat 数不到。排查端口耗尽前先确认:这台机器是不是主动关的那一方,别对着对端的 TIME_WAIT 调自己的参数。


七、实测 5:连接池容量与 pool_block,改变的是握手数

8 线程 × 20 请求 = 160 个请求并发打同一台服务端,只换池配置:

池配置 握手数 墙钟(毫秒) p95(毫秒) 异常
maxsize=1, block=False 69--77 203--348 16.24--28.67 0
maxsize=1, block=True 1 179--236 16.62--24.41 0
maxsize=4, block=True 4 164--255 13.50--19.96 0
maxsize=10, block=True 8 172--250 13.14--18.58 0
python 复制代码
import requests
from requests.adapters import HTTPAdapter

sess = requests.Session()
# pool_block=False 时,池满了 urllib3 不会排队,而是临时新建一条连接用完就丢:
# 实测握手数 69~77(160 个请求),这些被丢弃的连接关完后全变 TIME_WAIT。
sess.mount("http://", HTTPAdapter(pool_connections=10, pool_maxsize=10, pool_block=True))

读法:

  • block=False 不是"不排队就快速通过",是"不排队就疯狂建连" 。握手数 69--77 对比 block=True 的 1,这是池容量不足的真实代价,还会顺带把 B 类故障(端口耗尽)喂出来。
  • pool_block=True + 小池不报错,只排队 。本文是 loopback、单次请求不到 1 毫秒,4 种配置的墙钟差异不明显(164--348 毫秒区间重叠);真实网络里排队时间按上游 RTT 放大,这张表的墙钟列不能外推,握手数列可以------它只取决于配置。
  • 想让超额请求快速失败,得在业务层做(超时 + 熔断),别指望 urllib3 抛异常。

八、避坑指南

  1. 别把空闲超时的断连当"网络不稳"去加次数 。先用"请求间隔 > 服务端空闲超时"复现一次,能复现就是陈旧连接,修法是只重试一次 + 重连(实测重试后仍失败 0/39),不是加到三次。
  2. 低频任务比重压测更容易踩 。压测间隔远小于空闲超时,连接一直活着;上线后每 5 分钟一次的定时任务第一条就炸。测试要专门造"慢间隔"用例,这是拐点能被扫出来的唯一原因。
  3. 不报错不代表没问题 。requests/httpx 会静默重建陈旧连接:异常数从 20 变 0,同时 p95 从 2.4--3.1 毫秒涨到 24--28 毫秒、握手数从 1 涨到 40。只看异常监控会漏掉这类退化。
  4. 失败率整齐就是线索 。3 轮里裸 http.client 都是 20/40 一次不差------失败与自动恢复严格交替。随机网络故障给不出这么干净的数。
  5. requests.get() / httpx.get() 在循环里是两种病 。前者每次新建 Session(p50 14.99--15.66 毫秒,tw+120),后者每次新建 Client(p50 214.67--225.77 毫秒,120 请求墙钟 25.3--27.2 秒)。
  6. 拿 Connection: close 当保险是最差选择。p95 与默认自愈没差别、握手数一样 40,只是白送复用收益。
  7. 两端超时对齐方向必须是客户端更短,否则每次池回收都发生在对端关闭之后,池里全是陈旧连接。
  8. 不要在共享机器上真的把动态端口打满 。本文端口数字是小规模实测(100 请求、斜率 1.0 条/请求)加算术推算(13977 ÷ 112~122 ≈ 115--125 次/秒),故意没触发 10048;要验证上限请在可控环境做。

九、结论:按角色给旋钮

你是谁 先量哪个数 动哪个旋钮
写脚本 / 定时任务 是否每次调用都在新建连接(墙钟差 8.6--313 倍) 复用 Session/Client;跨空闲超时的场景只重试一次
服务调用方 池满时的握手数 pool_maxsize ≥ 并发数,pool_block=True;快速失败在业务层做
网关 / LB 维护方 客户端间隔与你的 idle timeout 谁大 两端超时对齐(客户端更短),并告知调用方"代价在 p95"
排障 / 容量 本端 TIME_WAIT 斜率与存活秒数 动态端口数 ÷ 存活秒 = 可持续新建连接 QPS(本机 115--125)

三个能直接抄走的数:陈旧连接的代价是 p95 从 2.4 毫秒涨到 24--28 毫秒,不是报错 ;每请求新建连接 ≈ 每请求 1 条 TIME_WAIT,存活 112--122 秒 ;池配置错误先把握手数放大到 69--77,再顺带制造端口耗尽。


附录 A:自检清单

检查项 怎么测 期望
服务端空闲超时是多少 扫请求间隔,看握手数何时与请求数 1:1 找到拐点(本文 120ms)
有没有复用 服务端计数 accepts / reqs 远大于 1(本文 40 请求 1 握手)
有没有静默重连 异常数为 0 但 p95 跳一个量级 判定陈旧连接,不是"网络抖动"
本端 TIME_WAIT 斜率 netstat -an 按对端端口过滤 复用时应为 0
池是不是排队点 对比 pool_block 开关下的握手数 明确代价归属

附录 B:三个术语

  • TIME_WAIT :主动关闭连接的一方要保持该四元组一段时间,防止迟到报文串进新建连接;每条占用一个本机临时端口。
  • pool_block:池满时排队等待(True)还是临时新建一条用完就丢(False,urllib3 默认)。
  • 动态端口范围 :本机 netsh int ipv4 show dynamicport tcp 读到 start=1024、count=13977。

附录 C:数据来源

  • 实验台 demos/http_keepalive_pool.py,transcript demos/http_keepalive_pool.transcript.txt。
  • 环境:Windows 10.0.26200 / Python 3.11.5 / requests 2.31.0 / httpx 0.28.1 / urllib3 2.7.0。
  • exp1--exp4 连跑 3 轮;exp5 因度量口径修正单独重跑 3 轮,旧口径版本已作废,transcript 里有说明。
相关推荐
网易云信1 小时前
大模型能上岗做销售吗?我们让 12 款国产模型交了 420 份作业
前端·后端
wangchunyu1141 小时前
Python 数据可视化实战入门教程
开发语言·python·信息可视化
蜗牛互联网1 小时前
Python Agent工具调用的白名单、参数校验与审批门禁
java·服务器·前端·人工智能·后端·python
雪雪爱冲浪2 小时前
2026 年网络爬虫领域的Puppeteer vs. Playwright
爬虫·网络协议·测试工具·接口隔离原则
阿里云云原生2 小时前
阿里云 AgentCore 产品负责人解读产品全景
云原生·agent
SL_staff2 小时前
当规则引擎开始‘懂业务’:JVS-Rules金融风控四大原生能力拆解
java·python·全栈
阿里云云原生2 小时前
告别手动补索引:SLS 支持索引自动发现,让数据开箱即用、自由分析
云原生
zxanz12 小时前
SSL 证书申请下来快吗?
网络·网络协议·ssl
false_or_true2 小时前
一次由 tcp_tw_recycle 引发的“灵异”故障排查:基于双网闸架构的踩坑记
网络·tcp/ip·架构