写点什么

长连接网关技术专题 (十三):基于 Netty 的携程高性能网关异步改造实践

作者:JackJiang
  • 2025-08-07
    江苏
  • 本文字数:7981 字

    阅读完需:约 26 分钟

本文由携程技术 Butters 分享,原题“干货 | 日均流量 200 亿,携程高性能全异步网关实践”,下文有修订和重新排版。


1、引言

本文分享的是携程 API 网关全异步改造的实践分享,包括从 Zuul 1.0 同步架构升级为基于 Netty 的全异步架构,通过 RxJava 实现业务流程异步化,结合流式转发、ZGC 等技术显著提升性能,并构建控制面实现多协议统一治理与模块化编排。

 

 

2、作者介绍

Butters:携程软件技术专家,专注于网络架构、API 网关、负载均衡、Service Mesh 等领域。

3、专题目录

本文是专题系列文章的第 13 篇,总目录如下:

《长连接网关技术专题(一):京东京麦的生产级 TCP 网关技术实践总结》


《长连接网关技术专题(二):知乎千万级并发的高性能长连接网关技术实践》


《长连接网关技术专题(三):手淘亿级移动端接入层网关的技术演进之路》


《长连接网关技术专题(四):爱奇艺 WebSocket 实时推送网关技术实践》


《长连接网关技术专题(五):喜马拉雅自研亿级 API 网关技术实践》


《长连接网关技术专题(六):石墨文档单机 50 万 WebSocket 长连接架构实践》


《长连接网关技术专题(七):小米小爱单机 120 万长连接接入层的架构演进》


《长连接网关技术专题(八):B 站基于微服务的 API 网关从 0 到 1 的演进之路》


《长连接网关技术专题(九):去哪儿网酒店高性能业务网关技术实践》


《长连接网关技术专题(十):百度基于 Go 的千万级统一长连接服务架构实践》


《长连接网关技术专题(十一):揭秘腾讯公网 TGW 网关系统的技术架构演进》


《长连接网关技术专题(十二):大模型时代多模型 AI 网关的架构设计与实现》


《长连接网关技术专题(十三):基于 Netty 的携程高性能网关异步改造实践》(* 本文)


4、技术背景

与许多公司一样,携程 API 网关也是同微服务架构一起引入的基础设施,最早版本发布于 2014 年。随着服务化在公司的快速推进,网关逐渐成为应用暴露到外网的标准方案。后来的“ALL IN 无线”、国际化、异地多活等,网关跟随着公司公共业务与基础架构共同演进。

技术方案上,公司微服务早期发展受 NetflixOSS 影响较深,网关方面最早也是参考了 Zuul 1.0 进行的二次开发。

核心可概括为四点:

  • 1)server 端:Tomcat NIO + AsyncServlet;

  • 2)业务流程:独立线程池,分阶段的责任链模式;

  • 3)client 端:Apache HttpClient,同步调用;

  • 4)核心组件:Archaius(动态配置客户端),Hystrix(熔断限流),Groovy(热更新支持)。

众所周知,同步调用阻塞线程,系统吞吐受 IO 影响大。作为行业先驱,Zuul 在设计上也考虑到了这点——通过引入 Hystrix,资源隔离配合限流,将故障(慢 IO)框在一定范围内;配合熔断策略,可提前释放部分线程资源;最终达到局部异常不影响全局的目的。

但随着公司业务的发展,上述策略效果逐渐减弱。

主要原因在于两方面的变动:

  • 1)业务出海:网关作为海外接入层,部分流量需转回国内,慢 IO 成为常态;

  • 2)服务规模增长:局部异常常态化,加上微服务异常扩散的特性,线程池可能长期处于亚健康状态。

全异步改造是携程 API 网关近年的一项核心工作点,本文也将由此展开,聊一聊我们在网关方面的工作与实践。重点包括:性能优化、业务形态、技术架构、治理经验等。


5、高性能网关核心设计 1:异步流程设计

