jdk8和Tomcat8.5是JDK和Tomcat的史诗级提升,直接把单车变跑车,所以如果你还是使用的jdk7和Tomcat8.5以后的版本,那可以考虑去线上换一下,但是不知道到时是架构师打死你还是你打死架构师
Tomcat的四种运行模式:
- BIO:同步阻塞模式,Tomcat7及之前默认的模式,性能最差,Tomcat8.5后已抛弃
- NIO:同步非阻塞模式,Tomcat8及之后默认的模式,性能高,现在的主流模式
- AIO:异步非阻塞模式,由于纯异步,性能最高
- APR:需要额外安装依赖库,从操作系统级别解决异步IO,大幅度的提高服务器的处理和响应性能,也是Tomcat运行高并发应用的首选模式,但是使用困难,依赖系统的底层网络包,团队没有C++大神和熟悉Linux底层的大神还是别拿出来秀了
主流是NIO模式,我们主要学习NIO模式的
配置优化
server status
通过配置可以看到Tomcat管理页server status,利用server status帮我们获取Tomcat的信息
我们需要修改一下配置文件,conf/tomcat-users.xml和
webapps/manager/META-INF/context.xml。
找到conf/tomcat-users.xml
<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<role rolename="manager-script"/>
<role rolename="manager-jmx"/>
<role rolename="manager-status"/>
<user username="tzb" password="123456" roles="manager-gui,admin-gui,manager-script,manager-jmx,manager-status"/>
找到
webapps/manager/META-INF/context.xml,将以下内容注释掉,我已经注释掉了,这样就能远程访问web manager了:
image.png
image.png
通过server status可以看到jvm的信息,还要很多的选项可供,例如应用程序列表、JVM信息、NIO模型等
image.png
禁用ajp协议
ajp协议是基于TCP协议的,Tomct使用ajp协议主要是为了连接http apache服务器的,http apache这个服务器已经被Ngnix完爆了,如果没有什么特殊癖好的话应该没人会用它了,但是这个配置会在程序中默认跑着十个线程,所以为了性能把这个没用的功能去除,如果不是2020年2月以后下载的Tomcat的话,是没有默认禁用的,所以需要手动禁掉
我的Tomcat是默认禁止的,我在配置中开启了ajp,我们看到这里跑了十个线程,跑了线程又用不到,浪费CUP的性能
image.png
在conf/server.xml中把这个配置注释了就禁止掉ajp了(如果你有特殊的癖好,想打开ajp,就把注释打开, 并且把secretRequired="true"修改为secretRequired="")
image.png
重启服务器,查看server status,ajp已经没有了
image.png
自定义Tomcat线程池
Tomcat需要给每一个请求创建线程,Tomcat默认的线程池最大线程数是200,核心线程数是10,如何并发高的场景,Tomcat就得不断的创建和销毁线程,所以就得自定义线程池提高核心线程数,这样可以帮助我们提高性能。但是如果不是是连接请求特别多的场景,最后别乱改,核心线程是需要占用内存的
先看看我们没修改前的状态,我使用Jvisualvm工具监控Tomcat的线程状态,我们看到这里默认是十个线程,由于我用的是Tomcat8.5所以运行模式默认是nio模式,线程的前缀是exec,记住这个名字后面用到的
image.png
修改server.xml文件,打开Executor的注释,我配置的参数是:最大线程数150,核心线程数15,线程池名称,每个线程的前缀。
为了区别,我把线程的前缀改为tzb-nb-,接着把默认的连接器配置注释掉,打开下面的连接器,让自定义的连接线程池生效
image.png
image.png
重启服务器,查看Jvisualvm,我们看到连接池已经生效了,整好十五个
image.png
Tomcat线程模型
我们主要讲Tomcat8后的NIO,因为NIO才是主流,Tomcat的线程有很多,主线程叫做main是负责启动和关闭
Tomcat的主要线程
看监控工具可以看到Tomcat的主要线程
Tomcat的主要线程
- Acceptor
使用NIO的原生ServerSocketChannel的accept()方法监听客户的连接请求,但是这个ServerSocketChannel是设置阻塞的,所以当没有连接请求来时,线程会阻塞在accept方法上。ServerSocketChannel设置的TCP连接队列大小默认是100,TCP连接队列就是把当前连接信息存放到全连接队列中,队列中的连接信息等待ServerSocket.accpt()处理,Acceptor队列由acceptCount控制,但是Tomcat保持的通道数默认是10000,也就是ServerSocket.accpt()进去的通道Tomcat能保持10000个,由maxConnections控制。然后将ServerSocket.accpt()监听获取到的客户端通道SocketChanel设置为非阻塞,同时将SocketChanel和注册事件封装成一个PollerEvent对象扔进队列SynchronizedStack中,SynchronizedStack内部是数组,然后队列里的PollerEvent等待Poller线程来注册处理,Poller线程内部就是使用了NIO中的Selector多路复用器,PollerEvent对象主要是装载了SocketChanel和注册事件OP_REGISTER在Poller内部其实就是给Selector注册OP_READ,PollerEvent其实本身也是个线程。Poller线程是队列的消费者,Acceptor是生产者。这个Acceptor线程默认只有1个,但是可以在Tomcat中配置数量
public void bind() throws Exception {
if (!getUseInheritedChannel()) {
//Acceptor的ServerSocketChannel
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!=null?
new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
serverSock.socket().bind(addr,getAcceptCount())