同一 TCP 连接上,慢处理函数等待时快 HTTP/2 响应完成;缺失 TCP 字节范围阻塞两个流的后续字节。

HTTP/2 消除 HTTP 响应顺序阻塞

这里的“消除队头阻塞”,指解除同一条连接上的 HTTP 响应顺序约束,而不是消除 TCP 队头阻塞。较晚发起的响应可以在较早的处理函数仍然等待时完成,但共享的传输容量、连接级流控和应用瓶颈,依然可能把不同流的进度关联起来。先说明这一层边界,才能区分应用协议的顺序约束与底层传输等待。 分清两种排队 HTTP/1.1 本来就支持持久连接,并非一个请求必须新建一条连接。多个请求可以顺序复用套接字;流水线还允许同时存在多个未完成请求,服务器也可以并行处理安全方法,但同一条连接上的响应必须按照请求顺序发送。因此,第一份响应过慢,会延迟后续响应的交付。客户端经常通过多开连接缓解这个限制。 HTTP/2 给帧关联流标识,在同一条连接上交错传递消息。服务器可以发送已经准备好的响应,不必先完成另一份无关响应。这是 HTTP 层的独立推进能力,不代表每个处理函数获得独占处理器、数据库、带宽或无限调度机会,也不意味着应用的共享资源自动变成互不影响。 TCP 仍然提供一个有序字节流。某段字节缺失时,后面已收到的字节可能必须等待补齐,才能交付应用,其中也可能包含其他 HTTP/2 流的帧;此前已交付字节不会被收回。HTTP/3 使用 QUIC,每条流内仍保持有序,但没有跨流的统一传输交付顺序。共享拥塞与流控预算、应用依赖和 QPACK 头部依赖仍可能产生等待。这是另一协议的规范背景,不是本例执行了 HTTP/3 实验。 并发仍然受到预算约束 对端通过 SETTINGS_MAX_CONCURRENT_STREAMS 限制本方向向它发起的流。非空 DATA 载荷同时消耗流级和连接级额度,计入填充,不计帧头。连接额度耗尽可能阻塞其他流的 DATA;但携带 END_STREAM 的空 DATA 帧仍可在零额度时发送,控制帧不消耗这些窗口。接收方消费正文、更新窗口,以及共享工作池、锁和上游连接的情况,仍会影响实际进度。 应检查所测那一跳真正协商出的协议。浏览器到代理使用 HTTP/2,不能证明代理到源站也是如此;TLS 握手成功也不足以证明使用了 HTTP/2。Go 配置自定义 TLS 参数后,可能需要 ForceAttemptHTTP2 才会尝试 HTTP/2,但最终证据仍是响应协议和协商出的 h2,而不是配置字段已经存在。 ...

九月 15, 2026 · 3 分钟 · XB Field Notes
简单请求已送达接口,但缺少允许来源头的二百响应在浏览器处停止暴露,脚本无法读取。

CORS 决定脚本能否读取响应

