内容简介:高并发下,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个小时
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Designing for Emotion
Aarron Walter / Happy Cog / 2011-10-18 / USD 18.00
Make your users fall in love with your site via the precepts packed into this brief, charming book by MailChimp user experience design lead Aarron Walter. From classic psychology to case studies, high......一起来看看 《Designing for Emotion》 这本书的介绍吧!