Bug复盘:接口异步返回的重要性
前言
最近接收了一个老项目,突然甲方 QA 报了一个 bug,连续请求 60 次,成功 8 次,后面的 52 次全部失败,而且成功的 case 返回时间普遍较长。看了日志,并非业务上的异常。这让刚毕业没什么经验的我,顿时陷入了沉思。但回过神来考虑了一下,大胆才猜测,可能是网络问题或者是并发请求上的问题。
但其实业务异常相对容易排查,而网络或者并发的问题会相对难一些,刚好自己对于服务器服务器请求处理的流程也不太清楚,所以就花了时间看了下,最后基本断定是接口是实现方式出现了问题,重新改写成异步接口后问题基本解决。
所以今天就打算复盘一下,聊聊 tomcat 处理请求的流程,心里好有个数,以及对于某些场合下接口异步返回的重要性。
Tomcat 请求流程
项目是 Spring Boot 开发的,默认用 jar 包部署,实际上就是运行在一个内嵌的 tomcat 中,所以下面就简单理一下 tomcat 处理并发请求的基本流程。这里不具体涉及到相关的组件以及源码,仅仅是梳理过程。
基本的 HTTP 请求处理的过程如下图,其中 Connector 和 Engine 是 tomcat 内置的组件。

- Connector 会监听响应的端口,例如 80 或 443,新的 HTTP 请求过来后会做响应的处理。
- Connector 接受请求后,将其封装成 Request 对象,并创建线程来处理。tomcat 默认可以处理的并发数为 200 个(通过
maxThreads参数设置),实际的处理速度取决于我们自己实现的服务程序;超出maxThreads部分,tomcat 仍然不断接收,但最多不能超过maxConnector设置的数,默认 1w 个;超过maxConnector的部分,tomcat 仍然不断接收,但不做处理,放入 Connector 创建的一个队列中,但最多不能超过acceptCount,超过则拒绝(也就是我们所说的,服务器卡死、挂了)。 - 配置时 Connector 会与 Engine 进行绑定,新创建的线程会在 pipeline 中有序等待 Engine 进行处理,其中就包含了 servlet 和 Spring MVC 的处理流程。
- Engine 处理完成后会将结果返回给对应的 Connector,再做进一步封装后返回给 HTTP 请求的一方。
清楚了上述流程之后,基本上对于服务器如何处理并发请求有了一个基本的概念,当并发量大的时候,可以对上述参数进行改动,以适应自己的项目。
接口异步返回的重要性
在回到之前讲的项目上来,可以看到 tomcat 默认配置就已经具有不小的并发量了,并且在 Spring Boot 中 Controller 是单例的,且每个请求的处理互不相关,但是为什么接口返回的速度仍然不似预期呢?这其实和这个项目的业务时有关的。
这个接口是对算法的集成,发起请求后需要通过 HTTP 调用算法处理返回结果,请求调用的速度远大于接口处理的速度,再者算法依赖于独占的 GPU,也就意味着一个请求在处理时,其他请求必须等待。而之前实现的接口是同步的,且设置的算法接口返回的 timeout 为 15s,因此当请求积累到一定数量时,后续等待时间超过 15s,直接返回了异常的结果,导致后续请求全部失败。
显然,在处理速度低于请求速度的接口,并且依赖资源是独占或者很紧张的场景下,通过同步的形式返回接口是不可取的。由于接口占用的资源有限,可以理解成将此接口加上了一个 synchronized,后续请求过来都会无限制等待,或者设置了 timeout,无限制拒绝服务,这两种情况都不是我们想要的。
异步就是一种更优雅的形式,请求发送后,接口的调用者可以继续干别的事,请求处理完后会自动通知给调用者。并且在 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.),但是真正遇到这么一个场景的时候,发现一切都是水到渠成的。只有在不断的实践中,才能调整对某一编程思想的认识,有新的体会。
Bug复盘:接口异步返回的重要性的更多相关文章
- .net 反射访问私有变量和私有方法 如何创建C# Closure ? C# 批量生成随机密码,必须包含数字和字母,并用加密算法加密 C#中的foreach和yield 数组为什么可以使用linq查询 C#中的 具名参数 和 可选参数 显示实现接口 异步CTP(Async CTP)为什么那样工作? C#多线程基础,适合新手了解 C#加快Bitmap的访问速度 C#实现对图片文件的压
以下为本次实践代码: using System; using System.Collections.Generic; using System.ComponentModel; using System ...
- 翻页bug 在接口文档中应规范参数的取值区间 接口规范
<?php$a=array("red","green","blue","yellow","brown&q ...
- ionic中将service中异步返回的数据赋值给controller的$scope
1.service中异步获取数据实例 angular.module('starter.services', []) .factory('Chats', function($http,$q) {//定义 ...
- PHP开发APP接口之返回数据
首先说明一下客户端APP通信的格式 1.xml:扩展标记语言(1.用来标记数据,定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言,xml格式统一,跨平台和语言,非常适合数据传输和通信,早已 ...
- EasyNVR无插件直播服务器软件接口调用返回“Unauthorized”最简单的处理方式
背景需求 对于EasyNVR的受众群体十分的广泛,不仅仅有将EasyNVR作为视频直播平台直接使用的,更多的是使用EasyNVR的对应功能集成到自身系统.对于前者,只需要将软件的使用功能搞清楚即可,对 ...
- 调试接口,返回的json数据,我定义了一个类,用来序列化,其中有一个字段定义为string 然后序列化的时候报错
调试接口,返回的json数据,我定义了一个类,用来序列化,其中有一个字段定义为string 然后序列化的时候报错 在需要解析的类型类上加上声明 eg:
- 链接服务器"(null)"的 OLE DB 访问接口 "SQLNCLI10" 返回了消息 "Cannot start more transactions on this session."
开发同事反馈一个SQL Server存储过程执行的时候,报"链接服务器"(null)"的 OLE DB 访问接口 "SQLNCLI10" 返回了消息 ...
- delphi xe7 多线程调用CMD,使用管道,临界区技术,实现指定用户名,多线程,异步返回CMD命令结果到memo
第一次发这个,发现格式很乱,不好看,可以用XE7的project--format project sources命令格式化一下代码. 后面我会上传此次修改函数用的源代码到云盘 链接: http://p ...
- ASP.NET WEB API微信支付通知接口,返回xml数据,微信服务器不识别问题
原文:ASP.NET WEB API微信支付通知接口,返回xml数据,微信服务器不识别问题 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/MrTra ...
随机推荐
- FPGA基础(verilog语言)——语法篇
verilog语言简介 verilog语言是一种语法类似于c的语言,但是与c语言也有不同之处,比如: 1.verilog语言是并行的,每个always块都是同时执行,而c语言是顺序执行的 2.veri ...
- <meta name="viewport" content="width=device-width,initial-scale=1.0">的意思
content属性值 : width:可视区域的宽度,值可为数字或关键词device-width height同理width intial-scale:页面首次被显示是可 ...
- ViewPage+Fragment的使用用法
一.概述 从前面几篇文章,我们知道,实现ViewPager是要有适配器的,我们前面用的适配器是PagerAdapter,而对于fragment,它所使用的适配器是:FragmentPagerAdapt ...
- linux目录相关操作
mkdir:新建目录 mkdir [-mp] 目录名称 -m:配置文件权限,直接设置,不需要看默认权限(umask) -p:递归创建目录 rmdir:删除空目录 rmdir [-p] 目录名称 -p: ...
- day20191113笔记
默写:程序=数据结构+操作系统. 一.文件上传 1.导入commons-fileupload.jar commons-io.jar 2.配置 <bean id="multipartRe ...
- 使用IDEA2017.3.5搭建SSM框架
转载自博客园,附上原文地址https://www.cnblogs.com/hackyo/p/6646051.html?utm_source=itdadao&utm_medium=referra ...
- vscode从听说到使用,vetur,prettier,htmljscssPrettify踩坑指南。
今天基于vue-cli(2.9.3)构建一个新的项目.我用的sublime,es6的代码格式要与公司格式兼容.采用了vue-cli自带的eslint后,有一些不统一的部分需要修改.先看看sublime ...
- CAS都不了解,你还怎么看J.U.C
前言 说到CAS(CompareAndSwap),不得不先说一说悲观锁和乐观锁,因为CAS是乐观锁思想的一种实现. 悲观锁:总是很悲观的认为,每次拿数据都会有其他线程并发执行,所以每次都会进行加锁,用 ...
- Vue全家桶高仿小米商城
大家好,我是河畔一角,时隔半年再次给大家带来一门重量级的实战课程:<Vue全家桶高仿小米商城>,现在很多公司都在参与到商城的构建体系当中,因此掌握一套商城的标准开发体系非常重要:商城的开始 ...
- C#开发安卓自学笔记1
今天开始研究了下C#开发安卓,刚开始什么都不懂,学过安卓的同学们也是用Java开发的,虽然两者开发差别不大,但是还是有差别的 // Set our view from the "main&q ...