跨源资源共享允许服务端选择是否向其他来源的脚本暴露响应。一个来源由协议、主机和端口组成,因此即使在同一台机器上,端口不同的两个服务也可以属于不同来源。浏览器会结合发起页面的来源检查 CORS 响应头。这套机制不会认证当前用户,也不会把接口变成只能从私有网络访问的服务。它回答的是特定页面的脚本能否获得响应内容,而不是业务操作本身是否获准。同站 Cookie 规则与同源响应访问也不是同一条边界,不能只看主机名相似,就推断两种机制都会采用相同结果。 对于允许访问的页面,接口可以在 Access-Control-Allow-Origin 中返回该页面序列化后的来源,浏览器再检查其他相关条件,决定是否暴露响应。某些公开供无凭据跨源读取的资源可以使用通配符,但通配符不能授权凭据模式为 include 的请求。服务端应根据明确规则选择允许的来源,不能把客户端任意传入的来源原样回显,就当成已经完成了访问策略判断。凭据模式为 include 时,暴露响应还要求 Access-Control-Allow-Credentials: true;这些响应头不会覆盖浏览器的 Cookie 发送策略。 图示概括文章中简单请求的预期行为,并非本次运行结果。两个请求都会到达接口;CORS 管响应暴露,认证、授权与 CSRF 防护仍须独立设计。 分清请求送达与响应可读 一部分通常称为简单请求的操作不需要 CORS 预检。例如使用纯文本内容类型、且脚本只设置安全列表内请求头的 POST,可以满足相关条件。这里的安全列表是浏览器协议术语,并不意味着接口只读,也不意味着请求不会修改数据。普通 HTML 表单早已能够跨来源发送相似请求,而无须事先取得读取目标响应的权限,因此不能把预检有无理解成业务副作用大小。 浏览器完全可能先发送请求,收到二百状态响应,然后因为响应暴露没有获准而让脚本的 fetch 失败。此时服务端可能已经执行完操作。普通非浏览器 HTTP 客户端也不会执行这项页面来源规则。认证、授权以及防止非预期跨站操作,需要独立的服务端设计;不能依靠是否返回某个 CORS 响应头,替代对请求身份、权限和业务意图的检查。 坏掉的样子 常见故障从“浏览器已经拦截,所以服务端什么都没做”这个误判开始。用户重试一个会改变状态的请求,结果因为第一次已经成功执行而造成重复操作。重试之前应检查服务端证据,并明确业务操作自己的重复执行语义。对于依靠 Cookie 认证的状态修改,还要采用合适的跨站请求伪造防护。仅仅不返回允许来源头,不能代替判断这次修改是否应该被接受。 另一种错误,是把所有 fetch 类型错误都当成 CORS 配置问题。网络失败也会导致请求承诺被拒绝,因此要结合浏览器诊断信息、实际收到的响应头和服务端请求记录判断。把请求改成 no-cors,也不会让应用取得普通可读响应;不透明响应的限制决定了它不能成为需要消费接口数据时的通用修复方法。应修正真实的协议或权限配置,而不是只让某条错误提示消失。 ...

九月 13, 2026 · 2 分钟 · XB Field Notes
条件 PUT 把 blue 改成 green,随后响应丢失;再次携带旧 If-Match 在实验中得到 412,而保存值仍为 green。

HTTP 幂等让重试变安全

HTTP 幂等关注的是重复同一个请求时,服务器上预期发生的效果,而不是每次响应是否一模一样。服务器可能已经执行修改,却在客户端收到答案之前断开连接;这时重复请求可能合理,但前提是操作的真实语义允许重复,并且客户端仍遵守有界重试政策。不能把“可以重复”扩大成所有失败都应该立即重试。 相同效果,不代表相同响应 RFC 9110 用多次相同请求与单次请求的预期服务端效果来定义幂等。PUT、DELETE 和安全方法都属于幂等方法。第一次成功 PUT 可能创建资源并返回 201,再次发送则可能替换已有资源并返回 204;先删除存在的资源,再删除已经不存在的资源,也可以返回不同状态码,但最终都让目标保持不存在。 “安全”是另一种性质:客户端没有请求改变状态。GET、HEAD、OPTIONS、TRACE 被定义为安全方法,但允许附带访问日志等行为,因此不是说服务器任何地方都绝对不能写入。PUT 和 DELETE 可以符合幂等语义,却不是安全方法;把两个概念混用,会影响自动抓取与重试策略。 方法名称需要实现来兑现 把一个每次递增的接口命名为 PUT,不会让重复递增自动变成幂等。服务器需要真正兑现公开的方法语义。同样,用 GET 删除指定对象违反安全方法的约定,预取器或爬虫可能在没有删除意图时触发它。不能因为请求行上写着某个词,就跳过对业务状态变化的检查。 POST 通常不保证幂等,但某个具体 POST 操作可以通过应用层契约去重;它的用途也不局限于创建资源。自动重放之前需要理解实际契约,不能断言所有 POST 都不能重试,也不能反过来认定所有使用幂等方法名称的接口都已经正确实现。 天然可重复的写入与记住操作键,解决的是相关但不同的问题。用指定表示替换资源,是表达希望得到的状态;凭借幂等键重放订单创建,则是在识别某次特定行动。两者都不意味着每一个内部函数、访问日志或辅助步骤只会执行一次。 前置条件处理的是竞争修改 幂等本身不能阻止覆盖另一个客户端在两次请求之间写入的新内容。重复无条件替换,可能把自己的旧值重新盖到别人的新值之上。通过强实体标签与 If-Match,可以要求目标仍然是之前观察到的表示,才允许执行这次修改,而不是只比较方法名称。 条件重试也可能在原修改已经成功后失败:第一次写入完成却丢了响应,原先的标签已经不匹配。实验在这种情况下返回 412。RFC 9110 也允许服务器在判断所要求的修改已经应用时返回成功,因此不能把实验的一个选择当成所有服务必须采用的行为。 遇到结果不明确,应重新核对当前资源,或查询相应操作记录。一个状态码不足以还原多个写入者的全部先后历史;读到期望值,也不自动证明它只可能由自己那次请求产生。 坏掉的样子 盲目重试会放大负载、耗尽截止期限、重复不受保护的计费副作用,或覆盖中间写入。需要限制尝试次数和总耗时,保留原来的目标与请求意图,并遵守适用的服务端等待提示。幂等既不是并发控制方案,也不是无限重试的许可。 还应区分预期资源修改与附带日志、版本历史。出现两条诊断记录,本身不能证明业务效果发生了两次;反过来,两份看起来相同的成功响应,也不能证明只执行了一次业务行动。判断重复效果,需要读取有权威性的业务状态,而不是仅比对响应文本。 幂等比较预期状态效果。前置条件防止覆盖中间修改,但成功响应丢失后,旧条件同样可能失效。 快速验证 保存为 methods_check.py,用 Python 3.10 以上版本执行 python3 methods_check.py。实验已在 Python 3.14.4 上运行,只使用标准库。代码会向自己创建的回环 HTTP 服务发送真实请求,端口由系统分配,完成后停止服务;所有业务状态都留在测试进程内存中,不访问线上系统。 ...

