本文目录

- 说在前面

- 日流量200亿,携程网关的架构设计

- 一、概述

- 二、高性能网关核心设计

- 2.1. 异步流程设计

- 2.2. 流式转发&单线程

- 2.3 其他优化

- 三、网关业务形态

- 四、网关治理

- 4.1 多协议兼容

- 4.2 路由模块

- 4.3 模块编排

- 五、总结

- 说在最后:有问题可以找老架构取经

- 部分历史案例

日流量200亿,携程网关的架构设计

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

一、概述

类似于许多企业的做法,携程 API 网关是伴随着微服务架构一同引入的基础设施,其最初版本于 2014 年发布。随着服务化在公司内的迅速推进,网关逐步成为应用程序暴露在外网的标准解决方案。后续的“ALL IN 无线”、国际化、异地多活等项目,网关都随着公司公共业务与基础架构的共同演进而不断发展。截至 2021 年 7 月,整体接入服务数量超过 3000 个,日均处理流量达到 200 亿。

在技术方案方面,公司微服务的早期发展深受 NetflixOSS 的影响,网关部分最早也是参考了 Zuul 1.0 进行的二次开发,其核心可以总结为以下四点:

  • server端:Tomcat NIO + AsyncServlet
  • 业务流程:独立线程池,分阶段的责任链模式
  • client端:Apache HttpClient,同步调用
  • 核心组件:Archaius(动态配置客户端),Hystrix(熔断限流),Groovy(热更新支持)

众所周知,同步调用会阻塞线程,系统的吞吐能力受 IO 影响较大。

作为行业的领先者,Zuul 在设计时已经考虑到了这个问题:通过引入 Hystrix,实现资源隔离和限流,将故障(慢 IO)限制在一定范围内;结合熔断策略,可以提前释放部分线程资源;最终达到局部异常不会影响整体的目标。

然而,随着公司业务的不断发展,上述策略的效果逐渐减弱,主要原因有两方面:

  • 业务出海:网关作为海外接入层,部分流量需要转回国内,慢 IO 成为常态
  • 服务规模增长:局部异常成为常态,加上微服务异常扩散的特性,线程池可能长期处于亚健康状态

全异步改造是携程 API 网关近年来的一项核心工作,本文也将围绕此展开,探讨我们在网关方面的工作与实践经验。

重点包括:性能优化、业务形态、技术架构、治理经验等。

二、高性能网关核心设计

2.1. 异步流程设计

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

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

我们改造的核心部分是将业务流程进行异步化,常见的异步场景有:

  • 业务 IO 事件:例如请求校验、身份验证,涉及远程调用
  • 自身 IO 事件:例如读取到了报文的前 xx 字节
  • 请求转发:包括 TCP 连接,HTTP 请求

从经验上看,异步编程在设计和读写方面相比同步会稍微困难一些,主要包括:

  • 流程设计&状态转换
  • 异常处理,包括常规异常与超时
  • 上下文传递,包括业务上下文与trace log
  • 线程调度
  • 流量控制

特别是在Netty上下文内,如果对 ByteBuf 的生命周期设计不完善,很容易导致内存泄漏。

围绕这些问题,我们设计了对应外围框架,最大努力对业务代码抹平同步/异步差异,方便开发;同时默认兜底与容错,保证程序整体安全。

在工具方面,我们使用了 RxJava,其主要流程如下图所示。

  • Maybe

  • RxJava 的内置容器类,表示正常结束、有且仅有一个对象返回、异常三种状态

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

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

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

  • Filter

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

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

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

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

五、总结

网关在各种技术交流平台上一直是备受关注的话题,有很多成熟的解决方案:易于上手且发展较早的 Zuul 1.0、高性能的 Nginx、集成度高的 Spring Cloud Gateway、日益流行的 Istio 等等。

最终的选型还是取决于各公司的业务背景和技术生态。

因此,在携程,我们选择了自主研发的道路。

技术在不断发展,我们也在持续探索,包括公共网关与业务网关的关系、新协议(如 HTTP3)的应用、与 ServiceMesh 的关联等等。

- End-

DailyMart是一个基于 DDD 和Spring Cloud Alibaba的微服务商城系统,采用SpringBoot3.x以及JDK17。旨在为开发者提供集成式的学习体验,并将其无缝地应用于实际项目中。该专栏包含领域驱动设计(DDD)、Spring Cloud Alibaba企业级开发实践、设计模式实际应用场景解析、分库分表战术及实用技巧等内容。如果你对这个系列感兴趣,可在本公众号回复关键词DDD获取完整文档以及相关源码。

本篇文章来源于微信公众号: JAVA日知录



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2024-08-01