携程百亿日流量Netty网关架构演进路线
携程 API 网关从 2014 年发布,早期参考 Zuul 1.0 二次开发,采用 Tomcat NIO + AsyncServlet、独立线程池责任链、Apache HttpClient 同步调用。随着业务出海和服务规模增长,慢 IO 和局部异常逐渐成为常态,网关必须从同步走向全异步。
截至 2021 年 7 月,携程网关接入服务数量超过 3000 个,日均处理流量达到 200 亿。
一、异步流程设计
全异步等于三部分异步:
server 端异步 + 业务流程异步 + client 端异步Server 和 Client 使用 Netty,业务流程使用 RxJava。核心抽象是 Processor 和 Maybe。
publicinterfaceProcessor<T>{ProcessorTypegetType();intgetOrder();booleanshouldProcess(RequestContextcontext);Maybe<T>process(RequestContextcontext)throwsException;}同步、异步 Filter 统一返回 Maybe,异步 Filter 默认增加超时,并按弱依赖处理、忽略错误。
publicabstractclassAbstractProcessorimplementsProcessor{protectedvoidprocessSync(RequestContextcontext)throwsException{}protectedTprocessSyncAndGetReponse(RequestContextcontext)throwsException{process(context);returnnull;}protectedMaybe<T>processAsync(RequestContextcontext)throwsException{Tresponse=processSyncAndGetReponse(context);if(response==null){returnMaybe.empty();}else{returnMaybe.just(response);}}@OverridepublicMaybe<T>process(RequestContextcontext)throwsException{Maybe<T>maybe=processAsync(context);if(maybeinstanceofScalarCallable){returnmaybe;}else{returnmaybe.timeout(getAsyncTimeout(context),TimeUnit.MILLISECONDS,Schedulers.from(context.getEventloop()),timeoutFallback(context));}}protectedlonggetAsyncTimeout(RequestContextcontext){return2000;}protectedMaybe<T>timeoutFallback(RequestContextcontext){returnMaybe.empty();}}责任链组合
整体流程沿用责任链,分为 inbound、outbound、error、log 四个阶段。Filter 顺序执行,遇到异常或 inbound 返回 response 时中断。
publicclassRxUtil{publicstatic<T>Maybe<T>concat(Iterable<?extendsCallable<Maybe<T>>>iterable){Iterator<?extendsCallable<Maybe<T>>>sources=iterable.iterator();while(sources.hasNext()){Maybe<T>maybe;try{maybe=sources.next().call();}catch(Exceptione){returnMaybe.error(e);}if(maybe!=null){if(maybeinstanceofScalarCallable){Tresponse=((ScalarCallable<T>)maybe).call();if(response!=null){returnmaybe;}}else{if(sources.hasNext()){returnnewConcattedMaybe(maybe,sources);}else{returnmaybe;}}}}returnMaybe.empty();}}ProcessEngine 负责串起四个阶段,并做全局兜底超时和资源释放:
publicclassProcessEngine{privatevoidprocess(RequestContextcontext){List<Callable<Maybe<Response>>>inboundTask=get(ProcessorType.INBOUND,context);List<Callable<Maybe<Void>>>outboundTask=get(ProcessorType.OUTBOUND,context);List<Callable<Maybe<Response>>>errorTask=get(ProcessorType.ERROR,context);List<Callable<Maybe<Void>>>logTask=get(ProcessorType.LOG,context);RxUtil.concat(inboundTask).toSingle().flatMapMaybe(response->{context.setOriginResponse(response);returnRxUtil.concat(outboundTask);}).onErrorResumeNext(e->{context.setThrowable(e);returnRxUtil.concat(errorTask).flatMap(response->{context.resetResponse(response);returnRxUtil.concat(outboundTask);});}).flatMap(response->RxUtil.concat(logTask)).timeout(asyncTimeout.get(),TimeUnit.MILLISECONDS,Schedulers.from(context.getEventloop()),Maybe.error(newServerException(500,"Async-Timeout-Processing"))).subscribe(unused->{logger.error("this should not happen, "+context);context.release();},e->{logger.error("this should not happen, "+context,e);context.release();},()->context.release());}}二、流式转发与单线程
HTTP 报文分为 initial line、header、body。携程网关业务不涉及 body,因此解析完 header 后即可进入业务流程,body 可边收边转发,降低延迟和内存占用。
流式转发带来三个挑战:
- 线程安全问题;
- 多阶段联动问题;
- 边缘场景处理问题。
携程采用单线程方式应对:
- RequestContext 绑定 EventLoop;
- Netty Server、业务流程、Netty Client 在同一 EventLoop 执行;
- 异步 Filter 如果必须使用独立线程池,后置处理必须切回;
- 连接池等资源做线程隔离。
单线程减少竞争和状态不确定性,但 EventLoop 内必须完全避免阻塞 IO。
三、其他优化
- 内部变量懒加载,cookie/query 不必要时不提前解析;
- 堆外内存和零拷贝,减少内存占用;
- ZGC 降低 GC 耗时;
- 定制 HTTP 编解码,处理 413、414、非 ASCII 字符等问题;
- 请求过滤,快速应用 request smuggling 等安全补丁。
四、业务形态
网关作为统一入向流量收口点,价值体现在:
- 解耦不同网络环境;
- 提供安全、认证、反爬、路由灰度、限流熔断、监控告警等公共切面;
- 提供私有协议、链路优化、异地多活等流量控制能力。
私有协议 SOTP 由客户端框架层拦截 HTTP 请求,通过服务端分配 IP、连接预热和自定义选址,避免 DNS 劫持,并在入口统一转换协议。
异地多活中,网关需要根据业务维度的 shardingKey(如 userId)分流,防止底层数据冲突。
五、网关治理:控制面
多协议、多角色的网关如果通过代码分支管理差异,运维复杂度会持续升高。携程的治理方案是:
- 协议适配层屏蔽不同协议的编解码和交互模式;
- 定义通用中间模型与接口;
- 引入控制面统一管理差异。
路由模型使用 JSON 描述匹配方式、标签、属性和 targets:
{"type":"uri","value":"/hotel/order","matcherType":"prefix","tags":["owner_admin","org_framework","appId_123456"],"properties":{"core":"true"},"routes":[{"condition":"true","conditionParam":{},"zone":"PRO","targets":[{"url":"http://test.ctrip.com/hotel","weight":100}]}]}模块编排则描述模块名称、执行阶段、顺序、灰度比例、条件和参数:
{"name":"addResponseHeader","stage":"PRE_RESPONSE","ruleOrder":0,"grayRatio":100,"condition":"true","conditionParam":{},"actionParam":{"connection":"keep-alive","x-service-call":"${request.func.remoteCost}","Access-Control-Expose-Headers":"x-service-call","x-gate-root-id":"${func.catRootMessageId}"},"exceptionHandle":"return"}六、总结
携程网关的演进表明:全异步不是把同步调用换成 Future,而是要从 Server、业务流程、Client 三端统一设计。流式转发和单线程闭环进一步降低延迟和并发复杂度,控制面则把多协议、多集群的差异治理从代码分支中解放出来。