全异步 = server 端异步 + 业务流程异步 + client 端异步

对于 server 与 client 端,我们选择了Netty框架,NIO/Epoll + Eventloop 本身就是事件驱动的设计。

改造核心在于业务流程的异步化,常见异步场景包括:

  • 1)业务 IO 事件:如请求校验、身份认证,涉及远程调用;

  • 2)自身 IO 事件:如读取到了报文的前 xx 字节;

  • 3)请求转发:包括 TCP 连接,HTTP 请求。

经验上,异步编程相比同步在设计、读写上都会困难一些。

所谓的困难,一般包括:

  • 1)流程设计 &状态转换;

  • 2)异常处理,包括常规异常与超时;

  • 3)上下文传递,包括业务上下文与 trace log;

  • 4)线程调度;

  • 5)流量控制。

尤其在 Netty 上下文内,对 ByteBuf 生命周期设计的不完善,很容易造成内存泄漏。围绕这些问题,我们设计了对应外围框架,最大努力对业务代码抹平同步/异步差异,方便开发;同时默认兜底与容错,保证程序整体安全。工具上借助了 RxJava,主要流程如下图所示。

Maybe:

  • 1)RxJava 内置容器类,标识正常结束、有且仅有一个对象返回、异常三种状态;

  • 2)响应式,方便整体状态机设计,自带异常处理、超时、线程调度等封装;

  • 3)Maybe.empty()/Maybe.just(T),适用同步场景;

  • 4)工具类 RxJavaPlugins,方便切面逻辑封装。

Filter:

  • 1)代表一块独立的业务逻辑,同步 &异步业务统一接口,返回 Maybe;

  • 2)异步场景(如远程调用)统一封装,如涉及线程切换,通过 maybe.obesrveOn(eventloop)切回;

  • 3)异步 filter 默认增加超时,并按弱依赖处理,忽略错误。


public interface Processor<T> {


ProcessorType getType(); int getOrder(); boolean shouldProcess(RequestContext context); //对外统一封装为 Maybe Maybe process(RequestContext context) throws Exception;


}


public abstract class AbstractProcessor implements Processor {


//同步&无响应,继承此方法
//场景:常规业务处理
protected void processSync(RequestContext context) throws Exception {}


//同步&有响应,继承此方法,健康检测
//场景:健康检测、未通过校验时的静态响应
protected T processSyncAndGetReponse(RequestContext context) throws Exception {
process(context);
return null;
};


//异步,继承此方法
//场景:认证、鉴权等涉及远程调用的模块
protected Maybe processAsync(RequestContext context) throws Exception
复制代码


{


    T response = processSyncAndGetReponse(context);
if (response == null) {
return Maybe.empty();
} else {
return Maybe.just(response);
}
};


@Override
public Maybe process(RequestContext context) throws Exception {
Maybe<T> maybe = processAsync(context);
if (maybe instanceof ScalarCallable) {
//标识同步方法,无需额外封装
return maybe;
} else {
//统一加超时,默认忽略错误
return maybe.timeout(getAsyncTimeout(context), TimeUnit.MILLISECONDS,
Schedulers.from(context.getEventloop()), timeoutFallback(context));
}
}


protected long getAsyncTimeout(RequestContext context) {
return 2000;
}


protected Maybe<T> timeoutFallback(RequestContext context) {
return Maybe.empty();
}
复制代码

整体流程:

  • 1)沿用责任链的设计,分为 inbound、outbound、error、log 四阶段;

  • 2)各阶段由一或多个 filter 组成;

  • 3)filter 顺序执行,遇到异常则中断,inbound 期间任意 filter 返回 response 也触发中断。


public class RxUtil{


