摘要
做网关时我一开始想"给每个上游接口写一个适配器",列完清单就停住了:embeddings、moderations、images、audio 这些接口的内容我根本不需要理解,原样转发就行。对它们写适配器等于为每个接口维护一套可能与上游产生分歧的结构体。这篇讲这条路——代码只有几十行,但有几个我认为值得说的决定。
技术博客与学习记录
做网关时我一开始想"给每个上游接口写一个适配器",列完清单就停住了:embeddings、moderations、images、audio 这些接口的内容我根本不需要理解,原样转发就行。对它们写适配器等于为每个接口维护一套可能与上游产生分歧的结构体。这篇讲这条路——代码只有几十行,但有几个我认为值得说的决定。
两个看起来相反的结论:一个被微基准证明"变慢了 2.1 倍"的优化我最后保留了;一个 P95 回归 69.7% 但成功率反而更好的重构我回滚了。它们背后的标准是同一条——我要的改善必须出现在真实负载的请求级指标上,而不是出现在任何可以被我解释的局部数字里。
我给自己做了一次代码审查,然后否决了它的大部分方案。因为那份清单是在没有读全部代码的情况下写的——比如它说我 domain/ 目录"名不副实",但那条不符合我在仓库规范里定义的准入标准。这篇讲逐条复核的过程:哪些我认了、哪些我否了、哪些我决定什么都不做。
我的每夜 E2E 连续失败过一阵子。按常规做法该加 retry,但我先去看失败之间有没有共同点——结果发现两类完全不同的失败混在一起:一类是它根本没在测我的代码,另一类是我的断言在等一个中间状态。这篇讲这两件事,以及测试矩阵和 mock upstream 的设计。
"服务端 0.8ms,但页面要等一秒"——这个对比是整次排查的转折点。如果我先去优化那个看起来最慢的聚合查询(76+69ms),我优化的只是 15% 的部分。这篇把那一秒拆成四份,逐份讲。
订阅账号会有各种"暂时不能用"的情况:被上游限流了、额度用完了、凭据过期了、上游在抖动。这些状态都要有退出机制——总不能因为一次 429 就永远不再用这个账号。
我第一版的 token 刷新逻辑大概是这样的:
接上游 provider 这件事,我一开始以为是"一家一个适配器"。写了才发现不是:三十多个渠道类型里,真正需要单独写协议转换逻辑的只有四家,其余二十多家全是同一个适配器的不同配置。
做订阅功能的时候,我脑子里只有一个"额度"概念:用户买了套餐,每个月能用 20 美元,超过就停。
我第一版的对账逻辑很简单:把钱包余额和账本净额比一下,不一致就报出来。跑起来之后几乎每个用户都进了异常列表——因为我没想清楚"余额"到底是什么。这篇讲七类核对分别在查什么、为什么它们的容差不一样,以及我为什么坚持让它只报告不修复。