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 ...
随机推荐
- React-基础总结
使用1. // js文件,第一部引入React(大写,不然保错) import React from 'react' // 创建数组 const arrList = Array.from({lengt ...
- java版单例模式
单例模式可以说是最常用的设计模式之一,其主要作用就是保证一个类只有一个实例,并且提供一个访问它的全局访问点,严格的控制用户的访问方式. 单例模式又分为懒汉模式和饿汉模式,首先说一下饿汉模式: 饿汉模式 ...
- Java虚拟机详解(九)------类文件结构
我们知道计算机是由晶体管.电路板等组装而成的电子设备,而这些电子设备其实只能识别0与1的信号. 那么问题来了,我们在操作系统上编写的Java代码(由字母.数字等各种符号组成),打包后部署到服务器上,是 ...
- vuejs中的回车事件
@keyup.enter.native="事件名称"
- C# - SPC(Statistical Process Control)系统 - 6西格玛数据决策和Chart模块的开发与实现
Statistical Process Control 简介 统计过程控制(Statistical Process Control)是一种借助数理统计方法的过程控制工具.它对生产过程进行分析评价,根据 ...
- 关于Java运行机制
目录 编译型语言与解释型语言的区别 编译型语言 解释型语言 细数两者之差别 Java的奇怪之处 Java的编译 Java的解释 具体机制 Java既是编译型语言,也是解释型语言. 首先先查找关于两种语 ...
- day 33 线程锁
Python的GIL锁 - Python内置的一个全局解释器锁,锁的作用就是保证同一时刻一个进程中只有一个线程可以被cpu调度. 为什么有这把GIL锁? 答:Python语言的创始人在开发这门语言时, ...
- Spring(Bean)6
生命周期构造 --->set--->Bean init前后执行 (新创建 类) public class MyBeanPostProcesser implements BeanPostPr ...
- Docker部署Mysql集群
单节点数据库的弊病 大型互联网程序用户群体庞大,所以架构必须要特殊设计 单节点的数据库无法满足性能上的要求 单节点的数据库没有冗余设计,无法满足高可用 单节点MySQL的性能瓶领颈 2016年春节微信 ...
- Linux基本架构
Linux linux设计思想 1.程序应该小而专一,程序应该尽量的小,且只专注于一件事上,不要开发那些看起来有用但是90%的情况都用不到的特性: 2.程序不只要考虑性能, 程序的可移植性更重要,sh ...