  //组合某阶段(如Inbound)内的多个filter(即Callable<Maybe<T>>)
public static Maybe concat(Iterable
Iterator
while (sources.hasNext()) {
Maybe<T> maybe;
try {
maybe = sources.next().call();
} catch (Exception e) {
return Maybe.error(e);
}
if (maybe != null) {
if (maybe instanceof ScalarCallable) {
//同步方法
T response = ((ScalarCallable<T>)maybe).call();
if (response != null) {
//有response,中断
return maybe;
}
} else {
//异步方法
if (sources.hasNext()) {
//将sources传入回调,后续filter重复此逻辑
return new ConcattedMaybe(maybe, sources);
} else {
return maybe;
}
}
}
}
return Maybe.empty();
}
复制代码


}

public class ProcessEngine{


  //各个阶段,增加默认超时与错误处理
private void process(RequestContext context) {
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) //inbound阶段
.toSingle() //获取response
.flatMapMaybe(response -> {
context.setOriginResponse(response);
return RxUtil.concat(outboundTask);
}) //进入outbound
.onErrorResumeNext(e -> {
context.setThrowable(e);
return RxUtil.concat(errorTask).flatMap(response -> {
context.resetResponse(response);
return RxUtil.concat(outboundTask);
});
}) //异常则进入error,并重新进入outbound
.flatMap(response -> RxUtil.concat(logTask)) //日志阶段
.timeout(asyncTimeout.get(), TimeUnit.MILLISECONDS, Schedulers.from(context.getEventloop()),
Maybe.error(new ServerException(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()
);
}
复制代码


}

6、高性能网关核心设计 2:流式转发 &单线程

以 HTTP 为例,报文可划分为 initial line/header/body 三个组成部分:

在携程,网关层业务不涉及 body。因为无需全量存,所以解析完 header 后可直接进入业务流程。

于此同时,如果接收到 body 部分:

  • 1)若已向 upstream 转发请求,则直接转发;

  • 2)否则需要将其暂存,待业务流程处理完毕,同 initial line/header 一并发送;

  • 3)对 upstream 端响应的处理方式亦然。

对比完整解析 HTTP 报文的方式,这样处理:

  • 1)更早进入业务流程,意味着 upstream 更早接收到请求,能有效降低网关这层引入的延迟;

  • 2)body 生命周期被压缩,可降低网关自身的内存开销。

虽说提升了性能,但流式的方式也极大提升了整个流程的复杂度

非流式场景下,Netty Server 端编解码、入向业务逻辑、Netty Cerver 端编解码、出向业务逻辑,各子流程相互独立,各自处理完整的 HTTP 对象。

采取流式后,请求则可能同时处于多流程内,引入的困难可归纳为以下三点:

  • 1)线程安全问题:不同流程若采用不同线程,会涉及上下文的并发修改;

  • 2)多阶段联动:比如 Netty Server 请求接收一半遇到了连接中断,此时已经连上了 upstream,那么 upstream 侧的协议栈是走不完的,也必须随之关闭连接;

  • 3)边缘场景处理:比如 upstream 在请求未完整发送情况下返回了 404/413,是选择继续发送、走完协议栈、让连接能够复用,还是选择提前终止流程,节约资源,但同时放弃连接?再比如,upstream 已收到请求但未响应,此时 Netty Server 突然断开,Netty Client 是否也要随之断开?等等。

针对这些场景,我们采用了单线程的方式,核心设计:

  • 1)上线文绑定 Eventloop,Netty Server/业务流程/Netty Client 在同个 eventloop 执行;

  • 2)异步 filter 如因 IO 库的关系,必须使用独立线程池,那在后置处理上必须切回;

  • 3)流程内资源做必要的线程隔离(如连接池)。

采用单线程的好处:

  • 1)杜绝了并发问题,在多阶段联动、边缘场景问题处理时,整个系统也处于确定的状态下,有效降低了开发难度与风险;

  • 2)减少线程切换,一定程度上也能够提升性能。

与之相对的,因为 worker 线程数较少(一般等于 CPU 核数),eventloop 内必须完全杜绝 IO 操作,否则将对系统吞吐造成毁灭性打击。


