Bug复盘:接口异步返回的重要性

前言

最近接收了一个老项目,忽然甲方 QA 报了一个 bug,连续请求 60 次,成功 8 次,后面的 52 次所有失败,并且成功的 case 返回时间广泛较长。看了日志,并不是业务上的异常。这让刚毕业没什么经验的我,顿时陷入了沉思。但回过神来考虑了一下,大胆才猜想,多是网络问题或者是并发请求上的问题。java

但其实业务异常相对容易排查,而网络或者并发的问题会相对难一些,恰好本身对于服务器服务器请求处理的流程也不太清楚,因此就花了时间看了下,最后基本判定是接口是实现方式出现了问题,从新改写成异步接口后问题基本解决。redis

因此今天就打算复盘一下,聊聊 tomcat 处理请求的流程,内心好有个数,以及对于某些场合下接口异步返回的重要性。算法

Tomcat 请求流程

项目是 Spring Boot 开发的,默认用 jar 包部署,实际上就是运行在一个内嵌的 tomcat 中,因此下面就简单理一下 tomcat 处理并发请求的基本流程。这里不具体涉及到相关的组件以及源码,仅仅是梳理过程。编程

基本的 HTTP 请求处理的过程以下图,其中 Connector 和 Engine 是 tomcat 内置的组件。tomcat

  1. Connector 会监听响应的端口,例如 80 或 443,新的 HTTP 请求过来后会作响应的处理。
  2. Connector 接受请求后,将其封装成 Request 对象,并建立线程来处理。tomcat 默承认以处理的并发数为 200 个(经过 maxThreads 参数设置),实际的处理速度取决于咱们本身实现的服务程序;超出 maxThreads 部分,tomcat 仍然不断接收,但最多不能超过 maxConnector 设置的数,默认 1w 个;超过 maxConnector 的部分,tomcat 仍然不断接收,但不作处理,放入 Connector 建立的一个队列中,但最多不能超过 acceptCount,超过则拒绝(也就是咱们所说的,服务器卡死、挂了)。
  3. 配置时 Connector 会与 Engine 进行绑定,新建立的线程会在 pipeline 中有序等待 Engine 进行处理,其中就包含了 servlet 和 Spring MVC 的处理流程。
  4. Engine 处理完成后会将结果返回给对应的 Connector,再作进一步封装后返回给 HTTP 请求的一方。

清楚了上述流程以后,基本上对于服务器如何处理并发请求有了一个基本的概念,当并发量大的时候,能够对上述参数进行改动,以适应本身的项目。服务器

接口异步返回的重要性

在回到以前讲的项目上来,能够看到 tomcat 默认配置就已经具备不小的并发量了,而且在 Spring Boot 中 Controller 是单例的,且每一个请求的处理互不相关,可是为何接口返回的速度仍然不似预期呢?这其实和这个项目的业务时有关的。网络

这个接口是对算法的集成,发起请求后须要经过 HTTP 调用算法处理返回结果,请求调用的速度远大于接口处理的速度,再者算法依赖于独占的 GPU,也就意味着一个请求在处理时,其余请求必须等待。而以前实现的接口是同步的,且设置的算法接口返回的 timeout 为 15s,所以当请求积累到必定数量时,后续等待时间超过 15s,直接返回了异常的结果,致使后续请求所有失败。并发

显然,在处理速度低于请求速度的接口,而且依赖资源是独占或者很紧张的场景下,经过同步的形式返回接口是不可取的。因为接口占用的资源有限,能够理解成将此接口加上了一个 synchronized,后续请求过来都会无限制等待,或者设置了 timeout,无限制拒绝服务,这两种状况都不是咱们想要的。app

异步就是一种更优雅的形式,请求发送后,接口的调用者能够继续干别的事,请求处理完后会自动通知给调用者。而且在 Java 中的实现也是比较简单的,直接建立一个线程池来接收请求就能够了,线程池自带阻塞队列已经很好地帮咱们处理排队这个场景,分布式场景则须要考虑用 redis 或者成熟的 mq 框架来进行调度了。调用者额外须要实现一个 callback 接口来接收处理完后的结果。这样再多的请求都可以有序的获取处处理的结果,无非是耗时的长短问题罢了。框架

@RequestMapping(value = "handleTask", method = RequestMethod.POST)
public RestResult handleTask(HttpServletRequest request,String callbackUrl) {
    mServerPool.submit((Runnable) SpringUtil.getBean("imageTask", callbackUrl));
    return new RestResult();
}

其实,用过支付宝支付 API 的开发者应该很熟悉这个套路,由于阿里也是这么在作的,发起支付后,用户有一段时间能够确认支付,所以这个过程并不是实时返回的,全部会有一个 callback 接口,用于实现用户支付完成的后的业务逻辑,当用户完成支付后,支付宝服务器会回调到这个接口,完成最终的一个业务。

总结

之前一直以为异步、并发很抽象,学习的时候也老是那么几个 demo(交叉输出、生产者消费者 etc.),可是真正遇到这么一个场景的时候,发现一切都是水到渠成的。只有在不断的实践中,才能调整对某一编程思想的认识,有新的体会。

相关文章
相关标签/搜索