[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-strapi-article:zh:ai-api-cross-region-regression-after-update":3,"blog-article:\u002Fblog\u002Fai-api-cross-region-regression-after-update":1042,"blog-related:\u002Fblog\u002Fai-api-cross-region-regression-after-update":1043,"blog-translations:\u002Fblog\u002Fai-api-cross-region-regression-after-update":1044},{"available":4,"article":5,"relatedArticles":267,"translations":860},true,{"source":6,"id":7,"documentId":8,"translationKey":8,"locale":9,"slug":10,"path":11,"_path":11,"title":12,"description":13,"excerpt":13,"image":14,"publishedAt":15,"updatedAt":16,"author":17,"tags":18,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":23,"bodyHtml":266},"strapi",3892,"rsorsnolp3dfzuciomy8st3p","zh","ai-api-cross-region-regression-after-update","\u002Fblog\u002Fai-api-cross-region-regression-after-update","AI 产品更新后，如何检查不同地区的 API 请求是否正常","产品更新后如何验证不同地区的 API 请求？本文用成功率、响应结构和耗时对照版本与地区差异，并说明出口核验、停止条件和回滚复验。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_AI_API_cover_2aa40cb615.webp","2026-11-03T02:57:41.793Z","2026-11-03T02:57:41.773Z","Global Proxies Team",[],false,0,3,"blocks",[24,30,36,40,57,61,65,69,87,91,109,113,117,121,125,143,147,151,155,169,173,185,189,193,211,215,219,223,227,231,235,242,246,252,256,262],{"type":25,"children":26},"paragraph",[27],{"text":28,"type":29},"产品更新后的回归，难点不在跑通，而在证明“还是原来那样”。先把“正常”拆成成功率、响应结构和耗时三类可判定指标，再固定请求、版本与地区，用发布前后、地区之间的对照排除波动。不同地区的回归要借助代理 IP 把出口切过去，否则“地区差异”和“发版差异”会混在一起。测试只在自有应用和测试账号中进行，涉及跨境访问时以目标服务的授权范围为准。","text",{"type":31,"level":32,"children":33},"heading",2,[34],{"text":35,"type":29},"怎么判断 API 更新后仍然正常？",{"type":25,"children":37},[38],{"text":39,"type":29},"“正常”不是一次请求返回 200，而是三项可以量化的结果。",{"type":41,"format":42,"children":43},"list","unordered",[44,49,53],{"type":45,"children":46},"list-item",[47],{"text":48,"type":29},"成功率：同一请求集在目标地区的成功比例。",{"type":45,"children":50},[51],{"text":52,"type":29},"响应结构：字段、类型、必填项是否与更新前一致。字段静默缺失比直接报错更难发现。",{"type":45,"children":54},[55],{"text":56,"type":29},"耗时分布：看 p50 和 p95，不看单次最快值。",{"type":25,"children":58},[59],{"text":60,"type":29},"三项都要在更新前测出基线，否则更新后没有比较对象。样本量和观察窗口应按请求波动、风险和团队基线设定；样本较少时，把结果标为初步观察，不作确定归因。",{"type":31,"level":32,"children":62},[63],{"text":64,"type":29},"哪些条件需要在更新前后保持一致？",{"type":25,"children":66},[67],{"text":68,"type":29},"对照前先锁定四类条件，并逐次记录实际值：",{"type":41,"format":42,"children":70},[71,75,79,83],{"type":45,"children":72},[73],{"text":74,"type":29},"变量：版本 ｜ 需要固定：每次请求实际命中的应用 build、依赖与上游版本\u002F标识 ｜ 容易忽略的点：灰度期间同一请求可能落到不同版本",{"type":45,"children":76},[77],{"text":78,"type":29},"变量：地区 ｜ 需要固定：国家、城市、出口 IP ｜ 容易忽略的点：只写国家，定位会漂到邻近城市",{"type":45,"children":80},[81],{"text":82,"type":29},"变量：请求 ｜ 需要固定：URL、请求体、header、账号 ｜ 容易忽略的点：换账号等于换权限，结果不可比",{"type":45,"children":84},[85],{"text":86,"type":29},"变量：客户端 ｜ 需要固定：SDK、HTTP 客户端、超时、重试 ｜ 容易忽略的点：重试次数不同，成功率会被补高",{"type":25,"children":88},[89],{"text":90,"type":29},"发布前后的对照要落到 2x2 矩阵：更新前与更新后、地区 A 与地区 B。只有一格缺失，就无法判断差异来自版本还是地区。",{"type":41,"format":42,"children":92},[93,97,101,105],{"type":45,"children":94},[95],{"text":96,"type":29},"单元格：更新前 × 地区 A ｜ 要记录的内容：版本号、出口、成功率、响应结构、p50 与 p95",{"type":45,"children":98},[99],{"text":100,"type":29},"单元格：更新前 × 地区 B ｜ 要记录的内容：同上，与 A 使用同一请求集和相近时间窗口",{"type":45,"children":102},[103],{"text":104,"type":29},"单元格：更新后 × 地区 A ｜ 要记录的内容：同上，版本号与请求集保持不变",{"type":45,"children":106},[107],{"text":108,"type":29},"单元格：更新后 × 地区 B ｜ 要记录的内容：同上，出口尽量与更新前同一地区一致",{"type":25,"children":110},[111],{"text":112,"type":29},"地区、版本或时间窗口无法全部覆盖时，在结论里直接写出缺哪一格、缺的原因，以及它能带来多大不确定性；缺失的格子不能靠推断补齐。",{"type":31,"level":32,"children":114},[115],{"text":116,"type":29},"怎么确认代理 IP 出口确实位于目标地区？",{"type":25,"children":118},[119],{"text":120,"type":29},"地区对照首先要确认请求实际走的出口路径。使用与被测 API 相同的客户端和代理配置核验，并检查 `NO_PROXY` 与路由；独立运行的本机 curl 可能走不同路径。",{"type":25,"children":122},[123],{"text":124,"type":29},"分别记录 IP 查询库结果、目标服务看到的源 IP\u002F地区判断和业务 locale。语言、时区与币种可能来自用户偏好，不能证明物理位置；来源冲突时并列记录并标记为定位不一致。下表用于让后续复测沿用相同字段：",{"type":41,"format":42,"children":126},[127,131,135,139],{"type":45,"children":128},[129],{"text":130,"type":29},"字段：出口 IP ｜ 示例：203.0.113.24 ｜ 用途：确认复测时没换出口",{"type":45,"children":132},[133],{"text":134,"type":29},"字段：ASN 与组织 ｜ 示例：AS64500（文档占位值），某云机房 ｜ 用途：仅作辅助线索",{"type":45,"children":136},[137],{"text":138,"type":29},"字段：城市 ｜ 示例：目标城市，或邻近城市 ｜ 用途：记录数据库定位结果",{"type":45,"children":140},[141],{"text":142,"type":29},"字段：服务端识别 ｜ 示例：源 IP、地区标签与判定时间 ｜ 用途：记录该服务的识别结果",{"type":25,"children":144},[145],{"text":146,"type":29},"同一地区可用两个出口做配对复测。结果变化只说明出口或路径相关因素可能参与；固定账号、请求、客户端和时间窗口重复测试，再结合链路与服务端证据判断。ASN、组织和数据库标签不能单独证明住宅\u002F机房属性或物理位置，示例中的 AS64500 是文档保留号。",{"type":31,"level":32,"children":148},[149],{"text":150,"type":29},"怎么判断问题来自版本、地区策略还是网络？",{"type":25,"children":152},[153],{"text":154,"type":29},"归因时按证据形成候选方向：",{"type":41,"format":42,"children":156},[157,161,165],{"type":45,"children":158},[159],{"text":160,"type":29},"更新前后有差异：核对实际 build、请求契约和配置。",{"type":45,"children":162},[163],{"text":164,"type":29},"同一时段不同地区有差异：核对服务端错误、账号\u002F模型范围及代理路径。",{"type":45,"children":166},[167],{"text":168,"type":29},"连接或 TLS 阶段变化：结合阶段日志和重复样本检查链路；TTFB 也包含上游处理。",{"type":25,"children":170},[171],{"text":172,"type":29},"同时核对链路、版本和服务端证据；仍不明确时保留未决。",{"type":25,"children":174},[175,177,183],{"text":176,"type":29},"若测试伴随地区版本灰度，可参考",{"text":178,"type":29,"marks":179},"国际站按地区灰度发布时，如何观察错误率并判断是否回滚？",[180],{"href":181,"type":182},"https:\u002F\u002Fwww.quanqiudaili.com\u002Fblog\u002Fregion-error-rate-rollback","link",{"text":184,"type":29},"。",{"type":31,"level":32,"children":186},[187],{"text":188,"type":29},"出现哪些情况应立即停止测试？",{"type":25,"children":190},[191],{"text":192,"type":29},"测试前先写清通过条件、失败阈值、最长运行时间和请求上限，再开始跑样本。出现下面任一情况，停测优先于补样本：",{"type":41,"format":42,"children":194},[195,199,203,207],{"type":45,"children":196},[197],{"text":198,"type":29},"影响非测试账号：立即停测，回滚配置，复盘调用链是否串了账号。",{"type":45,"children":200},[201],{"text":202,"type":29},"费用超出预算：停止请求，核对计费口径，再决定是否继续。",{"type":45,"children":204},[205],{"text":206,"type":29},"目标服务拒绝授权，且提示地区或权限：不要连续重试，先核对可用范围。",{"type":45,"children":208},[209],{"text":210,"type":29},"日志出现未脱敏的凭据：停止测试，按凭据泄露流程处理。",{"type":25,"children":212},[213],{"text":214,"type":29},"停止测试后，保存最后一轮的脱敏请求与响应头、出口 IP、发生时间和当时版本，便于复现异常并核对灰度或配置变化。",{"type":31,"level":32,"children":216},[217],{"text":218,"type":29},"更新通过后，还要怎么验证回滚？",{"type":25,"children":220},[221],{"text":222,"type":29},"通过的标准是三件事同时成立：基线可复现、更新后达标、回滚路径已验证。只满足前两项时，只能记为“待观察”，不能记为通过。",{"type":25,"children":224},[225],{"text":226,"type":29},"回滚不是“把版本改回去”这一件事，要同时覆盖应用版本、配置开关和流量比例。只改版本、不改配置，经常出现“版本回去了、行为没回去”。",{"type":25,"children":228},[229],{"text":230,"type":29},"回滚后重跑矩阵中更新前的对照格，并按基线和批准的 API 契约检查成功率、响应结构及 p95 耗时。回滚演练是否作为放行门槛，应由团队预先确定；指标未恢复时先记为未通过或待归因，再核对命中版本、配置\u002F缓存、上游状态、样本量和时间差。",{"type":31,"level":32,"children":232},[233],{"text":234,"type":29},"FAQ",{"type":25,"children":236},[237],{"text":238,"type":29,"marks":239},"Q：测试地区必须覆盖所有国家吗？",[240],{"type":241},"bold",{"type":25,"children":243},[244],{"text":245,"type":29},"A：不必，优先覆盖业务实际使用地区和近期变更影响地区，并记录抽样依据。",{"type":25,"children":247},[248],{"text":249,"type":29,"marks":250},"Q：上游 API 更新但没有版本号，怎么确定比较范围？",[251],{"type":241},{"type":25,"children":253},[254],{"text":255,"type":29},"A：记录测试时间、模型标识、请求参数和响应头，按同一时间窗口做前后对照。",{"type":25,"children":257},[258],{"text":259,"type":29,"marks":260},"Q：地区测试通过后能否直接全量发布？",[261],{"type":241},{"type":25,"children":263},[264],{"text":265,"type":29},"A：不能仅凭地区测试决定；还需结合灰度指标、监控和回滚条件判断。","",[268,457,633],{"source":6,"id":269,"documentId":270,"translationKey":270,"locale":9,"slug":271,"path":272,"_path":272,"title":273,"description":274,"excerpt":274,"image":275,"publishedAt":276,"updatedAt":277,"author":17,"tags":278,"featured":19,"sortOrder":20,"readingTime":32,"bodyType":22,"bodyBlocks":279,"bodyHtml":266},3897,"y568ae3jjjs4yuaunluzwsqs","ai-api-egress-region-network-policy-diagnosis","\u002Fblog\u002Fai-api-egress-region-network-policy-diagnosis","测试 AI API 地区切换时，如何判断变化来自网络还是服务策略","切换代理 IP 出口地区后结果变化，如何区分网络与服务策略？本文用配对测试对照链路耗时、出口身份和服务端响应，避免把地区差异当成故障。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_AI_API_cover_0df5f0b27f.webp","2026-11-03T03:01:09.178Z","2026-11-03T03:01:09.154Z",[],[280,284,288,292,306,310,314,318,322,326,330,334,338,342,352,356,360,370,374,378,396,400,414,424,427,433,437,443,447,453],{"type":25,"children":281},[282],{"text":283,"type":29},"换地区之后结果变了，可能是链路、出口身份或服务策略，三者要分开取证。做法是固定请求只改地区，用分阶段耗时、IP 画像和响应证据各查一层。换地区这一步通常由代理 IP 完成，所以“换了地区”和“换了 IP”要当成两个变量记录。测试限制在自有账号和测试额度内，不通过反复请求真实付费接口制造样本。",{"type":31,"level":32,"children":285},[286],{"text":287,"type":29},"地区切换后，哪些因素可能导致结果变化？",{"type":25,"children":289},[290],{"text":291,"type":29},"地区切换可能同时改变链路、出口身份和服务策略相关条件；测试时分别记录这三类证据。",{"type":41,"format":42,"children":293},[294,298,302],{"type":45,"children":295},[296],{"text":297,"type":29},"链路质量：RTT、丢包、TLS 握手、首字节时间。",{"type":45,"children":299},[300],{"text":301,"type":29},"出口身份：IP 归属、ASN、IP 类型与信誉。",{"type":45,"children":303},[304],{"text":305,"type":29},"服务策略：限流、地区可用性、账号权限、模型可用列表。",{"type":25,"children":307},[308],{"text":309,"type":29},"如果同时换地区、出口和请求条件，结果无法区分来自哪项变化，因此对照时每轮只改变一个目标变量。",{"type":31,"level":32,"children":311},[312],{"text":313,"type":29},"怎么设计只改变出口地区的对照测试？",{"type":25,"children":315},[316],{"text":317,"type":29},"固定模型、prompt、客户端、超时和重试；按测试目的只改变地区或出口，并记录实际命中的版本和时间。",{"type":25,"children":319},[320],{"text":321,"type":29},"请求次数与观察时段应按接口波动和测试预算设定；尽量在相近时间重复对照，并避免用单次结果判断地区策略。",{"type":25,"children":323},[324],{"text":325,"type":29},"对照组至少三组：原地区、目标地区、以及目标地区的第二个出口 IP。第二组用来区分“地区本身”和“这个 IP”。",{"type":31,"level":32,"children":327},[328],{"text":329,"type":29},"怎么从 DNS、连接和响应耗时判断网络问题？",{"type":25,"children":331},[332],{"text":333,"type":29},"用 curl 对照时，必须显式使用与被测 API 相同的代理、绕过和凭据配置；凭据应从受限环境注入，不写进命令历史。下列字段是从请求开始累计的时间里程碑，不是独立 TCP\u002FTLS 耗时。代理 CONNECT、代理侧 DNS、连接复用和重定向都会影响解释；跳过的阶段标记为未观测。",{"type":25,"children":335},[336],{"text":337,"type":29},"```bash\ncurl -o \u002Fdev\u002Fnull -s -x \"$PROXY_URL\" -w 'namelookup_cumulative=%{time_namelookup} connect_cumulative=%{time_connect} appconnect_cumulative=%{time_appconnect} ttfb_cumulative=%{time_starttransfer}\\n' \\\nhttps:\u002F\u002Fapi.example.com\u002Fv1\u002Fmodels\n```",{"type":25,"children":339},[340],{"text":341,"type":29},"固定客户端、目标接口和时间窗口建立自身基线，再比较阶段分布；样本不足时只记录现象。`time_connect` 与 `time_appconnect` 不能直接当作到源站的独立 TCP\u002FTLS 时长。首字节慢也可能来自上游处理。",{"type":25,"children":343},[344,346,351],{"text":345,"type":29},"若要建立可比较的地区延迟基线，可参考",{"text":347,"type":29,"marks":348},"SaaS API 地区延迟基线怎么建？请求口径与分位数",[349],{"href":350,"type":182},"https:\u002F\u002Fwww.quanqiudaili.com\u002Fblog\u002Fsaas-api-region-latency-baseline",{"text":184,"type":29},{"type":31,"level":32,"children":353},[354],{"text":355,"type":29},"怎么确认出口 IP 的地区和类型？",{"type":25,"children":357},[358],{"text":359,"type":29},"对照多个 IP 数据库与目标服务看到的源 IP\u002F地区标签，并记录来源和查询时间。ASN、组织、城市和 IP 类型标签只能提供线索，不能单独证明出口的物理所在地或住宅\u002F机房属性；平台地区识别、购买地区与物理位置应分开记录。",{"type":25,"children":361},[362,364,369],{"text":363,"type":29},"同一地区用第二个出口复测。结果变化只说明出口或路径相关因素可能参与；固定账号、请求和时间窗口重复对照，再结合链路与服务端证据判断。来源冲突时标记为不确定。相关归因思路可参考",{"text":365,"type":29,"marks":366},"多地区测试发现问题，如何判断是网络故障还是产品缺陷？",[367],{"href":368,"type":182},"https:\u002F\u002Fwww.quanqiudaili.com\u002Fblog\u002Fregional-network-vs-product-defect",{"text":184,"type":29},{"type":31,"level":32,"children":371},[372],{"text":373,"type":29},"哪些响应信息能提示账号或地区策略限制？",{"type":25,"children":375},[376],{"text":377,"type":29},"结合错误体、限流头、账号\u002F模型可用范围和传输记录判断；即使链路不稳定，有明确来源的服务端错误仍可作为证据，但不应单独结束排查。下表列出可继续核验的线索：",{"type":41,"format":42,"children":379},[380,384,388,392],{"type":45,"children":381},[382],{"text":383,"type":29},"证据：429 与 Retry-After ｜ 候选方向：限流或配额 ｜ 下一步核对：查限流头、控制台用量及重复样本",{"type":45,"children":385},[386],{"text":387,"type":29},"证据：403 与地区或模型提示 ｜ 候选方向：可用范围 ｜ 下一步核对：核对服务方文档、账号和模型配置",{"type":45,"children":389},[390],{"text":391,"type":29},"证据：request ID 与限流 header ｜ 候选方向：服务端处理 ｜ 下一步核对：按服务方定义与网关日志核验",{"type":45,"children":393},[394],{"text":395,"type":29},"证据：链路指标稳定但持续失败 ｜ 候选方向：服务策略候选 ｜ 下一步核对：重复请求并核对错误体和账号范围",{"type":31,"level":32,"children":397},[398],{"text":399,"type":29},"确认原因后，下一步分别怎么处理？",{"type":41,"format":42,"children":401},[402,406,410],{"type":45,"children":403},[404],{"text":405,"type":29},"候选方向：链路异常 ｜ 对应证据：阶段耗时反复偏离自身基线 ｜ 下一步动作：换路径或时段复测，再看服务端响应",{"type":45,"children":407},[408],{"text":409,"type":29},"候选方向：出口相关 ｜ 对应证据：固定其余条件后结果随出口变化 ｜ 下一步动作：增加配对样本，核对路径与出口来源",{"type":45,"children":411},[412],{"text":413,"type":29},"候选方向：服务策略候选 ｜ 对应证据：错误体、账号\u002F模型范围和重复样本相互吻合 ｜ 下一步动作：核对配置或向服务方确认",{"type":25,"children":415},[416,418,423],{"text":417,"type":29},"证据不一致时，记录缺少的对照条件并保持未决。curl 参数和时间字段的含义可查",{"text":419,"type":29,"marks":420},"curl 手册",[421],{"href":422,"type":182},"https:\u002F\u002Fcurl.se\u002Fdocs\u002Fmanpage.html",{"text":184,"type":29},{"type":31,"level":32,"children":425},[426],{"text":234,"type":29},{"type":25,"children":428},[429],{"text":430,"type":29,"marks":431},"Q：测试时 API 返回内容不稳定，会影响地区归因吗？",[432],{"type":241},{"type":25,"children":434},[435],{"text":436,"type":29},"A：会。应固定模型、输入和采样窗口；无法固定时，将内容波动作为单独限制记录。",{"type":25,"children":438},[439],{"text":440,"type":29,"marks":441},"Q：同一地区的两个出口结果不同，能直接认定是 IP 信誉吗？",[442],{"type":241},{"type":25,"children":444},[445],{"text":446,"type":29},"A：不能，先排除网络路径、账号状态和上游时间差，再把结论标为待验证。",{"type":25,"children":448},[449],{"text":450,"type":29,"marks":451},"Q：地区策略由服务方临时调整时，旧测试结论还有效吗？",[452],{"type":241},{"type":25,"children":454},[455],{"text":456,"type":29},"A：只对当时的账号、地区和时间窗口有效，策略变化后应重新验证。",{"source":6,"id":458,"documentId":459,"translationKey":459,"locale":9,"slug":460,"path":461,"_path":461,"title":462,"description":463,"excerpt":463,"image":464,"publishedAt":465,"updatedAt":466,"author":17,"tags":467,"featured":19,"sortOrder":20,"readingTime":32,"bodyType":22,"bodyBlocks":468,"bodyHtml":266},3896,"a6t066253yf747tqsxn0ts4x","http-client-proxy-retry-duplicate-submit-test","\u002Fblog\u002Fhttp-client-proxy-retry-duplicate-submit-test","HTTP 客户端升级后，如何检查代理重试是否导致重复提交","HTTP 客户端升级后，如何检查重试是否导致重复提交？本文用请求计数区分自动重试、重定向重放和连接中断，并说明幂等验证要点。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_HTTP_cover_6e441939e4.webp","2026-11-03T03:00:50.977Z","2026-11-03T03:00:50.965Z",[],[469,473,477,481,495,499,503,507,511,529,533,543,547,551,555,569,573,577,581,585,589,600,603,609,613,619,623,629],{"type":25,"children":470},[471],{"text":472,"type":29},"重复提交往往不是业务代码写错，而是客户端在代理 IP 链路上自动重试了非幂等请求。要验证这一点，得让“请求被执行了几次”变成可观测的数字，不靠肉眼核对结果。下面用一个计数接口做靶子，配合注入失败来暴露重试路径。",{"type":31,"level":32,"children":474},[475],{"text":476,"type":29},"怎么判断请求被重复处理了？",{"type":25,"children":478},[479],{"text":480,"type":29},"业务代码调用一次后，客户端可能发送多次网络请求；这不等于业务副作用也执行多次。对照三类记录：客户端发送尝试、服务端接收次数、同一业务键对应的实际副作用次数。再检查幂等键是否一致。只看最终结果不够，因为重复写入可能互相覆盖而不显错。常见重放入口如下：",{"type":41,"format":42,"children":482},[483,487,491],{"type":45,"children":484},[485],{"text":486,"type":29},"重试机制：状态码重试 ｜ 触发条件：503、502、连接超时 ｜ 注入方式：让服务端对部分请求返回 503",{"type":45,"children":488},[489],{"text":490,"type":29},"重试机制：重定向重放 ｜ 触发条件：307、308 通常要求保留方法与请求体 ｜ 注入方式：测试网关让 A 重定向到 B，仅跳转一次；循环作为单独负例并限制跳数",{"type":45,"children":492},[493],{"text":494,"type":29},"重试机制：连接重置重试 ｜ 触发条件：客户端认为请求未发出 ｜ 注入方式：代理侧主动断开连接",{"type":31,"level":32,"children":496},[497],{"text":498,"type":29},"怎么搭建一个可观察请求次数的测试接口？",{"type":25,"children":500},[501],{"text":502,"type":29},"测试接口按业务键统计接收次数和副作用次数，并返回请求序号。日志只保留脱敏关联键、必要状态和计数，过滤认证头、Cookie、URL 凭据与业务正文。测试请求应经过被测客户端和代理路径，以便对照客户端发送、服务端接收及业务处理记录。",{"type":31,"level":32,"children":504},[505],{"text":506,"type":29},"客户端会在什么情况下自动重试？",{"type":25,"children":508},[509],{"text":510,"type":29},"客户端的重试策略由具体库、版本、状态码和请求配置决定，不能只按 HTTP 方法推断。下表列的是需要核验的风险：",{"type":41,"format":42,"children":512},[513,517,521,525],{"type":45,"children":514},[515],{"text":516,"type":29},"方法：GET \u002F HEAD ｜ 方法语义：幂等 ｜ 待核验的风险：通常可安全重试，仍需核对客户端策略与服务端实现",{"type":45,"children":518},[519],{"text":520,"type":29},"方法：PUT \u002F DELETE ｜ 方法语义：幂等 ｜ 待核验的风险：客户端可能重试，需确认服务端确实做过幂等实现",{"type":45,"children":522},[523],{"text":524,"type":29},"方法：POST ｜ 方法语义：非幂等 ｜ 待核验的风险：重试即可能重复创建，需核对客户端策略、幂等键与服务端处理",{"type":45,"children":526},[527],{"text":528,"type":29},"方法：PATCH ｜ 方法语义：非幂等 ｜ 待核验的风险：部分客户端仍会重试，需按具体版本核对",{"type":25,"children":530},[531],{"text":532,"type":29},"升级后要特别确认：新版本是否把 POST 也纳入了可重试范围。这正是重复提交最常见的来源。",{"type":25,"children":534},[535,537,542],{"text":536,"type":29},"需要继续核对投递、签名与重试行为，可参考",{"text":538,"type":29,"marks":539},"SaaS Webhook 地区测试怎么做？到达、签名与重试",[540],{"href":541,"type":182},"https:\u002F\u002Fwww.quanqiudaili.com\u002Fblog\u002Fsaas-webhook-delivery-retry-region-testing",{"text":184,"type":29},{"type":31,"level":32,"children":544},[545],{"text":546,"type":29},"307 和 308 为什么可能重复发送请求？",{"type":25,"children":548},[549],{"text":550,"type":29},"客户端若自动跟随 307\u002F308，通常会尝试保留方法和请求体重新发送，但请求体是否可重放、原请求是否已被处理、服务端是否有副作用，都取决于客户端实现与应用设计。",{"type":25,"children":552},[553],{"text":554,"type":29},"测试时检查客户端是否自动跟随、请求体能否重放，以及 A、B 两端分别收到几次请求：",{"type":41,"format":42,"children":556},[557,561,565],{"type":45,"children":558},[559],{"text":560,"type":29},"是否携带同一个幂等键；",{"type":45,"children":562},[563],{"text":564,"type":29},"是否重复执行了创建逻辑；",{"type":45,"children":566},[567],{"text":568,"type":29},"是否存在重定向循环。",{"type":25,"children":570},[571],{"text":572,"type":29},"仅在隔离的测试环境中，可将 `Location` 指向计数接口，观察客户端是否再次发送请求。请求次数增加说明发生了重放；还需检查服务端是否重复执行了业务操作。若方法或请求体发生变化，应记录具体客户端行为，并对照其版本文档。",{"type":31,"level":32,"children":574},[575],{"text":576,"type":29},"代理连接中断时，写请求会不会被重新发送？",{"type":25,"children":578},[579],{"text":580,"type":29},"断开位置决定服务端是否处理过请求。可在受控环境中分别模拟发送前失败、服务端接收后失败和业务提交后丢弃响应，再对照客户端日志、接收次数与副作用记录。连接重置本身无法说明请求是否已执行。",{"type":31,"level":32,"children":582},[583],{"text":584,"type":29},"怎么模拟失败并确认修复有效？",{"type":25,"children":586},[587],{"text":588,"type":29},"在隔离环境中逐项注入故障：发送前断开、业务提交后丢弃响应、返回 503、A 到 B 的一次 307，以及受控超时。重定向循环另作负例并限制跳数。每轮只改一个条件，记录客户端发送、服务端接收和业务副作用次数。修复后重跑相同用例；只有业务记录没有重复副作用且各层计数符合预期，才算通过。",{"type":25,"children":590},[591,593,598],{"text":592,"type":29},"307\u002F308 的 HTTP 语义见 ",{"text":594,"type":29,"marks":595},"RFC 9110",[596],{"href":597,"type":182},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc9110.html",{"text":599,"type":29},"；客户端是否自动跟随及请求体能否重放，应按具体版本验证。",{"type":31,"level":32,"children":601},[602],{"text":234,"type":29},{"type":25,"children":604},[605],{"text":606,"type":29,"marks":607},"Q：幂等键能避免所有重复写入吗？",[608],{"type":241},{"type":25,"children":610},[611],{"text":612,"type":29},"A：不能，需确认服务端确实支持幂等键，并验证键的作用范围和保留时间。",{"type":25,"children":614},[615],{"text":616,"type":29,"marks":617},"Q：读请求和写请求可以使用相同重试策略吗？",[618],{"type":241},{"type":25,"children":620},[621],{"text":622,"type":29},"A：不应默认相同；写请求需评估服务端处理状态未知时的重复执行风险。",{"type":25,"children":624},[625],{"text":626,"type":29,"marks":627},"Q：客户端没有收到响应，能否判断服务端未处理请求？",[628],{"type":241},{"type":25,"children":630},[631],{"text":632,"type":29},"A：不能。连接中断时处理状态可能未知，应通过服务端记录或业务状态核对。",{"source":6,"id":634,"documentId":635,"translationKey":635,"locale":9,"slug":636,"path":637,"_path":637,"title":638,"description":639,"excerpt":639,"image":640,"publishedAt":641,"updatedAt":642,"author":17,"tags":643,"featured":19,"sortOrder":20,"readingTime":644,"bodyType":22,"bodyBlocks":645,"bodyHtml":266},3895,"tf37dm2ee9mh0u8xbco8p5ch","go-transport-proxy-connection-regression","\u002Fblog\u002Fgo-transport-proxy-connection-regression","Go 版本或 Transport 配置变更后，如何重新检查代理连接","Go 版本或 Transport 配置变更后，如何检查代理连接？本文核对代理配置来源，并对照连接复用、TLS、HTTP\u002F2 和分阶段耗时。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_Go_Transport_cover_4e51fe43de.webp","2026-11-03T03:00:34.422Z","2026-11-03T03:00:34.401Z",[],4,[646,650,654,658,672,676,680,684,688,692,696,700,704,708,712,716,726,730,734,748,752,756,760,774,778,782,786,790,794,816,820,827,830,836,840,846,850,856],{"type":25,"children":647},[648],{"text":649,"type":29},"Go 的代理 IP 行为由版本、环境变量和 `Transport` 配置共同决定，任何一项变化都可能改变实际走向。回归的关键是把“代理从哪来”和“请求走到哪一步”都变成可观测的。下面用标准库的 `httptrace` 与会话复用指标做示范，测试在自有服务与测试出口上进行。",{"type":31,"level":32,"children":651},[652],{"text":653,"type":29},"升级后先确认 Go 版本和哪些配置变了",{"type":25,"children":655},[656],{"text":657,"type":29},"先记录 Go 版本、构建选项和代理环境配置，作为升级前后对照基线：",{"type":41,"format":42,"children":659},[660,664,668],{"type":45,"children":661},[662],{"text":663,"type":29},"Go 版本：`go version`，大版本升级可能改变默认 TLS 与 HTTP\u002F2 行为；",{"type":45,"children":665},[666],{"text":667,"type":29},"GOFLAGS 与构建标签：是否启用或关闭了某些默认行为；",{"type":45,"children":669},[670],{"text":671,"type":29},"环境变量：`HTTP_PROXY`、`HTTPS_PROXY`、`NO_PROXY` 是否被设置，以及大小写写法。",{"type":25,"children":673},[674],{"text":675,"type":29},"`HTTP_PROXY` 与 `http_proxy` 的处理及 `NO_PROXY` 匹配规则应按 Go 版本核实。回归前记录变量是否设置、脱敏后的主机\u002F端口与绕过规则，再和升级前记录对比。不要输出含 URL userinfo、口令或完整凭据的代理环境变量。",{"type":31,"level":32,"children":677},[678],{"text":679,"type":29},"Go 请求最终使用了哪种代理配置？",{"type":25,"children":681},[682],{"text":683,"type":29},"最终用哪套配置，以当前请求实际使用的 `Transport` 和绕过规则为准。除了 `Proxy`，还要检查自定义 `DialContext`、`NO_PROXY`、重定向和连接池；环境变量是否被读取还取决于读取时机，不能只看代码或环境变量就下判断。",{"type":25,"children":685},[686],{"text":687,"type":29},"```go\ntr := &http.Transport{\nProxy: http.ProxyFromEnvironment, \u002F\u002F 读环境变量\n\u002F\u002F Proxy: http.ProxyURL(proxyURL), \u002F\u002F 显式指定\nTLSHandshakeTimeout: 5 * time.Second,\n}\nclient := &http.Client{Transport: tr, Timeout: 30 * time.Second}\nreq, _ := http.NewRequest(\"GET\", \"https:\u002F\u002Fapi.example.com\u002Fv1\u002Fmodels\", nil)\nreq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"API_KEY\"))\nresp, err := client.Do(req)\n```",{"type":25,"children":689},[690],{"text":691,"type":29},"若 `Transport.Proxy` 显式设置了代理函数或地址，请求将按该配置处理，不会再由另一套环境变量配置替代。使用标准库 `ProxyFromEnvironment` 时，代理环境配置存在进程级缓存行为；按目标 Go 版本核对实现。修改环境变量后，应在设置好环境的新进程中测试，只新建 Transport 不保证刷新缓存。需要运行时动态选择代理时，用明确管理的 `Proxy` 函数或 `ProxyURL`，并单独管理连接池。",{"type":25,"children":693},[694],{"text":695,"type":29},"在隔离测试环境中，可将代理地址指向不可用端口作为负向测试。请求仍成功只说明该请求没有依赖这个代理配置；还应排查 `NO_PROXY`、其他代理设置、连接复用等因素，并核对最终出口。",{"type":25,"children":697},[698],{"text":699,"type":29},"`NO_PROXY` 命中时请求会绕过代理；应把匹配结果与实际出口一起核对。",{"type":31,"level":32,"children":701},[702],{"text":703,"type":29},"怎么用 `httptrace` 定位连接耗时？",{"type":25,"children":705},[706],{"text":707,"type":29},"`httptrace` 提供的是阶段事件，不是现成的耗时报表；需要在事件回调里记时间戳、算差值，再和总耗时对照。",{"type":25,"children":709},[710],{"text":711,"type":29},"```go\nstart := time.Now()\nvar dnsStart, connStart, tlsStart time.Time\ntrace := &httptrace.ClientTrace{\nDNSStart:          func(httptrace.DNSStartInfo) { dnsStart = time.Now() },\nDNSDone:           func(httptrace.DNSDoneInfo) { log.Printf(\"dns %v\", time.Since(dnsStart)) },\nConnectStart:      func(network, addr string) { connStart = time.Now(); log.Printf(\"connect start %s %s\", network, addr) },\nConnectDone:       func(network, addr string, err error) { log.Printf(\"connect done %s %v %v\", addr, time.Since(connStart), err) },\nTLSHandshakeStart: func() { tlsStart = time.Now() },\nTLSHandshakeDone: func(cs tls.ConnectionState, err error) {\nlog.Printf(\"tls %v proto=%s\", time.Since(tlsStart), cs.NegotiatedProtocol)\n},\nGotFirstResponseByte: func() { log.Printf(\"ttfb %v\", time.Since(start)) },\n}\nreq = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))\n```",{"type":25,"children":713},[714],{"text":715,"type":29},"开始事件与完成事件要成对记录，才能计算 DNS、拨号、TLS 等阶段时长；只注册起点不会得到 duration。示例中的单一时间变量只适用于串行、单次尝试；Happy Eyeballs 等情况下可能并发或多次触发回调，应按连接尝试关联时间戳，并用互斥锁或并发安全结构保护。若连接复用，可能没有新的拨号事件，缺事件不等于零耗时。代理下 `ConnectStart` 的目标通常是代理地址；经 HTTP 代理时 DNS 也可能由代理完成。",{"type":25,"children":717},[718,720,725],{"text":719,"type":29},"移动应用场景的 DNS、连接和 TLS 分阶段定位方法，可参考",{"text":721,"type":29,"marks":722},"移动 App 网络兼容测试：五个失败阶段定位法",[723],{"href":724,"type":182},"https:\u002F\u002Fwww.quanqiudaili.com\u002Fblog\u002Fmobile-app-network-egress-compatibility-testing",{"text":184,"type":29},{"type":31,"level":32,"children":727},[728],{"text":729,"type":29},"怎么确认连接复用和超时设置仍然生效？",{"type":25,"children":731},[732],{"text":733,"type":29},"检查连接池配置与 `GotConn` 事件，比较同一负载下的复用结果：",{"type":41,"format":42,"children":735},[736,740,744],{"type":45,"children":737},[738],{"text":739,"type":29},"`DisableKeepAlives: true` 会让每个请求重新建连；",{"type":45,"children":741},[742],{"text":743,"type":29},"`TLSHandshakeTimeout` 过短会在跨境链路上频繁失败；",{"type":45,"children":745},[746],{"text":747,"type":29},"`MaxIdleConnsPerHost` 过低会导致高并发时连接被丢弃重建。",{"type":25,"children":749},[750],{"text":751,"type":29},"观测方式：看 `httptrace.GotConn` 回调里 `info.Reused` 的取值，统计连续请求中的复用比例。复用比例受请求模式、连接池和服务端行为影响，不设统一阈值；将升级前后相同负载下的结果对照，异常时再检查配置和默认值。",{"type":31,"level":32,"children":753},[754],{"text":755,"type":29},"升级后 HTTP\u002F2 或 TLS 协商有什么变化？",{"type":25,"children":757},[758],{"text":759,"type":29},"Go 版本升级可能改变 HTTP\u002F2 启用条件和 TLS 默认值，判断时以 Go 客户端自己的结果为准。",{"type":41,"format":42,"children":761},[762,766,770],{"type":45,"children":763},[764],{"text":765,"type":29},"自定义 `TLSClientConfig` 可能关闭 HTTP\u002F2；",{"type":45,"children":767},[768],{"text":769,"type":29},"`ForceAttemptHTTP2` 的设置会影响协商结果；",{"type":45,"children":771},[772],{"text":773,"type":29},"最低 TLS 版本提升后，旧代理或中间设备可能握手失败。",{"type":25,"children":775},[776],{"text":777,"type":29},"以 Go 的 `resp.Proto`、`resp.TLS.NegotiatedProtocol`（TLS 响应时）及客户端日志作为 Go 实际协商结果。curl 只能作显式相同代理、目标和路径下的独立对照，不能代表 Go 客户端结果：",{"type":25,"children":779},[780],{"text":781,"type":29},"```bash\ncurl -sI -o \u002Fdev\u002Fnull -w '%{http_version}\\n' https:\u002F\u002Fapi.example.com\u002F\n```",{"type":25,"children":783},[784],{"text":785,"type":29},"该命令只显示 curl 自己的协商结果；Go 的结果以 `resp.Proto` 与 TLS 状态为准。两者不一致时，先确认代理、目标和请求路径相同，再检查 Go 版本、`tls.Config` 与代理隧道能力。",{"type":31,"level":32,"children":787},[788],{"text":789,"type":29},"怎么判断代理连接回归通过？",{"type":25,"children":791},[792],{"text":793,"type":29},"可按下表对照代理来源、分阶段耗时、连接复用、协议协商及超时重试。",{"type":41,"format":42,"children":795},[796,800,804,808,812],{"type":45,"children":797},[798],{"text":799,"type":29},"检查项：代理来源 ｜ 观测点：检查 `Transport.Proxy`、绕过规则及实际出口 ｜ 正常表现：与预期代理配置和出口一致",{"type":45,"children":801},[802],{"text":803,"type":29},"检查项：分阶段耗时 ｜ 观测点：`httptrace` 成对事件算出的各段时长 ｜ 正常表现：各段与升级前同一量级",{"type":45,"children":805},[806],{"text":807,"type":29},"检查项：连接复用 ｜ 观测点：`GotConn` 的 `Reused` ｜ 正常表现：与升级前相同负载下的基线对照",{"type":45,"children":809},[810],{"text":811,"type":29},"检查项：Go 协商协议 ｜ 观测点：`resp.Proto` 与 TLS 状态 ｜ 正常表现：按目标服务、代理路径和升级前基线核对",{"type":45,"children":813},[814],{"text":815,"type":29},"检查项：超时与重试 ｜ 观测点：三类超时的触发日志 ｜ 正常表现：各按预期触发，次数不变",{"type":25,"children":817},[818],{"text":819,"type":29},"如果实际出口与配置不符，先检查 `Transport.Proxy` 和绕过规则；若某阶段耗时变化，检查对应的连接与超时配置；HTTP\u002F2 协商结果变化时，再核对 Go 版本、TLS 配置和代理支持情况。",{"type":25,"children":821},[822],{"text":823,"type":29,"marks":824},"Go net\u002Fhttp 官方文档",[825],{"href":826,"type":182},"https:\u002F\u002Fpkg.go.dev\u002Fnet\u002Fhttp",{"type":31,"level":32,"children":828},[829],{"text":234,"type":29},{"type":25,"children":831},[832],{"text":833,"type":29,"marks":834},"Q：环境变量代理设置会自动覆盖自定义 Transport 吗？",[835],{"type":241},{"type":25,"children":837},[838],{"text":839,"type":29},"A：取决于 `Transport.Proxy` 是否显式设置及 Go 版本行为，应以实际配置和版本文档为准。",{"type":25,"children":841},[842],{"text":843,"type":29,"marks":844},"Q：测试关闭连接复用会代表线上表现吗？",[845],{"type":241},{"type":25,"children":847},[848],{"text":849,"type":29},"A：不会。它适合隔离连接问题，结果应与默认复用配置分开记录。",{"type":25,"children":851},[852],{"text":853,"type":29,"marks":854},"Q：只在一个 Go 版本通过是否足够？",[855],{"type":241},{"type":25,"children":857},[858],{"text":859,"type":29},"A：若生产使用多个版本，应覆盖实际部署版本及升级目标版本。",[861],{"source":6,"id":7,"documentId":8,"translationKey":8,"locale":9,"slug":10,"path":11,"_path":11,"title":12,"description":13,"excerpt":13,"image":14,"publishedAt":15,"updatedAt":16,"author":17,"tags":862,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":863,"bodyHtml":266},[],[864,867,870,873,884,887,890,893,907,910,924,927,930,933,936,950,953,956,959,970,973,980,983,986,1000,1003,1006,1009,1012,1015,1018,1023,1026,1031,1034,1039],{"type":25,"children":865},[866],{"text":28,"type":29},{"type":31,"level":32,"children":868},[869],{"text":35,"type":29},{"type":25,"children":871},[872],{"text":39,"type":29},{"type":41,"format":42,"children":874},[875,878,881],{"type":45,"children":876},[877],{"text":48,"type":29},{"type":45,"children":879},[880],{"text":52,"type":29},{"type":45,"children":882},[883],{"text":56,"type":29},{"type":25,"children":885},[886],{"text":60,"type":29},{"type":31,"level":32,"children":888},[889],{"text":64,"type":29},{"type":25,"children":891},[892],{"text":68,"type":29},{"type":41,"format":42,"children":894},[895,898,901,904],{"type":45,"children":896},[897],{"text":74,"type":29},{"type":45,"children":899},[900],{"text":78,"type":29},{"type":45,"children":902},[903],{"text":82,"type":29},{"type":45,"children":905},[906],{"text":86,"type":29},{"type":25,"children":908},[909],{"text":90,"type":29},{"type":41,"format":42,"children":911},[912,915,918,921],{"type":45,"children":913},[914],{"text":96,"type":29},{"type":45,"children":916},[917],{"text":100,"type":29},{"type":45,"children":919},[920],{"text":104,"type":29},{"type":45,"children":922},[923],{"text":108,"type":29},{"type":25,"children":925},[926],{"text":112,"type":29},{"type":31,"level":32,"children":928},[929],{"text":116,"type":29},{"type":25,"children":931},[932],{"text":120,"type":29},{"type":25,"children":934},[935],{"text":124,"type":29},{"type":41,"format":42,"children":937},[938,941,944,947],{"type":45,"children":939},[940],{"text":130,"type":29},{"type":45,"children":942},[943],{"text":134,"type":29},{"type":45,"children":945},[946],{"text":138,"type":29},{"type":45,"children":948},[949],{"text":142,"type":29},{"type":25,"children":951},[952],{"text":146,"type":29},{"type":31,"level":32,"children":954},[955],{"text":150,"type":29},{"type":25,"children":957},[958],{"text":154,"type":29},{"type":41,"format":42,"children":960},[961,964,967],{"type":45,"children":962},[963],{"text":160,"type":29},{"type":45,"children":965},[966],{"text":164,"type":29},{"type":45,"children":968},[969],{"text":168,"type":29},{"type":25,"children":971},[972],{"text":172,"type":29},{"type":25,"children":974},[975,976,979],{"text":176,"type":29},{"text":178,"type":29,"marks":977},[978],{"href":181,"type":182},{"text":184,"type":29},{"type":31,"level":32,"children":981},[982],{"text":188,"type":29},{"type":25,"children":984},[985],{"text":192,"type":29},{"type":41,"format":42,"children":987},[988,991,994,997],{"type":45,"children":989},[990],{"text":198,"type":29},{"type":45,"children":992},[993],{"text":202,"type":29},{"type":45,"children":995},[996],{"text":206,"type":29},{"type":45,"children":998},[999],{"text":210,"type":29},{"type":25,"children":1001},[1002],{"text":214,"type":29},{"type":31,"level":32,"children":1004},[1005],{"text":218,"type":29},{"type":25,"children":1007},[1008],{"text":222,"type":29},{"type":25,"children":1010},[1011],{"text":226,"type":29},{"type":25,"children":1013},[1014],{"text":230,"type":29},{"type":31,"level":32,"children":1016},[1017],{"text":234,"type":29},{"type":25,"children":1019},[1020],{"text":238,"type":29,"marks":1021},[1022],{"type":241},{"type":25,"children":1024},[1025],{"text":245,"type":29},{"type":25,"children":1027},[1028],{"text":249,"type":29,"marks":1029},[1030],{"type":241},{"type":25,"children":1032},[1033],{"text":255,"type":29},{"type":25,"children":1035},[1036],{"text":259,"type":29,"marks":1037},[1038],{"type":241},{"type":25,"children":1040},[1041],{"text":265,"type":29},null,[],[]]