7、高性能网关核心设计 3:其他优化

内部变量懒加载:针对请求的 cookie/query 等字段,如无必要,不提前进行字符串解析

堆外内存 &零拷贝:结合前文流式转发的设计,进一步降低系统内存开销

ZGC:项目因 TLSv1.3 而引入了 JDK11(JDK8 支持相对较晚,8u261 版本,2020.7.14),自然也对新一代的 GC 算法进行了尝试,实际表现也确实不负盛名。除 CPU 占用有少量提升,整体 GC 耗时下降非常明显。



定制的 HTTP 编解码:HTTP 的悠久历史,加之协议自身的开放性,催生了许多“坏实践”,轻则影响成功率,重则威胁网站安全,举两个例子:

流量治理:诸如请求体过大(413)、uri 过长(414)、非 ASCII 字符(400)等问题,一般 WebServer 会选择直接拒绝并返回对应状态码。由于直接跳过了业务流程,这类问题在统计、服务定为、排障上都会比较麻烦。扩展编解码,让问题请求也能够走完路由流程,可以帮助解决非标流量的治理问题。

请求过滤:如 request smuggling(Netty 4.1.61.Final 修复,2021.3.30 发布)。扩展编解码,增加自定义的校验逻辑,让安全补丁能够更快落地。


8、网关业务形态

作为独立、统一的入向流量收口点,网关对公司的价值主要体现在三方面:

  • 1)解耦不同网络环境:典型场景包括内网 &外网、生产环境 &办公区、IDC 内部不同安全域、专线等;

  • 2)天然的公共业务切面:包括安全 &认证 &反爬、路由 &灰度、限流 &熔断 &降级、监控 &告警 &排障等;

  • 3)高效、灵活的流量控制。



这里展开讲几个细分场景。

私有协议:

  • 1)在收口的客户端(APP),由框架层拦截用户发起的 HTTP 请求,通过私有协议(SOTP)的方式发往服务端;

  • 2)选址方面:①通过服务端下发 IP,杜绝 DNS 劫持;②连接预热;③自定义的选址策略,可依据网络质量、环境等自行切换;

  • 3)交互方式上:①更加轻量的协议体;②统一加密 &压缩 &多路复用;③协议在入口处由网关统一转换,对业务透明。

链路优化:核心是引入接入层,让远距离用户就近访问,缓解握手开销过大的问题。同时,因为接入层与 IDC 是可控的两端,网络链路选择、协议交互模式上都有更大的优化空间。

异地多活:区别于按比例分配、就近访问策略等,异地多活模式下,网关(接入层)需按照业务维度的 shardingKey 进行分流(如 userId),防止底层数据冲突。


9、网关治理概述

下图总结了线上网关的工作状态:

横向对应我们的业务流程:不同渠道(APP、H5、小程序、供应商)、不同协议(HTTP、SOTP)的流量经由负载均衡打到网关,经过系列业务逻辑的处理,最终转发至后端服务。经历了第二章的改造后,横向业务在性能、稳定性上都得到了较好的提升。

另一方面:由于多渠道/协议的存在,线上网关按业务划分,进行了独立集群的部署。业务差异(路由数据、功能模块)早期通过独立代码分支管理,随着分支数的增加,整体的运维复杂度越来越高。系统设计中,复杂度往往也意味着风险。如何对多协议、多角色的网关实施统一治理,如何以较低的成本,快速为新业务搭建定制化网关,成为了我们后一阶段的工作重心。

解决方案也比较直观地在图中画了出来:

  • 1)是协议上兼容处理,让线上代码跑在一套框架下;

  • 2)是引入控制面,对线上网关的差异特性进行统一管理。


10、网关治理能力 1:多协议兼

协议兼容的做法并不新鲜,整体可以参考 Tomcat 对 HTTP/1.0、HTTP/1.1、HTTP/2.0 的抽象。HTTP 自身虽然在各个版本内新增了大量 feature,但我们在做业务开发时通常感知不到这些,核心在于 HttpServletRequest 接口的抽象。