八月 26, 2026 · 3 分钟 · XB Field Notes
十六个独立调用方分别对同一个共享预算键调用一次原子 Lua 准入脚本。脚本比较上限,获准时首次创建并设置过期时间,或递增计数。五次获准、十一次拒绝,是预算为五时这十六次调用的合计结果。本图依据原文实验,不是本次运行 Redis 的结果。

限流是共享预算

限流是一份关于获准工作量的预算,需要明确单位、身份、作用范围和时间规则。只说“五次请求”还不够:要说明是谁的请求、覆盖哪些入口与工作进程、按什么区间计数,以及耗尽后如何处理。即使用了共享计数器,也只有所有相关请求都经过同一个权威准入决定,才能落实共享预算。 每个调用方分别对同一预算键调用一次原子脚本。五次获准、十一次拒绝,是十六次调用的实验合计断言。实际内容审计仍将并发 Unix socket 运行标为受阻;本次没有运行 Redis,也不增加竞争或过期验证证据。原子执行不证明故障转移持久性,或与后续业务副作用构成事务。 先定义政策,再开始计数 一次请求可以消耗一个单位,也可以按预计工作量或字节数加权。按准入尝试、成功执行或退还额度分别记账,必须由契约明确规定,不能在实现中悄悄混用。获得预算只意味着允许继续执行,不保证后续业务操作一定成功,也不等于为成功结果预留了全部资源。 窗口算法并不相同。固定窗口可能在边界附近允许突发,滚动窗口衡量的是另一段区间;令牌桶允许迅速花掉已经积累的额度,并不会自动把请求均匀发出。这些算法本身也不限制仍未结束的慢操作数量,准入速率不能替代执行并发上限。 账号、路由、地域和整个服务的预算分别回答不同问题。剩余额度只是某个时刻的快照,并不是为调用方预留的位置:其他工作进程可能先把它用掉。各进程各自维护同样大小的本地计数器,会放大实际额度,除非存在明确的分配规则,将总预算拆给不同参与者。 把准入决定做成原子操作 把数据放进同一个 Redis 键,不会让客户端的一串命令自动变成原子操作。两个调用方可能都读到尚有额度,各自决定继续,再分别递增。每个 INCR 都是原子的,但之前的比较发生在它外面,所以最后总量仍可能超过承诺,不能只检查单条命令的原子性。 下面的示例将比较、首次创建计数器并附加过期时间、后续递增放进一个很短的 Lua 执行。窗口从首次获准请求开始;拒绝既不增加计数,也不延长截止时间。所有调用方传入同一个键和固定的正参数,实验独占这个数值计数器,不与其他业务共享它的写入权限。 这是从首次到达开始计时的窗口,不是持续滑动的配额。脚本执行不被其他命令交错,也不等于自动回滚、持久预留,或与应用业务操作构成同一个事务。Redis 执行脚本时会阻塞其他服务活动,因此应保持脚本简短;连接丢失应视为结果未知,不能直接断言额度从未被扣除。 准确解释拒绝结果 RFC 6585 用 HTTP 429 表示请求速率过高,但把身份选择和计数政策留给服务器决定。响应应解释条件,可以附带 Retry-After;429 响应不得被缓存保存。整个服务暂时不可用,与某个调用方超过自身额度,是两种不同情况,不应仅为了统一返回格式就抹掉区别。 Retry-After 可以表达建议等待时间,却不保证届时一定有额度。如果公开剩余量或重置时间等元数据,接口契约必须定义单位和范围。历史上不同 API 使用的 X-RateLimit 字段,并不由 RFC 6585 赋予一套通用解释;不能因为名字相似,就把某个平台的字段规则当成统一标准。 ...

