随着系统规模变大、复杂度越来越高,微服务架构渐渐成为主流。当一个单体应用被拆分成许许多多的微服务应用后,也带来了一些问题。一些与业务非强相关的功能,比如权限控制、日志输出、数据加密、熔断限流等,每个微服务应用都需要,因此存在着大量重复的代码实现。而且由于系统的迭代、人员的更替,各个微服务中这些功能的实现细节出现了较大的差异,导致维护成本变高。另一方面,原先单体应用下非常容易做的接口管理,在服务拆分后没有了一个集中管理的地方,无法统计已存在哪些接口、接口定义是什么、运行状态如何。
网关就是为了解决上述问题。作为微服务体系中的核心基础设施,一般需要具备接口管理、协议适配、熔断限流、安全防护等功能,各种开源的网关产品(比如 zuul)都提供了优秀高可扩展性的架构、可以很方便的实现我们需要的一些功能、比如鉴权、日志监控、熔断限流等。
但是,仅有这些功能还不够。企业内部系统一般都是异构的,存在多种协议的接口,而对外提供服务的接口依然是 http 协议的。因此,协议转换是非常现实的需求。还有数据聚合的能力,开源产品提供的都是一对一路由功能,并不直接提供聚合、编排的能力。而在实际开发中,需要对一些小接口进行聚合是很常见的,如果需要去写代码、发布就会很繁琐,而且以后小接口升级还得同步更新。
因此,我们参考 SpringCloud 网关产品的优秀设计理念,在基础网关的功能上支持多种下游协议、支持服务编排功能,并提供可视化的配置平台。
我们选择了 Java 微服务体系中三个主流的 API 网关框架 Zuul1、Zuul2 和 SpringCloud Gateway,在线程模型、协议适配、熔断限流,服务编排等方面进行了对比。
三种框架的优缺点都很明显,但是都缺少了对 dubbo 的支持,同时也没有数据聚合(服务编排)的能力,因此我们借鉴它们的优秀架构,自研业务网关。下面看一下 Zuul2 的系统架构。
这是典型的网关系统架构,由三种功能 Filter 以及异常 Filter 组成。Inbound Filters 主要负责对 request 的校验、加工、拦截,Endpoint Filter 负责路由 & 下游接口调用,Outbound Filters 负责对 response 的校验、加工、日志监控等,Exception Filter 则负责功能 Filter 中的异常处理。这套架构简单清晰、可扩展性强,可以很容易实现模块化、并且各个模块(Filter)间彼此无耦合,类似 Plugin 的独立性。
先看一下网关的体系结构。
校验拦截层提供了网关的基础能力,通过 Inbound Filters 和 Outbound Filters 来实现,可无限扩展。
服务调度层和接口通信层提供了核心能力,通过 EndPoint Filter 来实现。
异常处理统一交由 Exception Filter 来处理,之后会重新流转到 Outbound Filters 中。
公司内部 rpc 服务采用 dubbo 这套解决方案的基础上二次开发,大量的微服务采用 dubbo 协议,网关也必须要支持 dubbo 接口的转发。dubbo 的泛化调用非常适合网关的场景,它需要接口定义和接口配置(zkaddress、group、version 等)两部分信息。其中,接口定义信息手工填写非常繁琐,为了避免人为因素导致的配置错误,网关会根据 jar 包的 maven 坐标去拉取 jar 包进行扫描,获取到接口的具体方法签名。
网关管理所有的 Dubbo Reference,监听配置平台的推送消息,动态管理 Reference 的生命周期以及运行参数,比如实时调整 dubbo 接口的超时时间、实时升级 dubbo 接口的版本等。所有的 Reference 对象都在初始化后才交付给执行器,保证接口的执行效率。
微服务架构下会更依赖通过各微服务之间的协作来实现一个完整的业务流程,这种协作就是服务编排。编排涉及到 RPC、分布式事务等,需要有完善的编排框架来支撑。
编排的实现,需要解决四个关键点:
其中,复杂依赖和执行时机是难点。
复杂依赖:采用服务自治的思路,通过定义每个服务所关心的服务即可。通过分析,发现这是个有向无环图,通过成环检测可以发现不合理的依赖关系。
执行时机:采用面向协作的思路,通过消息的交互序列来控制各个服务的交互,参与交互的服务都是对等的,没有集中的控制。如上图所示,所有服务通过消息总线共享事件通知,决定自身是否执行。
为什么需要嵌入式?要解决什么问题?
一般主要业务接口都会附带着调用很多小接口,并随着主接口的数据一并返给调用方。主业务接口一般比较稳定,但是小接口的迭代相对比较频繁。此时,因为小接口的迭代导致修改主业务接口所在的工程,就显得那么的多余。我们希望能把与主业务接口无耦合的小接口透传化,达到小接口升级、主接口无感知的目标。
我们将网关的核心能力抽象出来形成一个 jar 包,主要包括配置同步、服务编排、协议转换,称作“嵌入式网关”。宿主工程引入嵌入式网关,并将调用下游接口的地方改成嵌入式网关的执行入口就行了,剩下的就是去配置平台配置这个接口的相关信息。如果是 dubbo 接口的话,还可以顺便将依赖的 api jar 包删掉,因为泛化调用不需要引入 jar 包。
嵌入式网关提供了宿主工程透传化改造的能力,可以实现非强耦合下游接口的数据纯透传化,降低宿主工程的业务复杂度,并且可享受网关系统的所有能力。
如何考量一个网关产品的优劣?常用的性能指标包括:吞吐量、耗时、错误率。有个非常关键的指标:高可用性,也就是传说中的几个 9。
高可用性涵盖了内部和外部的各种不确定因素,这里讲一下网关系统在高可用性方面做的努力。
网关上线后,最直接的收益是人效方面。因为减少了一个协作团队,因此相应节省了开发与测试的工时。
选择了几个比较独立的业务做数据分析,整体节省了大概 27%左右的工时。
注:节省工时包括“ API 层服务团队的开发、自测联调,以及对应 QA 的工时”。前端跟后端按 2:1,开发跟 qa 按 3:1 算,=前端*67%
注:”商品售卖“通过嵌入式网关实现了与主接口的解耦,迭代过程中无主工程的发布。
由于网关具备了调整数据的能力,对于一些由于数据不规范或者缺失导致的故障,提供了快速恢复的手段。
故障案例:
1、新开发一个纯透传的接口
常规:写代码调用下游接口,定义 bean,将结果添加到 response 中。当下游增加字段时,需要代码迭代。
网关:在配置平台上配置下游服务,无需定义 bean,纯透传,下游接口增加字段无需任何改动。
2、活动首页配置获取接口,新增一个维度的配置,该配置数据由一个新接口提供(数据聚合)
常规:写代码调用新的下游接口,将结果添加到 response 中。
网关:在配置平台上增加一个下游服务,将结果挂载到 response 中,后续迭代无需任何改动。
3、touch 接口的跨域解决
常规:针对下游接口做 ng 转发,或者由下游接口做跨域处理。当下游使用 jsonp 时,还可能存在 xss 漏洞隐患。
网关:在公司 touch 域名下增加了/gw 的 location 配置,开发人员无需考虑跨域问题。还支持 jsonp,并且做了 xss 漏洞防御。
4、从一个接口获取酒店列表,然后从另一个接口给每一个酒店补充评论信息
常规:调用第一个接口获取酒店列表,做简单业务判断后,对列表做循环调用第二个评论接口,并将数据补充进去。
网关:定义两个下游接口,将第二个评论接口设置为依赖”第一个酒店列表接口“,并且指定循环用的字段路径,还可以指定失败时的降级内容。
头图:Unsplash
作者:施黎明
原文:业务网关的落地实践
来源:Qunar 技术沙龙 - 微信公众号 [ID:QunarTL]
转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
领取专属 10元无门槛券
私享最新 技术干货