在携程,网关面对的都是请求—响应模式的无状态协议,报文组成上也可以划分为元数据、扩展头、业务报文三部分,因此可以比较方便地进行类似的尝试。

对应工作可以用以下两点概括:

  • 1)协议适配层:用于屏蔽不同协议的编解码、交互模式、对 TCP 连接的处理等;

  • 2)定义通用中间模型与接口:业务面向中间模型与接口编程,更好地聚焦到协议对应的业务属性上去。

11、网关治理能力 2:路由模块

路由模块是控制面的两个主要组成部分之一。

除了管理网关—服务间的映射关系,服务本身可以用以下模型概括:


{


  //匹配方式
"type": "uri",


//HTTP默认采用uri前缀匹配,内部通过树结构寻址;私有协议(SOTP)通过服务唯一标识定位。
"value": "/hotel/order",
"matcherType": "prefix",


//标签与属性
//用于portal端权限管理、切面逻辑运行(如按核心/非核心)等
"tags": [
"owner_admin",
"org_framework",
"appId_123456"
],
"properties": {
"core": "true"
},


//endpoint信息
"routes": [{
//condition用于二级路由,如按app版本划分、按query重分配等
"condition": "true",
"conditionParam": {},
"zone": "PRO",


//具体服务地址,权重用于灰度场景
"targets": [{
"url": "http://test.ctrip.com/hotel",
"weight": 100
}
]
}]
复制代码


}

12、网关治理能力 3:模块编排

模块编排是控制面的另一项核心部分。

我们在网关处理流程内预留了多个阶段(上图中用粉色标记)。除开熔断、限流、日志等通用功能,运行时不同网关所需执行的业务功能由控制面统一下发。功能本身在网关内部有独立的代码模块,控制面额外定义了功能对应的执行条件、参数、灰度比例、错误处理方式等。这种编排方式也在侧面保证了模块间的解耦。

{


  //模块名称,对应网关内部某个具体模块
"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"
复制代码


}


13、本文小结

网关长期以来都是各类技术交流平台上的热点,方案也非常丰富:发展早、易上手的 Zuul1.0、高性能的 Nginx、集成度高的 SpringCloud Gateway、如日中天的 Istio 等等。最终决定选型的还是各公司自身的业务背景与技术生态。也正因此,在携程我们选择了自研的道路。

技术不断发展,我们也在持续探索,公共网关同业务网关的关系、新协议的落地(HTTP3)、与 ServiceMesh 的关系等等,真诚欢迎有兴趣的同学一起参与讨论。(本文已同步发布于:http://www.52im.net/thread-4854-1-1.html

14、参考资料

[1] 京东京麦的生产级 TCP 网关技术实践总结

[2] 手淘亿级移动端接入层网关的技术演进之路

[3] 喜马拉雅自研亿级 API 网关技术实践

[4] 小米小爱单机 120 万长连接接入层的架构演进

[5] B 站基于微服务的 API 网关从 0 到 1 的演进之路

[6] 去哪儿网酒店高性能业务网关技术实践

[7] 少啰嗦!一分钟带你读懂 Java 的 NIO 和经典 IO 的区别

[8] 史上最强 Java NIO 入门:担心从入门到放弃的,请读这篇!

[9] Java 的 BIO 和 NIO 很难懂?用代码实践给你看,再不懂我转行!

[10] 史上最通俗 Netty 框架入门长文:基本介绍、环境搭建、动手实战

[11] 新手入门:目前为止最透彻的的 Netty 高性能原理和框架架构解析


用户头像

JackJiang

关注

还未添加个人签名 2019-08-26 加入

开源IM框架MobileIMSDK、BeautyEye的作者。

评论

发布
暂无评论
长连接网关技术专题(十三):基于Netty的携程高性能网关异步改造实践_网络编程_JackJiang_InfoQ写作社区