ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

海淘系统API网关落地实践:从选型到调优踩坑

海淘系统API网关落地实践:从选型到调优踩坑 做海淘系统这几年有一个组件是越用越离不开的那就是API网关。说实话早期我们系统刚上线的时候根本没想过要单独搞一层网关觉得所有服务直接暴露出去也没啥问题。等到用户量起来、业务线变多、海外节点也铺开之后问题就像多米诺骨牌一样一个接一个倒下来鉴权逻辑每个服务各写一套、限流没法统一做、线上出问题连流量打到哪个服务都说不清楚。那个时候才意识到API网关不是一个“可选优化项”而是海淘这类复杂业务架构里绕不开的“基础设施”。这篇文章就围绕我们自己在海淘系统里落地API网关的完整过程来写会涉及整体设计思路、网关选型、核心功能拆解、具体参数调优、实操配置、以及那些文档里不会写但实际一定会踩的坑。不管你是刚开始接触网关还是已经在用但想优化细节这篇文章应该都能给你一些参考。1. 海淘系统为什么绕不开API网关1.1 海淘业务的链路特征和网关定位海淘系统的业务链路和普通电商有一个很大的区别它天然跨地域、跨时区、跨币种、跨多种支付渠道还要对接物流清关。用户在国内下单商品可能在海外仓也可能在保税仓支付可能要经过境外收单机构订单状态要实时同步给物流方。整个链条拆成微服务之后服务数量很容易超过几十个每个服务都有自己的协议、自己的鉴权方式、自己的数据格式。这时候如果没有一层统一的网关挡在前面客户端要面对的就会是一堆散乱的接口。用户登录要请求A服务查商品要请求B服务下单要请求C服务支付回调又要请求D服务。每次新增一个业务客户端就得多配一个域名和一套鉴权逻辑。而API网关的核心价值就是把这个混乱收敛掉客户端只认一个入口网关负责把请求路由到正确的后端服务同时把鉴权、限流、日志、灰度这些横切能力全部集中到这一层来做。海淘场景对网关的依赖还会更深一层。比如海外节点的延迟问题用户从国内发起请求如果每次都打到部署在海外的后端服务往返可能就是几百毫秒。但商品详情这种读多写少的数据完全可以在网关层做缓存策略直接命中网关缓存层返回避免每次都穿透到海外源站。1.2 网关在海淘架构中承担的职责清单具体来说我们在海淘系统里让API网关承担了这么几块职责这也是我认为海淘业务对网关卡得最紧的几个点第一是统一接入和协议转换。客户端和服务端之间有JSON、有protobuf老系统还有XML报文网关要做格式转换海外的后端服务可能走gRPC客户端走HTTP网关负责把HTTP请求转换成gRPC请求。这个能力在对接跨境物流和清关报文时特别有用第三方物流商往往只支持特定格式的报文网关可以把内部标准报文转换成外部要求的格式。第二是安全与鉴权。用户Token校验、签名校验、设备指纹识别、基于风险等级的风控拦截这些都放在网关层。尤其是海淘支付环节网关需要对敏感接口做额外的风控校验比如大额订单、新设备登录、频繁修改收货地址等场景全部在网关层预判一次。第三是流量治理。包括限流、熔断、降级、重试。大促期间比如黑五、双十一海外购会场峰值流量往往是日常的数十倍。如果没有网关层的限流保护后端订单服务很容易被打挂。而熔断机制保证了当某个下游服务异常时网关能快速失败并返回兜底数据而不是让所有线程都阻塞在下游超时上。第四是可观测性。网关是所有流量的必经之路天然适合做统一日志采集、链路追踪标记、调用量统计。我们后来排查线上问题基本上第一件事都是去网关看日志和调用链数据而不是直接翻后端服务日志。这几块职责如果散落在每个微服务里各自实现不仅代码重复严重而且很容易出现“有的服务做了、有的没做”的漏洞。放到网关层统一管理之后策略调整只需要改网关配置后端服务完全不用动。2. 网关的技术选型和整体架构落位2.1 自研、开源还是云厂商网关我的选型思路网关选型这个问题我们在项目初期纠结了挺久。当时面前有三条路自研、基于开源项目二次开发、直接用云厂商的API网关产品。三条路我都调研过也简单聊聊我当时评估的思路。自研网关控制力最强完全贴合自己的业务但成本和风险都高。网关是所有流量的咽喉一旦出问题就是全局性故障。要做到高可用、高性能、持续迭代需要专门的团队长期投入。对于业务体量还在上升期的海淘系统来说自研的性价比不高。云厂商API网关胜在省心控制台点点点就能创建一个网关自带鉴权、限流、监控等常见功能。但我们有几个顾虑一是我们有多地多活的需求云厂商网关的能力可能受限在特定地域二是有一些敏感数据我们希望能完全自控三是长久来看如果网关能力被锁在某个云厂商上后面架构演进会受限。开源网关二次开发最终我们选择了这条路。我们以Apache APISIX作为基础把核心路由、限流、鉴权这些能力直接复用同时基于它的插件机制做自定义插件去适配海淘特有的多币种处理、跨境清关报文转换等需求。选APISIX而不是其他开源网关主要是看重它基于etcd的配置管理支持热更新配置不需要重启网关就能调整路由和限流规则再加上它本身就是高性能的Nginx OpenResty技术栈社区活跃度也不错。2.2 网关在整体架构中的上下游关系网关层落位之后整个海淘系统的流量路径变得很清晰客户端请求先到达CDNCDN做静态资源加速和基础的DDoS防护然后进入API网关网关完成鉴权、限流、路由转发再往后是业务服务层包括商品服务、订单服务、支付服务、用户服务、库存服务等服务之间通过内部RPC通信所有与外部系统的对接比如物流、海关、第三方支付也统一经过网关或专用的外部对接层。有一点需要注意网关并不是所有流量的唯一入口。海淘系统里还有一些不会经过用户域名的内部回调比如支付渠道的异步通知、物流状态的回传这些如果也走统一网关可能会因为网关层超时或路由规则问题导致回调丢失。我们后来是专门给外部回调单独开了一条网关路由走独立的域名和服务分组避免和用户侧请求互相影响。另外网关要避免变成“胖网关”也就是别什么逻辑都往网关里塞。比如海淘的多币种汇率转换我们最终没有放在网关层做完整转换网关只做汇率缓存和币种标记真正的金额计算还是在后端订单服务里做。原因很简单网关适合做无状态、统一的横切逻辑一旦涉及复杂的业务状态判断塞进网关会让网关变得难以维护。3. 海淘网关核心功能详解与参数调优3.1 路由与流量治理一套规则管多个区域海淘系统通常会有多区域部署比如国内主集群、海外节点集群。客户端请求进到网关之后怎么决定把流量转发到哪个区域的后端服务我们这里采用了全局限流和区域路由分离的策略。用户请求带着用户ID和商品ID网关根据商品归属仓库的Region信息把请求路由到对应区域。比如用户浏览的是一款美国直邮商品请求会被路由到美国区域的商品服务如果是一款保税仓商品请求会被路由到国内集群。这里的关键点在于路由规则不是简单按URL前缀去匹配而是要结合业务上下文做判断。我们通过自定义插件在网关层注入了一个region_resolver逻辑从请求参数中解析商品所在仓库区域再动态选择上游节点。全局限流则是按API维度去配置比如商品详情接口全局限流5000 QPS超过部分直接返回一个友好的降级页面。在做路由和限流时有几个参数我每次都要反复和其他团队对齐稍微搞错一个线上就会出问题。下面是我整理的参数参考表参数项推荐配置说明worker进程数与CPU核数一致不是越多越好避免上下文切换开销过大keepalive连接数单上游保持1024网关到后端服务的连接复用如果太小会出现TIME_WAIT堆积路由超时时间连接5秒读10秒写10秒超时设置过长会导致大量线程被慢请求占满限流阈值按接口平时峰值的1.21.5倍设置设置太低会误杀正常流量太高就起不到保护作用熔断阈值错误率30%触发熔断时长20秒熔断时长不宜太短要留给下游恢复时间3.2 鉴权与风控从登录态到支付验证的串联海淘系统的鉴权链路比纯国内电商更复杂因为涉及多个端App、小程序、H5还有海外用户通过独立站访问。我们统一在网关层做了Token校验和签名校验。客户端登录成功之后会拿到一个JWT格式的Access Token网关通过内置的JWT插件校验Token的签名和有效期。这里有个细节JWT本身是无状态的网关校验只依赖密钥不需要回源查询会话信息这在海淘这种高并发场景下是很必要的可以省掉一次Redis查询。但JWT的问题是无法主动吊销用户被风控拉黑之后已签发的Token在到期之前依然有效。我们的解法是在网关层额外维护了一个黑名单Redis每次请求命中JWT校验后再用O(1)的方式检查一下用户的Token ID是否在黑名单里。黑名单的Key用Token ID而不是用户ID避免误伤用户正常的多个设备会话。支付接口的鉴权会更严格一步。除了用户Token支付请求还需要带上签名参数由客户端使用私钥对请求参数做签名网关先验签名再用我们自定义的风控插件检查设备指纹、用户历史行为、订单金额等。如果命中了风控策略网关直接返回“需要人工审核”的错误码请求根本不会到达后端支付服务。这套逻辑放在网关层做的好处是所有需要安全校验的接口都被强制涵盖了新上线的接口如果忘了加注解网关默认也有一层基础防护不会出现裸奔的情况。3.3 限流与熔断大促压力测试下的参数计算限流是网关最核心的能力之一但只配置一个QPS数字还远远不够。做海淘大促之前我们做了一轮完整的压力测试并根据压测结果计算限流参数。我拿一个真实场景来说。订单提交接口在平常时段的峰值QPS大约是800我们就用这个数据来设定限流阈值。网关限流采用了令牌桶算法两个关键参数是速率rate和容量capacity。假设我们希望接口在日常峰值1.5倍的情况下依然能正常响应速率rate 800 * 1.5 1200每秒生成1200个令牌容量capacity 1200 * 2 2400应对突发流量峰值允许在瞬时最多有2400个请求同时通过为什么容量要设成速率的两倍因为流量往往是突发的用户不会匀速地每秒正好发800个请求点击一个“立即购买”按钮可能在几毫秒内就涌入一两千个请求。容量过小会让突发请求被直接打回虽然系统是安全的但用户体验很不好容量过大又起不到真正的保护作用。我们经过压测发现2倍容量既能把突发流量平滑掉又不会让后端服务承受超过其承载力的压力。熔断参数我们用的是错误率和最小请求数结合的方式。当下游服务在10秒内错误率超过30%并且请求总数超过1000时网关开启熔断后续请求在20秒内直接返回兜底数据不再调用下游。熔断恢复之后先用半开状态放少量流量做试探确认下游恢复了再完全放量。3.4 协议转换和数据裁剪多币种多语言的落地思路海淘业务还有两个网关必须处理的“脏活”多币种展示和本地化字段裁剪。举个例子用户在App端看到的商品价格是人民币但后端商品服务的价格数据可能是美元。早期这个换算是在商品服务里做的导致每个接口都要传汇率而且不同服务缓存的汇率版本还不一样会出现同一个商品在两个页面显示不同价格的低级问题。我们后面把汇率逻辑收敛到网关层网关从Redis读取当前最新汇率汇率数据由后台定时任务刷新在返回响应结果时对标记了amount字段的JSON节点自动做币种转换和格式化。具体实现是网关内置了一个响应体解析插件通过JSONPath定位到需要转换的字段乘以对应汇率后重新生成JSON。这样一个接口就能同时服务国内用户人民币展示和海外用户美元展示后端服务完全不需要关心币种逻辑。不过我得提醒一句网关做响应体改写是一个敏感操作必须谨慎。响应体解析和重写会占用CPU资源如果每个接口都做全量JSON扫描网关性能会明显下降。我们的处理方式是只对配置了特定x-currency-convert: true响应头的接口做改写其他接口直接透传避免无谓的性能损耗。4. 实操过程从零接入网关的完整配置记录4.1 环境准备和基础部署这部分我记录一下我们实际部署网关时的步骤方便新同学参考。我们的网关是部署在Kubernetes集群里的多副本部署保证高可用配置通过etcd分发到所有节点。部署APISIX时我们用了Helm Chart核心配置如下apisix: nodePorts: http: 31800 replicaCount: 2 resources: limits: cpu: 4 memory: 4Gi requests: cpu: 2 memory: 2Gi etcd: replicaCount: 3部署完成之后需要修改config.yaml把网关的Admin API设置成内网可访问并开启必要的插件apisix: enable_admin: true allow_admin: - 0.0.0.0/0 plugins: - jwt-auth - limit-req - limit-count - proxy-rewrite - grpc-transcode - serverless-pre-function这里有个容易踩的坑Admin API默认监听在0.0.0.0:9180如果不加访问控制等于把网关的管理权限暴露给了所有能访问这个端口的人。我们修改了默认配置让Admin API只监听在内网网段并且通过Ingress做了额外的基础认证。4.2 路由、限流、灰度发布的核心配置进入网关之后的第一件事就是创建路由。以用户商品查询接口为例我们配置一个路由把/api/v1/products/*转发到后端的商品服务curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: admin-key -X PUT -d { name: product_query_route, uri: /api/v1/products/*, methods: [GET], upstream: { type: roundrobin, nodes: { product-service.default.svc.cluster.local:8080: 1 } }, plugins: { limit-count: { count: 5000, time_window: 60, rejected_code: 429 }, jwt-auth: {} } }这个路由配置了limit-count插件在60秒窗口内允许最多5000个请求超过的请求返回429。同时开启了JWT鉴权也就是说这个路由下的请求必须携带合法的Token才能访问。灰度发布的场景我也说下。我们的商品详情页经常会做AB实验新版本服务先让5%的流量试跑。网关里可以通过traffic-split插件实现curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: admin-key -X PATCH -d { plugins: { traffic-split: { rules: [ { weighted_upstreams: [ { upstream: { type: roundrobin, nodes: { old-product-service:8080: 95 } } }, { upstream: { type: roundrobin, nodes: { new-product-service:8080: 5 } } } ] } ] } } }灰度发布一定要配合监控数据来决策不能直接拍脑袋把流量调到50%。我们的经验是先在低峰期观察新版本的延迟和错误率确认没问题后再逐步放量。4.3 上线前后的压测和观测指标网关接好之后不能直接上生产先做一轮完整的压测。我们用的工具是wrk和k6。压测的核心目标不是“跑多少QPS”而是确认网关在极限边缘的表现是否符合预期。压测时重点看三个指标P99延迟网关的P99延迟应该控制在50ms以内纯网关转发耗时不包含后端处理如果超过这个数字说明网关自身的性能有问题。错误率在逐步加压的情况下错误率应该保持为0直到达到配置的限流阈值后错误码变成429而不是5xx。CPU和内存网关进程CPU占用率超过70%就说明需要扩容了内存如果持续增长可能存在泄漏。我们第一次压测就发现一个问题网关的错误率在4000 QPS时出现了明显上涨但CPU还没满。排查后发现是Nginx的worker_connections参数设置太小导致可用连接数耗尽。调整后网关承受到了目标QPS。5. 真实踩坑汇总那些文档里没写明白的问题5.1 连接池泄漏导致的长尾延迟上线运行不到一周我们就发现接口P99延迟在慢慢上升。刚开始以为是后端服务变慢了排查了半天发现后端指标完全正常问题出在网关到后端服务的连接池上。我们的网关默认开启了keepalive但后端服务的连接池大小配置为100个在高峰期连接被占满后新的请求需要等待可用连接释放这个等待时间不断累积就造成了长尾延迟。解决方法是把连接池从100增加到500同时在后端服务增加对应的keepalive参数。这里需要注意连接池不是越大越好过大会导致后端服务资源被无效连接占用。5.2 幂等设计在大促重复支付中的救场海淘大促期间用户最容易遇到的问题是“支付成功但订单状态没更新”。原因是支付回调在网络抖动的时候支付渠道会重试发送回调如果网关和后端没有做幂等处理一条回调消息被处理两次就会导致订单状态错乱。网关层能做的是对支付回调这种外部请求做去重。我们在网关里加了一个插件对同一条回调消息的notification_id字段做Redis去重相同ID的事件在网关层就丢弃不给后端制造重复请求。同时后端的订单服务也设计了幂等表双重保险。5.3 日志与链路追踪的“尾采样”困局网关每天产生的请求日志量非常大全量存储的成本太高但只做随机采样又容易丢失异常请求的日志。我们用的方案是网关层根据响应状态码做“尾采样”即正常请求按1%概率记录错误请求响应码大于等于400按100%记录。这样既控制了存储成本又能保证排障时有足够的异常现场数据。云原生的分布式追踪系统我们用的是OpenTelemetry网关作为入口节点在请求头上注入trace_id后端服务在接收请求时自动关联。日常排查问题的时候只要从网关日志里拿到trace_id就能一路追踪到具体的服务节点效率提升非常明显。6. 海淘网关后续演进的方向思考网关在目前的架构里已经稳定运行挺长一段时间了但说实话每次大促前做压测我仍然会捏把汗。网关是全站流量的单点入口不管多副本怎么部署、怎么高可用它的风险始终是存在的。我后续想尝试的一个方向是“多活网关”也就是在不同区域部署多套网关实例通过DNS和全局负载均衡把流量分散到多个入口同时在网关层做数据同步。这样即使单个区域的网关整体故障其他区域的网关也能接管全部流量避免大面积服务不可用。另外一个方向是把网关和容器化服务发现结合得更紧密一些。目前我们的后端服务是部署在Kubernetes里的服务扩缩容之后网关去拿最新的服务实例列表还有一点延迟。后续我想用APISIX的服务发现集成直接对接Kubernetes Endpoint让网关动态感知后端节点变化减少配置手动维护的成本。最后也建议大家网关的能力已经足够丰富但不要一个插件可以搞定就把它接一大堆。每加一个插件网关的CPU和内存消耗都会增加而且插件之间的顺序和冲突排查起来也很耗神。保持网关的轻量、简单比功能堆叠更重要。
返回列表