Undertow,Tomcat和Jetty服务器配置详解与性能测试
undertow,jetty和tomcat可以说是javaweb项目当下最火的三款服务器,tomcat是apache下的一款重量级的服务器,不用多说历史悠久,经得起实践的考验。然而:当下微服务兴起,spring boot ,spring cloud 越来越热的情况下,选择一款轻量级而性能优越的服务器是必要的选择。spring boot 完美集成了tomcat,jetty和undertow,本文将通过对jetty和undertow服务器的分析以及测试,来比较两款服务器的性能如何。
值得一提的是jetty和undertow都是基于NIO实现的高并发轻量级的服务器,支持servlet3.1和websocket。所以,有必要先了解下什么是NIO。
NIO(非阻塞式输入输出)
- Channel
- Selector
- Buffer
- Acceptor
Client和Server只向Buffer读写数据不关注数据的流向,数据通过Channel通道进行流转。而Selector是存在与服务端的,用于Channel的注册以此实现数据I/O操作。Acceptor负责接受所以的连接通道并且注册到Channel中。而整个过程客户端与服务端是非阻塞的也就是异步操作。
Jetty和Undertow主要配置
对于服务器端而言我们关心的重点不是连接超时时间,socket超时时间以及任务的超时时间等的配置,重点是线程池设置,包括工作线程,I/O线程的分配。Jetty在这方面似乎有点太随意,全局使用一个线程池QueuedThreadPool,而最小线程数8最大200,Acceptor线程默认1个,Selector线程数默认2个。而Undertow就比较合理点,Acceptor通过递归循环注册,而用于I/O的线程默认是cpu的线程数,而工作线程是cpu线程数*8。
对于服务器而言,如何分配线程可以提高服务器的并发性能。所以,下面将分析两款服务器的详细配置。
服务器如何实现通道的注册
Jetty可以设置acceptors的线程数默认是1个。详细实现如下:
protected void doStart() throws Exception {
if(this._defaultProtocol == null) {
throw new IllegalStateException("No default protocol for " + this);
} else {
this._defaultConnectionFactory = this.getConnectionFactory(this._defaultProtocol);
if(this._defaultConnectionFactory == null) {
throw new IllegalStateException("No protocol factory for default protocol \'" + this._defaultProtocol + "\' in " + this);
} else {
SslConnectionFactory ssl = (SslConnectionFactory)this.getConnectionFactory(SslConnectionFactory.class);
if(ssl != null) {
String i = ssl.getNextProtocol();
ConnectionFactory a = this.getConnectionFactory(i);
if(a == null) {
throw new IllegalStateException("No protocol factory for SSL next protocol: \'" + i + "\' in " + this);
}
}
super.doStart();
this._stopping = new CountDownLatch(this._acceptors.length);
for(int var4 = 0; var4 < this._acceptors.length; ++var4) {
AbstractConnector.Acceptor var5 = new AbstractConnector.Acceptor(var4, null);
this.addBean(var5);
this.getExecutor().execute(var5);
}
this.LOG.info("Started {}", new Object[]{this});
}
}
}
加黑地方就是启动所有的acceptors线程,以下是线程详细执行过程。
public void run() {
Thread thread = Thread.currentThread();
String name = thread.getName();
this._name = String.format("%s-acceptor-%d@%x-%s", new Object[]{name, Integer.valueOf(this._id), Integer.valueOf(this.hashCode()), AbstractConnector.this.toString()});
thread.setName(this._name);
int priority = thread.getPriority();
if(AbstractConnector.this._acceptorPriorityDelta != 0) {
thread.setPriority(Math.max(1, Math.min(10, priority + AbstractConnector.this._acceptorPriorityDelta)));
}
AbstractConnector stopping = AbstractConnector.this;
synchronized(AbstractConnector.this) {
AbstractConnector.this._acceptors[this._id] = thread;
}
while(true) {
boolean var24 = false;
try {
var24 = true;
if(!AbstractConnector.this.isRunning()) {
var24 = false;
break;
}
try {
Lock stopping2 = AbstractConnector.this._locker.lock();
Throwable var5 = null;
try {
if(!AbstractConnector.this._accepting && AbstractConnector.this.isRunning()) {
AbstractConnector.this._setAccepting.await();
continue;
}
} catch (Throwable var41) {
var5 = var41;
throw var41;
} finally {
if(stopping2 != null) {
if(var5 != null) {
try {
stopping2.close();
} catch (Throwable var38) {
var5.addSuppressed(var38);
}
} else {
stopping2.close();
}
}
}
} catch (InterruptedException var43) {
continue;
}
try {
AbstractConnector.this.accept(this._id);
} catch (Throwable var40) {
if(!AbstractConnector.this.handleAcceptFailure(var40)) {
var24 = false;
break;
}
}
} finally {
if(var24) {
thread.setName(name);
if(AbstractConnector.this._acceptorPriorityDelta != 0) {
thread.setPriority(priority);
}
AbstractConnector stopping1 = AbstractConnector.this;
synchronized(AbstractConnector.this) {
AbstractConnector.this._acceptors[this._id] = null;
}
CountDownLatch stopping4 = AbstractConnector.this._stopping;
if(stopping4 != null) {
stopping4.countDown();
}
}
}
}
thread.setName(name);
if(AbstractConnector.this._acceptorPriorityDelta != 0) {
thread.setPriority(priority);
}
stopping = AbstractConnector.this;
synchronized(AbstractConnector.this) {
AbstractConnector.this._acceptors[this._id] = null;
}
CountDownLatch stopping3 = AbstractConnector.this._stopping;
if(stopping3 != null) {
stopping3.countDown();
}
}
可以看到通过while循环监听所有建立的连接通道,然后在将通道submit到SelectorManager中。
Undertow就没有这方面的处理,通过向通道中注册Selector,使用ChannelListener API进行事件通知。在创建Channel时,就赋予I/O线程,用于执行所有的ChannelListener回调方法。
SelectionKey registerChannel(AbstractSelectableChannel channel) throws ClosedChannelException {
if(currentThread() == this) {
return channel.register(this.selector, 0);
} else if(THREAD_SAFE_SELECTION_KEYS) {
SelectionKey task1;
try {
task1 = channel.register(this.selector, 0);
} finally {
if(this.polling) {
this.selector.wakeup();
}
}
return task1;
} else {
WorkerThread.SynchTask task = new WorkerThread.SynchTask();
this.queueTask(task);
SelectionKey var3;
try {
this.selector.wakeup();
var3 = channel.register(this.selector, 0);
} finally {
task.done();
}
return var3;
}
所以:无论设计架构如何可以看到两个服务器都是基于NIO实现的,而且都有通过Selector来执行所有的I/O操作,通过IP的hash来将Channel放入不同的WorkThread或SelectorManager中,然后具体的处理工作有线程池来完成。所有,我个人认为Jetty的selectors数和Undertow的IOThreads数都是用于Selector或说是做I/O操作的线程数。不同的是Jetty全局线程池。而对于两个服务器的承载能力以及读写效率,包括LifeCycle过程的管理等,决定了两个服务器性能的好坏。毕竟用于工作的线程所有的开销在于业务,所有个人觉得:I/O操作,管理与监听,决定了两个服务器的优劣。
Jetty和Undertow压测分析
准备工具:
- siege用于压测
- VisualVm用于监测
项目准备:
Jetty:acceptors=1,selectors=2, min and max threads=200
Undertow: work_threads=200,io_threads=2
压测梯度:
siege -c 50 -r 2000 -t 2 --log=/Users/maybo/joinit_200.log http://127.0.0.1:8080/test
siege -c 80 -r 2000 -t 2 --log=/Users/maybo/joinit_200.log http://127.0.0.1:8080/test
siege -c 100 -r 2000 -t 2 --log=/Users/maybo/joinit_200.log http://127.0.0.1:8080/test
测试结果:
| 服务器 | 命中 | 成功率 | 吞吐量 | 平均耗时 |
| Jetty | 11488 | 100% | 96.25 trans/sec | 0.00sec |
| 18393 | 100% | 153.92 trans/sec | 0.01sec | |
| 21484 | 99.99% | 179.51 trans/sec | 0.01sec | |
| Undertow | 11280 | 100% | 94.02 trans/sec | 0.00sec |
| 19442 | 100% | 163.35 trans/sec | 0.01sec | |
| 23277 | 100% | 195.54 tran/sec | 0.01sec | |
| Tomcat | 10845 | 100% | 90.95 trans/sec | 0.02sec |
| 21673 | 99.98% | 181 trans/sec | 0.01sec | |
| 25084 | 99.98% | 209.10 trans/sec | 0.01sec |
从中可以看出在高负载下Undertow的吞吐量高于Jetty而且随着压力增大Jetty和Undertow成功率差距会拉大。而在负载不是太大情况下服务器处理能力差不多,jetty还略微高于Undertow。而tomcat的负载能力似乎和Undertow很接近。
对比三个服务器发现在Undertow在负载过重情况下比Jetty和Tocmat更加顽强,实践证明在负载继续加大情况下Undertow的成功率高于其它两者,但是在并发不是太大情况下三款服务器整体来看差别不大。此次测试网络传输数据量太小,所以没有通过不断加大数据传输量来观察负载情况,个人决定测试一款服务器的I/O情况,还要通过改变数据传输量来看看在大数据文本高负载下三款服务器的性能。
大数据量测试
使用1892byte回复数据测试三款服务器性能,下面是开启线程执行情况图。
Undertow:

Jetty:

Tomcat:

实验过程发现Undertow和Tomcat的负载能力很接近但是Undertow比较好点,而Jetty远远不足。通过观察以上三张图不难发现,Undertow的I/O线程执行100% , Tomcat的执行也是100%两者不同的是Undertow用于I/O的线程数是可以调整的,而Tomcat不可以,起码通过spring boot 无法调整,这样就制约了它的负载能力。而Jetty由于全局共享线程池所以,会存在Selector和Acceptor阻塞情况,这样就制约了I/O操作。但是有个好处就是在负载不是太重的情况下可以使工作线程有更多占用资源来处理程序,提高了吞吐量。但是,总体而言这种差距是很小的。
结论:
本篇不在分析三款服务器孰好孰坏,意在通过分析源码理解服务器实现原理,然后搞清楚服务器配置参数的意义,更好的为项目服务。
Undertow,Tomcat和Jetty服务器配置详解与性能测试的更多相关文章
- CAS (5) —— Nginx代理模式下浏览器访问CAS服务器配置详解
CAS (5) -- Nginx代理模式下浏览器访问CAS服务器配置详解 tomcat版本: tomcat-8.0.29 jdk版本: jdk1.8.0_65 nginx版本: nginx-1.9.8 ...
- Apache + Tomcat集群配置详解 (1)
一.软件准备 Apache 2.2 : http://httpd.apache.org/download.cgi,下载msi安装程序,选择no ssl版本 Tomcat 6.0 : http://to ...
- 【转】VPN服务器配置详解
参考博文: VPN服务器配置详解 等公司上服务器开始配置 vpn
- Tomcat配置(二):tomcat配置文件server.xml详解和部署简介
*/ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hl ...
- Tomcat(二):tomcat配置文件server.xml详解和部署简介
Tomcat系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.html 1. 入门示例:虚拟主机提供web服务 该示例通过设置虚拟主机来提供web服务 ...
- Nginx + Tomcat 负载均衡配置详解
Nginx常用操作指南一.Nginx 与 Tomcat 安装.配置及优化1. 检查和安装依赖项 yum -y install gcc pcre pcre-devel zlib zlib-devel o ...
- Tomcat安装及配置详解
Tomcat安装及配置详解 一,Tomcat简介 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat是Apache 软件基金会(Apache Software Found ...
- 引用 Windows Server 2003 FTP服务器配置详解
引用 昆神之星 的 Windows Server 2003 FTP服务器配置详解 1.FTP文件传输协议,主要用于计算机之间文件传输,是互联网上仅次于www的第二大服务.本文主要演示如何在Window ...
- Tomcat启动过程原理详解 -- 非常的报错:涉及了2个web.xml等文件的加载流程
Tomcat启动过程原理详解 发表于: Tomcat, Web Server, 旧文存档 | 作者: 谋万世全局者 标签: Tomcat,原理,启动过程,详解 基于Java的Web 应用程序是 ser ...
随机推荐
- Linux常用命令大全(转载)
最近都在和Linux打交道,这方面基础比较薄弱的我只好买了本鸟哥的书看看,感觉还不错.我觉得Linux相比windows比较麻烦的就是很多东西都要用命令来控制,当然,这也是很多人喜欢linux的原因, ...
- SpringMVC中使用@ResponseBody注解将任意POJO对象返回值转换成json进行返回
@ResponseBody 作用: 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区. ...
- [ASP.NET 大牛之路]02 - C#高级知识点概要(1) - 委托和事件
在ASP.NET MVC 小牛之路系列中,前面用了一篇文章提了一下C#的一些知识点.照此,ASP.NET MVC 大牛之路系列也先给大家普及一下C#.NET中的高级知识点.每个知识点不太会过于详细,但 ...
- linux中gdb的可视化调试
今天get到一个在linux下gdb调试程序的技巧和大家分享一下!平时我们利用gcc进行编程,进行程序调试时,观察程序的跳转等不是这么直观.都是入下的界面! 但是如果我们在编译连接时上加了-g命令生成 ...
- java.util.ResourceBundle 读取国际化资源或配置文件
1.定义三个资源文件,放到src的根目录下面 命名规范是: 自定义名_语言代码_国别代码.properties 默认 : 自定义名.properties 2.资源文件都必须是ISO-8859-1编 ...
- python 面向对象 私有属性
__init__构造函数 self.name = name # 属性, 实例变量,成员变量,字段 def sayhi()# 方法, 动态属性 私有属性不对外看到 前面加上__ class role() ...
- IScroll5中文API整理,用法与参考
IScroll是移动页面上被使用的一款仿系统滚动插件.IScroll5相对于之前的IScroll4改进了许多,使得大家可以更方便的定制所需的功能了. 做项目的时候正好用到了这个插件,自己做了一下总结, ...
- mysql++使用
Mysql++是官方发布的.一个为MySQL设计的C++语言的API.Mysql++为Mysql的C-Api的再次封装,它用STL(Standard Template Language)开发并编写,并 ...
- Django 基础篇章
Django 紧紧地遵循这种 MVC 模式,可以称得上是一种 MVC 框架. 以下是 Django 中 M.V 和 C 各自的含义: M ,数据存取部分,由django数据库层处理. V ,选择显示哪 ...
- Quick中require与import的区别
载入一个模块 import() 与 require() 功能相同,但具有一定程度的自动化特性. 假设我们有如下的目录结构: app/ app/classes/ app/classes/MyClass. ...