八月 22, 2026 · 3 分钟 · XB Field Notes
必需输入正常、可选注释缺失时,基础报表仍可提供;已声明政策让就绪和基础报表保持 200。

健康检查端点是在回答一个问题

健康检查端点是运维决策的一项输入,不是证明服务一切正常的通用证书。一个只返回 200 的小端点并非天然没有价值;如果消费者只需要确认这条处理路径能够回答,它就能提供有限但明确的证据。问题出在把“这个处理器回答了”扩大解释为“每一类用户请求都一定成功”。 写清问题,也写清消费者 存活检查关注应用是否进入了适合通过重启恢复的状态;就绪检查关注当前实例是否应该接收其契约覆盖的流量。诊断面板可以汇总更广泛的问题,但显示一项异常,不等于应该授权调度器立即重启实例。这些信号可以相关,却不应该默认触发同一种动作。 消费者决定信号是否真正生效。一个从不访问该端点的路由器,不会因为它开始返回 503 就自动摘除实例;就绪变更也不会撤销已经到达的请求。应写明谁来轮询、什么结果算失败、哪些流量受影响,以及判断传播需要多久,避免只有端点而没有行动链路。 必需依赖不等于所有依赖 严格必需的依赖可以合理地影响就绪:如果缺少必需输入,实例确实可能只能返回错误。但是,一份允许省略附加说明的报表,不应仅因可选注释服务暂时失效就完全退出服务。判断依据应是产品仍然支持什么结果,而不是依赖清单上列了多少组件。 同一个共享依赖故障,可能让全部副本同时不就绪。摘除所有副本不会修复这个依赖,还可能一并停止本来仍可工作的其他路由。把每个后端检查串到同一个流量闸口之前,应确认服务边界和降级响应契约,而不是认为“检查越深就一定越安全”。 远程依赖故障通常不应直接进入存活判断,除非有证据表明重启本进程能修复当前状态。数据库不可用时连续重启所有应用实例,往往只是增加初始化开销,并不能让数据库恢复。存活失败对应的恢复措施,也需要作为检查设计的一部分接受验证。 信号既要廉价,也要有时效 频繁执行的存活与就绪检查,不应反复运行昂贵的业务工作。反复生成报表、扫描数据表或占用紧张的连接,可能把检查对象进一步压垮,也会让探针自身制造的负载混入观察结果。检查耗时和并发开销应有边界,不能无限借用正常请求的资源。 端点可以暴露最近观察到的依赖状态,而不必每轮都同步访问所有后端,但这会改变契约:观察结果能保存多久、过期后如何判断,都必须明确。永久缓存的绿色标记可能掩盖故障,过于激进的过期策略又可能错误地移除健康容量,二者都需要现实证据来校准。 响应应小而明确,不向外暴露凭据、内部拓扑或完整异常栈。Cache-Control: no-store 告诉遵守 HTTP 缓存规则的中间层不要保存响应,但它既不是认证机制,也不能证明端点内部使用的健康观察足够新。响应缓存时效与观测数据时效是两个不同的问题。 坏掉的样子 独立的轻量 HTTP 处理器可能继续回答,而业务工作线程早已死锁;反过来,过深的存活检查也可能把一次外部波动变成重启风暴。无论路径叫 health、live 还是 ready,名字和绿色状态都不能证明所测条件与预期恢复动作真正对应。 路径写错、认证重定向、中间层缓存和探针网络不可达,都可能产生误导结果。因此,应测试消费者实际访问的路径,而不只是在进程内部调用一次函数。过紧的超时和失败阈值,也会让只是暂时繁忙的健康实例反复被摘除,进一步增加剩余实例压力。 Google SRE 区分内部观测与面向用户行为的黑盒检查。可以合理组合两者:从外部请求一份报表,能够发现内部就绪标记遗漏的用户路径故障,但这个外部失败仍不应不经判断就变成重启命令。发现症状、判断原因和决定动作,需要保持清楚的联系。 必需输入决定能否提供报表,可选注释可以省略。依赖标志和 HTTP 响应核对声明的政策,不代表真实故障检测或路由传播。 快速验证 保存为 health_check.py,用 Python 3.10 以上版本执行 python3 health_check.py。实验已在 Python 3.14.4 上运行,只使用标准库。真实 HTTP 服务绑定系统分配的回环端口;依赖只是注入的状态标记,并非真实数据库。实验明确约定:报表可以在缺少可选注释时继续返回基础内容。 ...

