本文由去哪儿网技术团队田文琦分享,本文有修订和改动。
本文针对去哪儿网酒店业务网关的吞吐率下降、响应时间上升等问题,进行全流程异步化、服务编排方案等措施,进行了高性能网关的技术优化实践。
田文琦:2021年9月加入去哪儿网机票目的地事业群,担任软件研发工程师,现负责国内酒店主站技术团队。主要关注高并发、高性能、高可用相关技术和系统架构。主导的酒店业务网关优化项目,荣获22年去哪儿网技术中心TC项目三等奖。
本文是专题系列文章的第9篇,总目录如下:
近来,Qunar 酒店的整体技术架构在基于 DDD 指导思想下,一直在进行调整。其中最主要的一个调整就是包含核心领域的团队交出各自的“应用层”,统一交给下游网关团队,组成统一的应用层。
这种由多个网关合并成大前台(酒店业务网关)的融合,带来的好处是核心系统边界清晰了,但是对酒店业务网关来说,也带来了不小的困扰。
系统面临的压力主要来自两方面:
这就导致了一系列的问题:
酒店业务网关作为直接面对用户的系统,出现任何问题都会被放大百倍,上述这些问题亟待解决。
现有系统虽然业务处理部分是异步化的,但是并不是全链路异步化(如下图所示)。
同步 servlet 容器,servlet 线程与业务逻辑线程是同一个,高峰期流量上涨或者尤其是遇到流量尖峰的时候,servlet 容器线程被阻塞的时候,我们服务的吞吐量就会明显下降。
业务处理虽然使用了线程池确实能实现异步调用的效果,也能压缩同步等待的时间,但是也有一些缺陷。
比如:
前期为了快速落地酒店 DDD 架构,合并大前台的重构中,并没有做到一步到位的设计。
为了保证项目质量,将整个过程切分为了迁移+重构两个步骤。迁移之后,整个酒店业务网关的内部代码结构是割裂、混乱的。
总结如下:
我们最核心的一个接口会调用70多个上游接口,上述问题:边界不清、不内聚、各种重复调用、依赖阻塞等问题导致了核心接口的响应时间有明显上涨。
全流程异步化方案,我们主要采用的是 Spring WebFlux。
1)响应式编程模型:Spring WebFlux 基于响应式编程模型,使用异步非阻塞式 I/O,可以更高效地处理并发请求,提高应用程序的吞吐量和响应速度。同时,响应式编程模型能够更好地处理高负载情况下的请求,降低系统的资源消耗。
2)高性能:Spring WebFlux 使用 Reactor 库实现响应式编程模型,可以处理大量的并发请求,具有出色的性能表现。与传统的 Spring MVC 框架相比,Spring WebFlux 可以更好地利用多核 CPU 和内存资源,以实现更高的性能和吞吐量。
3)可扩展性:Spring WebFlux 不仅可以使用 Tomcat、Jetty 等常规 Web 服务器,还可以使用 Netty 或 Undertow 等基于 NIO 的 Web 服务器实现,与其它非阻塞式 I/O 的框架结合使用,可以更容易地构建可扩展的应用程序。
4)支持函数式编程:Spring WebFlux 支持函数式编程,使用函数式编程可以更好地处理复杂的业务逻辑,并提高代码的可读性和可维护性。
5)50与 Spring 生态系统无缝集成:Spring WebFlux 可以与 Spring Boot、Spring Security、Spring Data 等 Spring 生态系统的组件无缝集成,提供了完整的 Web 应用程序开发体验。
上图中从下到上每个组件的作用:
在整个过程中 Spring WebFlux 实现了响应式编程模型,构建了高吞吐量、高并发的 Web 应用程序,同时也具有响应快速、可扩展性好、资源利用率高等优点。
下面我们来看下 webFlux 是如何将 Servlet 请求异步化的:
1)ServletHttpHandlerAdapter 展示了使用 Servlet 异步支持和 Servlet 3.1非阻塞I/O,将 HttpHandler 适配为 HttpServlet。
2)第10行:request.startAsync()开启异步模式,然后将原始 request 和 response 封装成 ServletServerHttpRequest 和 ServletServerHttpResponse。
3)第36行:httpHandler.handle(httpRequest, httpResponse) 返回一个 Mono 对象(即Publisher),对 Request 和 Response 的所有具体处理都在 Mono 对象中定义。
所有的操作只有在 subscribe 订阅的那一刻才开始进行,HandlerResultSubscriber 是 Reactive Streams 规范中标准的 subscriber,在它的 onComplete 事件触发时,会结束 servlet 的异步模式。
对 Servlet 返回结果的异步写入,以 DispatcherHandler 为例说明:
1)第2行:exchange 是对 ServletServerHttpRequest 和 ServletServerHttpResponse 的封装。
2)第10-15行:在系统预加载的 handlerMappings 中根据 exchange 找到对应的 handler,然后利用 handler 处理 exchange 执行相关业务逻辑,最终结果由 result 将 ServletServerHttpResponse 写入到输出流中。
最后:除了 Servlet 的异步化,作为业务网关,要实现全链路异步化还需要在远程调用方面要支持异步化。在 RPC 调用方式下,我们采用的异步 Dubbo,在 HTTP 调用方式下,我们采用的是 WebClient。
WebClient 默认使用的是 Netty 的 IO 线程进行发送请求,调用线程通过订阅一些事件例如:doOnRequest、doOnResponse 等进行回调处理。异步化的客户端,避免了业务线程池的阻塞,提高了系统的吞吐量。
在使用 WebClient 这种异步 http 客户端的时候,我们也遇到了一些问题:
1)首先:为了避免默认的 NettyIO 线程池可能会执行比较耗时的 IO 操作导致 Channel 阻塞,建议替换成其他线程池,替换方法是 Mono.publishOn(reactor.core.scheduler.Schedulers.newParallel("biz_scheduler", 300))。
2)其次:因为线程发生了切换,无法兼容 Qtracer (Qunar内部的分布式全链路跟踪系统),所以在初始化 WebClient 客户端的时候,需要在 filter 里插入对 Request 的修改,记录前一个线程保存的 Qtracer 的上下文。WebClient.Builder wcb = WebClient.builder().filter(new QTraceRequestFilter())。
Spring WebFlux 并不是银弹,它并不能保证一定能降低接口响应时间,除了全流程异步化,我们还利用 Spring WebFlux 提供的响应式编程模型,对业务流程进行服务编排,降低依赖之间的阻塞。
在介绍服务编排之前,我们先来了解一下 Spring WebFlux 提供的响应式编程模型 Reactor。
它有最重要的两个响应式类 Flux 和 Mono:
不管是 Flux 还是 Mono,它的处理过程分三步:
每个执行过程连通输入流和输出流,子过程之间可以是并行的,也可以是串行的这个取决于实际的业务逻辑。我们的服务编排就是完成输入和输出流的编排,即在第一步声明执行过程(包括子过程),第二步和第三步完全交给 Reactor。
下面是我们服务编排的总体设计:
如上图所示:
1)service:是最小的业务编排单元,对 invoker 和 handler 进行了封装,并将结果写回到上下文中。主流程中,一般是由多个 service 进行并行/串行地编排。
2)Invoker:是对第三方的异步非阻塞调用,对返回结果作 format,不包含业务逻辑。相当于子过程,一个 service 内部根据实际业务场景可以编排0个或多个 Invoker。
3)handler:纯内存计算,封装共用和内聚的业务逻辑。在实际的业务开发过程中,对上下文中的任一变量,只有一个 handler 有写权限,避免了修改扩散问题。也相当于子过程,根据实际需要编排进 service 中。
4)上下文:为每个接口都设计了独立的请求/处理/响应上下文,方便监控定位每个模块的处理正确性。
上下文设计举例:
在复杂的 service 中我们会根据实际业务需求组装 invoker 和 handler,例如:日历房售卖信息展示 service 组装了酒店报价、辅营权益等第三方调用 invoker,优惠明细计算、过滤报价规则等共用的逻辑处理 handler。
在实际优化过程中我们抽象了100多个 service,180多个 invoker,120多个 handler。他们都是小而独立的类,一般都不会超过200行,减轻了开发同学尤其是新同学对代码的认知负担。边界清晰,逻辑内聚,代码的不可知问题也得到了解决。
每个 service 都是由一个或多个 Invoker、handler 组装编排的业务单元,内部处理都是全异步并行处理的。
如下图所示:ListPreAsyncReqService 中编排了多个 invoker,在基类 MonoGroupInvokeService 中,会通过 Mono.zip(list, s -> this.getClass() + " succ")将多个流合并成为一个流输出。
在 controller 层就负责处理一件事,即对 service 进行编排(如下图所示)。
我们利用 flatMap 方法可以方便地将多个 service 按照业务逻辑要求,进行多次地并行/串行编排。
1)并行编排示例:第12、14行是两个并行处理的输入流 afterAdapterValidMono、preRankSecMono ,二者并行执行各自 service 的处理。
2)并行处理后的流合并:第16行,搜索结果流 rankMono 和不依赖搜索的其他结果流preRankAsyncMono,使用 Mono.zip 操作将两者合并为一个输出流 afterRankMergeMono。
3)串行编排举例:第16、20、22行,afterRankMergeMono 结果流作为输入流执行 service14 后转换成 resultAdaptMono,又串行执行 service15 后,输出流 cacheResolveMono。
以上是酒店业务网关的整体服务编排设计。
下面来介绍一下,我们是如何进行流程编排,发挥网关优势,在系统内和系统间达到响应时间全局最优的。
8.2.1)系统内:
上图示例中的左侧方案总耗时是300ms。
这300ms 来自最长路径 Service1的200ms 加上 Service3 的100ms:
而右图是将 Service1 的200ms 的 invoker 迁移至与 Service1 并行的 Service0 里。
此时,整个处理的最长路径就变成了200ms:
通过系统内 invoker 的最优编排,整体接口的响应时间就会从300ms 降低到200ms。
8.2.2)系统间:
举例来说:优化前业务网关会并行调用 UGC 点评(接口耗时100ms)和 HCS 住客秀(接口耗时50ms)两个接口,在 UGC 点评系统内部还会串行重复调用 HCS 住客秀接口(接口耗时50ms)。
发挥业务网关优势,UGC 无需再串行调用 HCS 接口,所需业务聚合处理(这里的业务聚合处理是纯内存操作,耗时可以忽略)移至业务网关中操作,这样 UGC 接口的耗时就会降下来。对全局来说,整体接口的耗时就会从原来的100ms 降为50ms。
还有一种情况:假设业务网关是串行调用 UGC 点评接口和 HCS 住客秀接口的话,那么也可以在业务网关调用 HCS 住客秀接口后,将结果通过入参在调用 UGC 点评接口的时候传递过去,也可以省去 UGC 点评调用 HCS 住客秀接口的耗时。
基于对整个酒店主流程业务调用链路充分且清晰的了解基础之上,我们才能找到系统间的最优解决方案。
优化后最直接的效果就是在用户体感上,页面的打开速度明显加快了。
以详情页为例:
列表、详情、订单等主流程各个核心接口的P50响应时间都有明显的降幅,平均下降了50%。
以详情页的 A、B 两个接口为例,A接口在优化前的 P50 为366ms:
A 接口优化后的 P50 为36ms:
B 接口的 P50 响应时间,从660ms 降到了410ms:
单机可支持 QPS 上限从100提升至200,吞吐量性能上限提升100%,平稳应对七节两月等常规流量高峰。
在考试、演出、临时政策变化、竞对故障等异常突发事件情况下,会产生瞬时的流量尖峰。在某次实战的情况下,瞬时流量高峰达到过二十万 QPS 以上,酒店业务网关系统经受住了考验,能够轻松应对。
单机性能的提升,我们的机器资源成本也下降了一半。
具体就是:
1)通过采用 Spring WebFlux 架构和系统内/系统间的服务编排,本次酒店业务网关的优化取得了不错的效果,单机吞吐量提升了100%,整体接口的响应时间下降了50%,为同类型业务网关提供一套行之有效的优化方案。
2)在此基础上,为了保持优化后的效果,我们除了建立监控日常做好预警外,还开发了接口响应时长变化的归因工具,自动分析变化的原因,可以高效排查问题作好持续优化。
3)当前我们在服务编排的时候,只能根据上游接口在稳定期的响应时间,来做到最优编排。当某些上游接口响应时间存在波动较大的情况时,目前的编排功能还无法做到动态自动最优,这部分是我们未来需要优化的方向。(本文已同步发布于:http://www.52im.net/thread-4618-1-1.html)
[1] 从C10K到C10M高性能网络应用的理论探索
[2] 一文读懂高性能网络编程中的I/O模型
[3] 一文读懂高性能网络编程中的线程模型
[4] 以网游服务端的网络接入层设计为例,理解实时通信的技术挑战
[5] 手淘亿级移动端接入层网关的技术演进之路
[6] 喜马拉雅自研亿级API网关技术实践
[7] B站基于微服务的API网关从0到1的演进之路
[8] 深入操作系统,彻底理解I/O多路复用
[9] 深入操作系统,彻底理解同步与异步
[10] 通俗易懂,高性能服务器到底是如何实现的
[11] 百度统一socket长连接组件从0到1的技术实践
[12] 淘宝移动端统一网络库的架构演进和弱网优化技术实践
[13] 百度基于金融场景构建高实时、高可用的分布式数据传输系统的技术实践
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。