内容简介:高并发下,Tomcat、HttpClient让系统瘫痪最近做了一个项目,需要通过http多次请求和外部系统数据交换,例如支付,地图等。但是交互过程通过http调用第三方接口响应时间慢会导致并发量下降,甚至堵死系统。下面将从Tomcat底层原理上分析为什么http交互会导致Tomcat性能下降。
高并发下,Tomcat、HttpClient让系统瘫痪
最近做了一个项目,需要通过http多次请求和外部系统数据交换,例如支付,地图等。但是交互过程通过http调用第三方接口响应时间慢会导致并发量下降,甚至堵死系统。
下面将从Tomcat底层原理上分析为什么http交互会导致Tomcat性能下降。
Tomcat和BIO
老版本的Tomcat底层使用BIO方式实现,就是 java 常用的Socket网络编程。
什么是BIO
BIO的实现在java.io包中。它是基于流模型实现的,交互的方式是同步、阻塞方式。也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里。
特点:
1.同步阻塞IO
2.一个请求对应一个线程
3.没有数据达到,也会阻塞
优点: 代码比较简单、直观
缺点: 同步执行导致阻塞,一个Socket使用一个线程,浪费资源,容易成为应用性能瓶颈。
正是因为BIO的特性,因此每一个客户端连接需要分配一个线程。虽然使用线程池可以让提升处理性能,但是线程分配也是有上限的不可能无限分配线程。这就导致如果系统内发起http请求返回数据等待时间较长时,并发数基本上就是分配的线程数上限。
当线程池分配的线程都在使用时,新accept的socket在调用executorService.execute时就会进入线程池的队列中等待。等到有可用线程时任务才开始执行,但是http请求的响应时间又很长,这就导致后续的socket等待的时间也开始变长,出现恶性循环。
ExecutorService executorService = Executors.newFixedThreadPool(200);
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket accept = serverSocket.accept();
executorService.execute(new Runnable() {
@Override
public void run() {
try {
// todo 调用servlet
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
那有没有方法可以提高Tomcat的性能呢?其实新版本的Tomcat的底层有一套NIO的实现,通过配置Connector的protocol为Http11NioProtocol就可以实现NIO的方式。
Tomcat和NIO
什么是NIO
NIO的实现在java.nio包中,通过单个Selector监听多个Channel中的数据到达事件,俗称多路复用。这样的好处是一个线程就可以监听许多Channel的数据,相对于BIO有着显著的性能提成的。
特点:
1.同步非阻塞IO
2.利用IO多路复用技术+NIO,多个channel一个线程监听
优点: 事件监听线程只有一个主线程。数据发过来时启动另一个线程读取,主线程又可以继续监听其他Channel的事件。
同时读取线程使用线程池可以公用资源,用完还给线程池再给别的线程用。
缺点: 事件监听是异步的,在业务中数据都是通过接口回调的方式进行的。所以编程的思想和思路都要发生转变。
同时也增加了技术实现的难度。
// 创建一个selector
Selector selector = Selector.open();
// 初始化TCP连接监听通道
ServerSocketChannel serverCh = ServerSocketChannel.open();
serverCh.bind(new InetSocketAddress(8080));
serverCh.configureBlocking(false);
serverCh.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int events = selector.select();
if (events > 0) {
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while (selectionKeys.hasNext()) {
SelectionKey key = selectionKeys.next();
if (key.isAcceptable()) {
SocketChannel sc = ((ServerSocketChannel) key.channel()).accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// todo 调用servlet
}
selectionKeys.remove();
}
}
}
问题: 这么说是不是使用Tomcat的Http11NioProtocol就万事大吉了呢?
我们以Spring Boot为例,Spring Boot底层集成了Embed Tomcat并且使用了Http11NioProtocol。
下面使用Spring Boot实现一个请求第三方接口返回数据的demo。
配置一个test接口延时1000毫秒后返回数据,来模拟第三方接口返回数据慢的情况。
@SpringBootApplication
@RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient();
}
@Autowired
private OkHttpClient okHttpClient;
@RequestMapping("/bio")
public byte[] bio(HttpServletRequest req) throws IOException {
Request request = new Request.Builder().url("http://127.0.0.1:8080/test").build();
Response response = okHttpClient.newCall(request).execute();
byte[] bytes = response.body().bytes();
return bytes;
}
@RequestMapping("/test")
public String test() throws Exception {
Thread.sleep(1000);
return "ok";
}
}
配置Tomcat线程数为500,最大排队数为0
server.port=8080
server.Tomcat.max-threads=200
server.Tomcat.accept-count=0
使用jmeter进行测试,配置jmeter并发线程数为800,每个线程循环100次,进行测试。
测试结束发现 当jmeter并发线程逐步升高到500以上时,性能开始下降直至整个系统崩溃 。似乎使用Tomcat NIO模型性能并没有提升。和BIO模型性能差不多,这是为什么呢。
所以我们再仔细回想一下,Tomcat使用了NIO监听数据事件,调用线程池异步执行。当代码执行到test方法时可以断点查看确实已经在线程池里了。
所以问题不是在Tomcat NIO上。那就是OKHttpClient的问题。
OkHttpClient号称是java界性能最好的HttpClient为什么性能不行呢。以下我们分析一下OkHttpClient的实现原理。
OkHttpClient底层使用BIO
如果你阅读过OkHttpClient的源代码你就会发现他的底层是BIO实现的。虽然使用NIO架构的Tomcat的工作线程有500个,但是当jmeter并发数到达500时,所有的线程都在阻塞等待OkHttpClient的数据返回。
如果这个时候再有新的请求上来,Tomcat就会因为线程数就不够而拒绝服务。
ReactorNetty
所以要想性能获得提升,就需要使用基于NIO的httpServer和httpClient。
下面我们httpServer继续使用NIO模型的Tomcat,OkHttpClient替换成reactor-netty的HttpClient。
首先,导入maven包
<dependencies>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
<version>0.9.2.RELEASE</version>
</dependency>
</dependencies>
代码实现如下
@SpringBootApplication
@RestController
public class DemoApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@RequestMapping("/nio")
public DeferredResult<byte[]> nio(HttpServletRequest req) throws IOException {
DeferredResult<byte[]> result = new DeferredResult<>(0L);
HttpClient httpClient = HttpClient.create();
httpClient.request(HttpMethod.GET)
.uri("http://127.0.0.1:8080/test")
.responseSingle(new BiFunction<HttpClientResponse, ByteBufMono, Mono<byte[]>>() {
@Override
public Mono<byte[]> apply(HttpClientResponse httpClientResponse, ByteBufMono byteBufMono) {
return byteBufMono.asByteArray();
}
})
.doOnNext(e -> result.setResult(e))
.doOnError(e -> result.setErrorResult(e))
.subscribe();
return result;
}
@RequestMapping("/test")
public String test() throws Exception {
Thread.sleep(1000);
return "ok";
}
}
为什么要使用DeferredResult呢。因为HttpClient数据发送和返回都是异步的。如果不使用DeferredResult,spring mvc默认你是同步调用test方法执行完成就默认返回200给客户端,并且释放HttpServletRequest和HttpServletResponse。
加了DeferredResult以后,spring mvc就知道你需要异步返回数据就会为保持和客户端的连接。
此时再次使用jmeter进行并发测试,配置参数不变。
对比图
BIO
NIO
总结
最后我们通过流程图再梳理一下整个的调用流程。
1.Tomcat监听到请求后启动线程处理业务,由于返回的是DeferredResult,所以与客户端连接保持。但是线程已经释放。
2.httpClient监听到,第三方接口返回数据时,启动线程处理数据返回客户端。结束这次http请求,释放线程。
通过流程图发生,实际上从httpClient收到数据时,后续的业务逻辑是在httpClient启动的线程上执行的,而不是在Tomcat的线程上执行的。这是和BIO模式最大的区别。
都2020年你在使用Tomcat和okhttp做业务?
微信关注我,下期带你了解最前沿的Spring WebFlux。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 高并发下,Tomcat、HttpClient 让系统瘫痪
- 黑客锁定市政系统勒索比特币,政府拒付赎金!全美最危险城市陷入瘫痪的第三周……
- 微博服务器瘫痪!运维:该拿什么拯救我?
- 跑得好好的 Java 进程,怎么突然就瘫痪了?
- “技术故障”背后有黑手 50元能让网站瘫痪一小时
- GitHub网络链路中断43秒,导致瘫痪了24个小时
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
PHP Hacks
Jack Herrington D. / O'Reilly Media / 2005-12-19 / USD 29.95
Programmers love its flexibility and speed; designers love its accessibility and convenience. When it comes to creating web sites, the PHP scripting language is truly a red-hot property. In fact, PH......一起来看看 《PHP Hacks》 这本书的介绍吧!