八月 19, 2026 · 2 分钟 · XB Field Notes
在 max-age=60 下,年龄 59 秒仍新鲜,直接复用响应;60 与 61 秒已陈旧,进入验证器比较,结果可能为 304 或 200。

两只钟模型中的缓存新鲜度与校验

缓存的连贯性取决于你让哪一对条件显式化。

八月 14, 2026 · 1 分钟 · XB Field Notes
方法、所选字节与缓存组成客户端证据路径;比较时保持相同的表示选择。

像客户端一样读取活动边缘

从方法到响应体再到缓存走一遍真实源站,并带走可复用的检查。

八月 7, 2026 · 3 分钟 · XB Field Notes
Go 实验中,POST 经 301、302、303 变为无正文 GET;经 307、308 则保持 POST 和原来的可重放正文。

重定向是对「去哪」的一份承诺

重定向是一份响应,要求客户端在另一个目标上发起新请求。Location 字段和状态码共同描述去哪以及方法怎样变化。浏览器通常会自动跟随,但客户端库也可以停下、检查或拒绝后续跳转。每一跳都应当作为协议数据记录:只看最终页面,无法知道 POST 是否变成了 GET、凭据是否越过了源边界,或是否有循环消耗了重试预算。 永久和临时表达不同承诺 301 与 308 表示永久迁移,302 与 307 表示临时绕行;303 明确要求用 GET(原请求为 HEAD 时用 HEAD)取得表示。永久状态可能被缓存、写入书签和搜索索引,所以维护窗口不能随意使用永久码。临时状态告诉客户端下次仍应访问旧地址。RFC 9111 的缓存规则和资源策略仍然有效,“永久”不是忘记授权、信任任意主机或跳过证书检查的指令。 方法是否保留要看状态码 出于历史兼容,许多客户端在 301、302 或 303 后把 POST 改为 GET 并丢掉正文;307 和 308 则要求保留原方法与正文,但前提是客户端能够重新读取该正文。重定向不会把非幂等动作变得安全,保留方法也不等于源站已经接受了重放。服务端应选择与操作语义相符的状态,客户端则应确认请求体可重放并匹配合适的重试策略。 相对 Location 要根据有效请求 URI 解析;客户端应记录解析后的每一跳并设置最大跳数。缺少 Location 时没有可跟随的新目标,自循环和过长链是服务器缺陷,不是无限提高上限的理由。跳转也可能改变 scheme、端口或 origin,转发 Cookie、Bearer token 和客户端证书前必须执行显式允许列表;指向另一源的链接不是发送凭据的授权。 ...

七月 28, 2026 · 4 分钟 · XB Field Notes