观察、决策、行动三个方框,旁边强调多路复用不改变 TCP 的有序交付。

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

这里的“消除队头阻塞”,指解除同一条连接上的 HTTP 响应顺序约束,而不是消除 TCP 队头阻塞。较晚发起的响应可以在较早的处理函数仍然等待时完成,但共享的传输容量、连接级流控和应用瓶颈,依然可能把不同流的进度关联起来。先说明这一层边界,才能区分应用协议的顺序约束与底层传输等待。 分清两种排队 HTTP/1.1 本来就支持持久连接,并非一个请求必须新建一条连接。多个请求可以顺序复用套接字;流水线还允许同时存在多个未完成请求,服务器也可以并行处理安全方法,但同一条连接上的响应必须按照请求顺序发送。因此,第一份响应过慢,会延迟后续响应的交付。客户端经常通过多开连接缓解这个限制。 HTTP/2 给帧关联流标识,在同一条连接上交错传递消息。服务器可以发送已经准备好的响应,不必先完成另一份无关响应。这是 HTTP 层的独立推进能力,不代表每个处理函数获得独占处理器、数据库、带宽或无限调度机会,也不意味着应用的共享资源自动变成互不影响。 TCP 仍然提供一个有序字节流。某段字节缺失时,后面已经收到的字节可能必须等待补齐缺口,才能交付应用,其中也可能包含其他 HTTP/2 流的帧。此前已经交付的字节并不会被收回。QUIC 则在每条流内保持有序,不要求不同流之间统一排序;不能把它解释成普通流数据可以任意乱序交付,也不能据此忽略共享的拥塞控制和流控预算。 并发仍然受到预算约束 对端通过 SETTINGS_MAX_CONCURRENT_STREAMS 限制活动流数量。DATA 帧还同时消耗流级和连接级的流控额度。每条流拥有单独窗口,不等于无限独立:连接额度耗尽时,其他流的 DATA 也可能停住。接收方如何消费正文、何时更新窗口,以及共享工作池、锁和上游连接是否紧张,都会改变实际推进情况。 应检查所测那一跳真正协商出的协议。浏览器到代理使用 HTTP/2,不能证明代理到源站也是如此;TLS 握手成功也不足以证明使用了 HTTP/2。Go 配置自定义 TLS 参数后,可能需要 ForceAttemptHTTP2 才会尝试 HTTP/2,但最终证据仍是响应协议和协商出的 h2,而不是配置字段已经存在。 坏掉的样子 快请求先完成,可能只是客户端另开了一条 TCP 连接;单连接 HTTP/1.1 客户端则可能在请求抵达服务器之前,就已经让它等待。应同时核对服务端接受的连接数量和客户端获取连接的事件,再判断排队发生在哪里。仅凭两条访问日志的结束时间,无法区分这些情况。 增加流并发量,不能修复过载数据库,也不能修复未读数据造成的共享窗口紧张。应记录正文完成情况、活动流、共享资源等待以及逐跳协议。不要把每次慢请求都称为 TCP 队头阻塞,更不要把升级协议直接写成已经实测的页面提速;性能结论需要独立的负载与网络测量支持。 ...

九月 15, 2026 · 3 分钟 · XB Field Notes
两行标题旁边的三个相连方块,依次标注 Observe、Decide 与 Act。

CORS 决定脚本能否读取响应

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

九月 13, 2026 · 2 分钟 · XB Field Notes
标题卡写着“相同效果,不同响应”,旁边排列观察、决策与行动面板。

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
观察、决策与行动三块面板,旁边的标题说明共享预算需要原子检查。

限流是共享预算

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

八月 22, 2026 · 3 分钟 · XB Field Notes
浅色网格上的标题为‘健康检查需要明确问题’,旁侧是观察、判断、行动三个步骤。

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

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

八月 19, 2026 · 2 分钟 · XB Field Notes
两只分别标注新鲜度与校验的钟,之间是一个 ETag。

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

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

八月 14, 2026 · 1 分钟 · XB Field Notes
客户端遍历活动边缘返回的响应元数据,从方法到范围与缓存。

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

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

八月 7, 2026 · 3 分钟 · XB Field Notes
观察、决策、行动三个方框,旁边强调按策略跟随跳转并保留方法。

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

重定向是一份响应,要求客户端在另一个目标上发起新请求。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