API网关与美团Shepherd
API网关的概念
API网关是一种架构模式,用以管理一个单体架构的服务被拆分成很多个微服务系统后急剧增多的API,在日益增多的微服务的系统构建场景下,存在这样一种用以统一管理各个微服务模块的API的中间件的需求,于是API网关就诞生了
API网关,就像它的名字一样,和普通的网关“调配”的职能类似,API网关也是用来完成各个微服务API之间的“ 调配 ”工作,主要是负责对外部请求的协议转换,鉴权,参数检验,监控等,是运行于外部请求和内部服务之间的一个流量入口,通俗地来讲,就像是电影院的工作人员,接收顾客的电影票,转换为几号厅哪个座位的信息,同时还要负责观察顾客电影票是否正确,是否到了看电影的时间,有没有带味大的食物等,类似这种任务就是API网关需要做的
为什么要做Shepherd API网关
在没有Shepherd API网关之前,美团业务研发人员如果要将内部服务输出为对外的HTTP API接口。通常要搭建一个Web应用,用于完成基础的鉴权、限流、监控日志、参数校验、协议转换等工作,同时需要维护代码逻辑、基础组件的升级,研发效率相对比较低。此外,每个Web应用都需要维护机器、配置、数据库等,资源利用率也非常差。
美团内部一些业务线苦于没有现成的解决方案,根据自身业务特点,研发了业务相关的API网关。放眼业界,亚马逊、阿里巴巴、腾讯等公司也都有成熟的API网关解决方案。
因此,Shepherd API网关项目正式立项,我们的目标是为美团提供高性能、高可用、可扩展的统一API网关解决方案,让业务研发人员通过配置的方式即可对外开放功能和数据。
Shepherd API网关架构
主要包含三个主要模块:控制面 - 配置中心 - 数据面
控制面
控制面也被分为两个部分
- 管理平台,负责完成API的全生命周期管理以及配置下发
- 监控中心,负责完成API请求监控数据的收集和业务告警功能
关于API的生命周期,大概包括这些阶段:
- API创建(此处Shepherd做了参数录入与DSL(动态脚本语言)一键生成),大致使用就是接到业务之后确定所需参数,然后通过DSL脚本生成API
- API测试(测试并创建文档)
- API审核
- API发布(灰度发布(让一部分系统先使用起来) -> 正式发布 -> API回滚)
- API管控(API鉴权 -> API流量控制)
- API监控(失败告警 / 请求日志)
- API下线(资源回收 / 停止监控)
分别由管理平台和监控平台负责,整体来看,就是实现了API的流水线式的自动脚本生成,完整的API发放/回滚/下线管理和API使用情况监控
配置中心
配置中心存放API相关的配置信息,并使用自定义的DSL(领域专用语言)描述,用于向API网关的数据面下发API的路由/规则/组件等配置变更
配置中心的设计上使用统一配置管理服务Lion(Java配置中心,用于解决分布式系统中配置管理的问题)和本地缓存结合的方式,实现动态配置,不停机发布。API的配置如下图所示:
配置中心相当于是对API配置信息进行了统一的管理,确定了每个API配置信息都是统一格式,方便接收方接收,使用方式大概是由控制面控制,收到请求信息后根据API创建的相关信息创建API的配置信息,接着把配置信息发放到数据面供其使用对应的API
数据面
数据面包含以下比较关键的部分:
API路由
API网关的数据面在感知到API配置后,会在内存中建立请求路径与API配置的路由信息。通常HTTP请求路径上,会包含一些路径变量,考虑到性能问题,Shepherd没有采用正则匹配的方式,而是设计了两种数据结构来存储。如下图所示:
- 不包含路径变量的直接映射MAP结构,Key就是完整的域名和路径信息,Value是具体的API配置
- 包含路径变量的前缀树数据结构,通过前缀匹配的方式,先进行叶子节点精准查找,并将查找节点入栈处理,如果匹配不上,则将栈顶节点出栈,再将同级的变量节点入栈,如果仍然找不到,则继续回溯,直到找到/没找到路径节点并退出
设计这两种数据结构可以恰好对应HTTP请求中包含/不包含路径变量的两种情况,如果请求中不包含路径变量,也就是请求的API不需要根据请求中给出的条件进行限定,则直接根据HTTP请求中的域名和路径信息进行API的查询;如果请求中包含路径变量,也就是后端需要根据HTTP中传输的这个路径变量寻找对应的API(可能存在的形式如下:https://example.com/resource/value1/value2 ),这里的value1/value2就是通过路径变量传输的变量,然后依次按照顺序去前缀树中进行匹配,这样设计清晰地将两种情况分开处理,如果使用常见的正则表达式匹配的方式,性能不如这种处理方式,同时不方便debug
功能组件
当请求流量命中API请求路径进入服务端,具体处理逻辑由DSL中配置的一系列功能组件完成。网关提供了丰富的功能组件集成,包括链路追踪、实时监控、访问日志、参数校验、鉴权、限流、熔断降级、灰度分流等,如下图所示:
这些功能组件组成一条责任链,每个组件负责管理API的一部分使用
这部分用于实现API网关功能中的 监控/限流/鉴权/参数检验 等
协议转换 & 服务调用
API调用的最后一步,就是协议转换以及服务调用了。网关需要完成的工作包括:获取HTTP请求参数、Context本地参数,拼装后端服务参数,完成HTTP协议到后端服务的协议转换,调用后端服务获取响应结果并转换为HTTP响应结果
大致步骤如下:先从前端的HTTP请求中获取携带的参数(使用JsonPath表达式获取不同部位的参数值,如header,path等),然后根据获取的参数值生成服务参数DSL,最后将服务参数DSL通过RPC泛化调用进行使用
这部分也就是API网关所需要实现的 协议转换 的部分
Shepherd API网关高可用设计
排除性能隐患
Shepherd对API请求做了全异步化处理,请求通过Jetty IO线程异步提交到业务处理线程池,调用后端服务使用RPC或HTTP框架的异步方式,释放了由于网络等待引起的线程占用,使线程数不再成为网关的瓶颈。下图是使用Jetty容器时,服务端的请求线程处理逻辑:
在Shepherd服务上线稳定运行一年以后,我们再次对性能进行优化,并且做了一次网络框架升级,将Jetty容器全面替换为Netty网络框架,性能提升10%以上,Shepherd端到端的QPS成功提升到15000以上。下图是使用Netty框架时,服务端的请求线程处理逻辑:
这种设计,关键就在于 全异步化处理 ,异步化处理让APP端的请求与服务端的处理结果变得非阻塞,这样就省去了等待结果的时间,同时这样通过线程池的方式处理请求,由于不再是一个请求等待一个结果这样的一个过程让一个线程管理,线程池的线程个数也不再会影响整体的性能
服务隔离
集群隔离
Shepherd在美团技术团队初步设计的时候,参考了之前缓存/任务调度等成熟的组件的研发经验,将服务节点集群按照业务线维度进行集群隔离,同样也支持重要业务独立部署
我想这样做的目的主要是防止不同业务之间相互影响,可能不同业务之间对性能和并发的要求级别不同,所以分别部署方便设置不同的系统参数,另一方面集群隔离还能帮助运维老哥维护集群
请求隔离
服务节点维度,Shepherd支持请求的快慢线程池隔离。快慢线程池隔离主要用于一些使用了同步阻塞组件的API,例如SSO鉴权、自定义鉴权等,可能导致长时间阻塞共享业务线程池。
快慢隔离的原理是统计API请求的处理时间,将请求处理耗时较长,超过容忍阈值的API请求隔离到慢线程池,避免影响其他正常API的调用。
除此之外,Shepherd也支持业务研发人员配置自定义线程池进行隔离。具体的线程隔离模型如下图所示:
这方面的隔离实现,我想与上面的集群隔离有相同的考虑,也就是针对实际情况对系统的要求不同,提供不同的实现方案,这里将请求处理耗时较长的API调用,也就是“慢”的调用,放到“慢”的线程池中,因为线程池的大小是有上限的,如果这些本来就会“慢”或者因为特殊情况而超时的API请求一直占用需要尽可能快地返回结果的API调用需要的线程池的位置,就会让这些本来产生局部“慢”的影响的API请求进一步地影响了整个系统,甚至导致大部分系统不可用,这样一设计就保证了正常的需要“快”的API的调用的正常处理
稳定性保障
Shepherd提供了一些常规的稳定性保障手段,来保证自身和后端服务的可用性。如下图所示:
- 流量管控:从用户自定义UUID限流、App限流、IP限流、集群限流等多个维度提供流量保护。
- 请求缓存:对于一些幂等的、查询频繁的、数据及时性不敏感的请求,业务研发人员可开启请求缓存功能。
- 超时管理:每个API都设置了处理超时时间,对于超时的请求,进行快速失败的处理,避免资源占用。
- 熔断降级:支持熔断降级功能,实时监控请求的统计信息,达到配置的失败阈值后,自动熔断,返回默认值。
这些稳定性保障手段都是常见的可用性优化手段,流量管控是保证过多的超出系统负载的流量不影响系统整体,防止雪崩等;请求缓存是将常见的请求存入缓存当中,这样下一次查询这个请求就能直接命中缓存而并非去数据库中寻找,这样提高了部分热点请求的处理速度;超时管理主要是处理那些不正常的请求处理,防止一直占用系统资源;熔断降级和流量管控有点类似,当一个级别的请求(参数级/IP级/接口级等)过多时,为了避免对系统的影响,要对该类请求降级处理,也就是做出限流处理
请求安全
请求安全是API网关非常重要的能力,Shepherd集成了丰富的安全相关的系统组件,包括有基础的请求签名、SSO单点登录、基于SSO鉴权的UAC/UPM访问控制、用户鉴权Passport、商家鉴权EPassport、商家权益鉴权、反爬等等。业务研发人员只需要简单配置,即可使用。
可灰度
API网关作为请求入口,往往肩负着请求流量灰度验证的重任。
灰度场景
Shepherd在灰度能力上,支持灰度API自身逻辑,也支持灰度下游服务,也可以同时灰度API自身逻辑和下游服务。如下图所示:
灰度前面也提到过,这种概念我想是来源于黑白灰这三个色调,一般就是指未黑但已不白,产品场景下就是部分投入使用,前面的灰度测试也是让部分服务更新来小批次地进行测试,如果没问题再批量更新,在这里应该是服务有部分更新的时候,先将一部分流量通过路由导入到新的服务节点进行服务,逐步地进行服务更新,在这里也再次强调了API网关的 路由 功能
灰度策略
Shepherd支持丰富的灰度策略,可以按照比例数灰度,也可以按照特定条件灰度。
这里就是说明了Shepherd的灰度策略多样化,同时也可以自定义策略,可以根据实际生产使用情况进行选择
监控告警
立体化监控
Shepherd提供360度的立体化监控,从业务指标、机器指标、JVM指标提供7x24小时的专业守护,如下表:
序号 | 监控模块 | 主要功能 |
---|---|---|
1 | 统一监控Raptor | 实时上报请求调用信息、系统指标,负责应用层(JVM)监控,系统层(CPU、IO、网络)监控 |
2 | 链路追踪Mtrace | 负责全链路参数透传,全链路追踪监控 |
3 | 日志监控Logscan | 监控本地日志异常关键字:如5xx状态码,空指针异常等 |
4 | 远程日志中心 | API请求日志、Debug日志、组件日志等可上报远程日志中心 |
5 | 健康检查Scanner | 对网关节点进行心跳检测和API状态检测,及时发现异常节点和异常API |
多维度告警
有了全面的监控体系,自然少不了配套的告警机制,主要的告警能力包括:
序号 | 告警类型 | 触发时机 |
---|---|---|
1 | 限流告警 | API请求达到限流规则阈值触发限流告警 |
2 | 请求失败告警 | 验权失败,请求超时,后端服务异常等触发请求失败告警 |
3 | 组件异常告警 | 自定义组件处理耗时长、失败率高告警 |
4 | API异常告警 | API发布失败、API检查异常时触发API异常告警 |
5 | 健康检查失败告警 | API心跳检查失败、网关节点不通时触发健康检查失败告警 |
整体来看,这部分就是通过多个针对不同指标的监控模块组成的立体化监控架构来实现对于多个失败与异常情况的监控与告警
故障自愈
Shepherd服务端接入了弹性伸缩模块,可根据CPU等指标进行快速扩容、缩容。除此之外,还支持快速摘除问题节点,以及更细粒度的问题组件摘除。
这里的故障自愈,主要指的是对出现问题的节点和组件进行摘除,并根据使用情况对自身进行扩容缩容,我想核心思想是保全整体,尽可能不让局部问题影响到整体,比起解决问题,这里更像是回避问题,无论是更换备用的服务组件还是暂时不提供相关服务,都尽可能避免整体系统的停摆
易用性设计
自动生成DSL
业务RD只需在网关录入API文档信息,然后录入服务的Appkey、服务名、方法名信息,Shepherd管理端会从最新发布的服务框架控制台获取到服务参数的JSON Schema信息,JSON Schema定义了服务参数的类型和结构信息,管理端可根据这些信息,自动生成服务参数的JSON Mock数据。结合API文档的信息,自动替换参数名相同的Value值。 这套DSL自动生成方案,使用过程中对业务透明、标准化,业务方只需升级最新版本服务框架即可使用,极大提升研发效率,目前受到业务研发人员的广泛好评。
简单来讲,DSL(领域特定语言)就是在某一特定领域,规定好特定词义和结构的可自定义语句,就像定义好的一个类对象一样,给定了这个类里面有哪些变量,然后根据这个标准来给每个变量赋值,这样这个类对象就可以当作一份数据拿来使用,DSL也就是这样确定了一个API的相关参数,通过自动生成DSL相当于定义了这个类的变量有哪些,然后填入指定的API的相关参数就是给这些参数赋值,最后拿着这个DSL去实际创建API
API操作提效
- 快速创建API
- 批量操作
- API导入导出
可拓展性设计
Shepherd提供了丰富的系统组件完成鉴权、限流、监控能力,能够满足大部分的业务需求。但仍有一些特殊的业务需求,如自定义验签、自定义结果处理等。Shepherd通过提供加载自定义组件能力,支持业务完成一些自定义逻辑的扩展。
自定义组件
下图是自定义组件实现的一个实例。getName中填写自定义组件申请时的名称,invoke方法中实现自定义组件的业务逻辑,如继续执行、进行页面跳转、直接返回结果、抛出异常等。
这样给出一个大致的回调函数invoke的框架,在函数中自定义处理流程和处理方式,就可以自定义一个组件投入使用
服务编排
一般情况下,网关上配置的一个API对应后端一个RPC或者HTTP服务。如果调用端有聚合和编排后端服务的需求,那么有多少后端服务,就必须发起多少次HTTP的请求调用。由此就会带来一些问题,调用端的HTTP请求次数过多,效率低,在调用端聚合服务的逻辑过重。
服务编排的需求应运而生,服务编排是对既有服务进行编排调用,同时对获取的数据进行处理。主要应用在数据聚合场景:一次HTTP请求返回的数据需要调用多个或多次服务(RPC或HTTP)才能获取到完整的结果。
经过前期调研,公司已经有一套成熟的服务编排框架,由客服团队开发的海盗组件(参见《海盗中间件:美团服务体验平台对接业务数据的最佳实践》一文(https://tech.meituan.com/2018/07/26/sep-service-arrange.html) ),也是美团内部的公共服务。
因此我们与海盗团队合作,设计了Shepherd的服务编排支持方案。海盗通过独立部署的方式提供服务编排能力,Shepherd与海盗之间通过RPC进行调用。这样可以解耦Shepherd与海盗,避免因服务编排能力影响集群上的其他服务,同时多一次RPC调用并不会有明显耗时增加。使用上对业务研发人员也是透明的,非常方便,业务研发人员在管理端配置好服务编排的API,通过配置中心同时下发到Shepherd服务端和海盗服务上,即可开始使用服务编排能力。整体的交互架构图如下:
服务编排,实际上就是优化了HTTP调用的这样一个流程,将原先的多次HTTP请求调用转换为一次HTTP请求调用与多次内部调用,这样即可以减少客户端与服务端之间的通信消耗,也可以更多地将服务处理安排在服务端,方便编排整理处理
Shepherd的未来规划
云原生架构演进
Shepherd API网关的云原生架构演进有三个目标:简化接入网关步骤,提升业务研发人员的研发效率;减小服务端War包大小,提升安全性和稳定性;接入Serverless弹性,降低成本,提高资源利用率。
为了实现这个三个目标,我们计划整体迁移网关服务到公司的Serverless服务Nest(参见《美团Serverless平台Nest的探索与实践》一文) (https://tech.meituan.com/2021/04/21/nest-serverless.html) 上,同时通过抽取Shepherd核心功能到SDK的方式集成到业务的网关集群,业务研发人员可以只选择自己需要使用的自定义组件,从而大幅减小服务端的War包大小。
静态网站托管
依托Shepherd API网关实现静态网站托管的目标是:建设通用的静态网站托管解决方案,为开发者提供便捷、稳定、高扩展性的静态网站托管服务。
静态网站托管解决方案能为业务研发人员提供的主要功能包括:托管静态网站资源,包括存储及访问;管理应用生命周期,包括自定义域配置以及身份验证和授权;CI/CD集成等。
组件市场
Shepherd API网关组件市场的目标是:合作共赢,形成开发生态,业务研发人员可将开发的自定义组件提供给其他有需要的业务研发团队使用。
我们希望让业务研发人员参与到自定义组件的开发,完善使用文档后设置为公共组件,开放给所有使用Shepherd的业务研发人员使用,避免重复造轮子。
就Shepherd的未来规划整体来看,就是进一步将这一网关加入更加现代化的架构中,比如云原生,也比如云开发,同时关注这一网关的生态结构,总得来说,就是在不影响这个中间件的原本的架构和内容的基础上,拓展使用面,更加注重投入生产的这一方面的使用