高并发下,Tomcat、HttpClient 让系统瘫痪

栏目: IT技术 · 发布时间: 4年前

内容简介:高并发下,Tomcat、HttpClient让系统瘫痪最近做了一个项目,需要通过http多次请求和外部系统数据交换,例如支付,地图等。但是交互过程通过http调用第三方接口响应时间慢会导致并发量下降,甚至堵死系统。下面将从Tomcat底层原理上分析为什么http交互会导致Tomcat性能下降。

高并发下,Tomcat、HttpClient让系统瘫痪

最近做了一个项目,需要通过http多次请求和外部系统数据交换,例如支付,地图等。但是交互过程通过http调用第三方接口响应时间慢会导致并发量下降,甚至堵死系统。

下面将从Tomcat底层原理上分析为什么http交互会导致Tomcat性能下降。

Tomcat和BIO

老版本的Tomcat底层使用BIO方式实现,就是 java 常用的Socket网络编程。

什么是BIO

高并发下,Tomcat、HttpClient 让系统瘫痪

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

高并发下,Tomcat、HttpClient 让系统瘫痪

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次,进行测试。

高并发下,Tomcat、HttpClient 让系统瘫痪

测试结束发现 当jmeter并发线程逐步升高到500以上时,性能开始下降直至整个系统崩溃 。似乎使用Tomcat NIO模型性能并没有提升。和BIO模型性能差不多,这是为什么呢。

高并发下,Tomcat、HttpClient 让系统瘫痪

所以我们再仔细回想一下,Tomcat使用了NIO监听数据事件,调用线程池异步执行。当代码执行到test方法时可以断点查看确实已经在线程池里了。

所以问题不是在Tomcat NIO上。那就是OKHttpClient的问题。

OkHttpClient号称是java界性能最好的HttpClient为什么性能不行呢。以下我们分析一下OkHttpClient的实现原理。

OkHttpClient底层使用BIO

如果你阅读过OkHttpClient的源代码你就会发现他的底层是BIO实现的。虽然使用NIO架构的Tomcat的工作线程有500个,但是当jmeter并发数到达500时,所有的线程都在阻塞等待OkHttpClient的数据返回。

如果这个时候再有新的请求上来,Tomcat就会因为线程数就不够而拒绝服务。

高并发下,Tomcat、HttpClient 让系统瘫痪

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进行并发测试,配置参数不变。

高并发下,Tomcat、HttpClient 让系统瘫痪

对比图

BIO

高并发下,Tomcat、HttpClient 让系统瘫痪

NIO

高并发下,Tomcat、HttpClient 让系统瘫痪

总结

最后我们通过流程图再梳理一下整个的调用流程。

高并发下,Tomcat、HttpClient 让系统瘫痪

1.Tomcat监听到请求后启动线程处理业务,由于返回的是DeferredResult,所以与客户端连接保持。但是线程已经释放。

2.httpClient监听到,第三方接口返回数据时,启动线程处理数据返回客户端。结束这次http请求,释放线程。

通过流程图发生,实际上从httpClient收到数据时,后续的业务逻辑是在httpClient启动的线程上执行的,而不是在Tomcat的线程上执行的。这是和BIO模式最大的区别。

都2020年你在使用Tomcat和okhttp做业务?

微信关注我,下期带你了解最前沿的Spring WebFlux。

高并发下,Tomcat、HttpClient 让系统瘫痪


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

SQL进阶教程

SQL进阶教程

[ 日] MICK / 吴炎昌 / 人民邮电出版社 / 2017-11 / 79.00元

本书是《SQL基础教程》作者MICK为志在向中级进阶的数据库工程师编写的一本SQL技能提升指南。全书可分为两部分,第一部分介绍了SQL语言不同寻常的使用技巧,带领读者从SQL常见技术,比如CASE表达式、自连接、HAVING子句、外连接、关联子查询、EXISTS……去探索新发现。这部分不仅穿插讲解了这些技巧背后的逻辑和相关知识,而且辅以丰富的示例程序,旨在帮助读者提升编程水平;第二部分着重介绍关系......一起来看看 《SQL进阶教程》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具