jdk8和Tomcat8.5是JDK和Tomcat的史詩(shī)級(jí)提升,直接把單車(chē)變跑車(chē)撵割,所以如果你還是使用的jdk7和Tomcat8.5以后的版本,那可以考慮去線上換一下,但是不知道到時(shí)是架構(gòu)師打死你還是你打死架構(gòu)師
Tomcat的四種運(yùn)行模式:
- BIO:同步阻塞模式澎蛛,Tomcat7及之前默認(rèn)的模式,性能最差蜕窿,Tomcat8.5后已拋棄
- NIO:同步非阻塞模式谋逻,Tomcat8及之后默認(rèn)的模式,性能高桐经,現(xiàn)在的主流模式
- AIO:異步非阻塞模式毁兆,由于純異步,性能最高
- APR:需要額外安裝依賴庫(kù)阴挣,從操作系統(tǒng)級(jí)別解決異步IO气堕,大幅度的提高服務(wù)器的處理和響應(yīng)性能,也是Tomcat運(yùn)行高并發(fā)應(yīng)用的首選模式,但是使用困難,依賴系統(tǒng)的底層網(wǎng)絡(luò)包茎芭,團(tuán)隊(duì)沒(méi)有C++大神和熟悉Linux底層的大神還是別拿出來(lái)秀了
主流是NIO模式揖膜,我們主要學(xué)習(xí)NIO模式的
配置優(yōu)化
server status
通過(guò)配置可以看到Tomcat管理頁(yè)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壹粟,將以下內(nèi)容注釋掉,我已經(jīng)注釋掉了宿百,這樣就能遠(yuǎn)程訪問(wèn)web manager了:禁用ajp協(xié)議
ajp協(xié)議是基于TCP協(xié)議的,Tomct使用ajp協(xié)議主要是為了連接http apache服務(wù)器的外臂,http apache這個(gè)服務(wù)器已經(jīng)被Ngnix完爆了坐儿,如果沒(méi)有什么特殊癖好的話應(yīng)該沒(méi)人會(huì)用它了,但是這個(gè)配置會(huì)在程序中默認(rèn)跑著十個(gè)線程宋光,所以為了性能把這個(gè)沒(méi)用的功能去除貌矿,如果不是2020年2月以后下載的Tomcat的話,是沒(méi)有默認(rèn)禁用的罪佳,所以需要手動(dòng)禁掉
我的Tomcat是默認(rèn)禁止的逛漫,我在配置中開(kāi)啟了ajp,我們看到這里跑了十個(gè)線程,跑了線程又用不到赘艳,浪費(fèi)CUP的性能自定義Tomcat線程池
Tomcat需要給每一個(gè)請(qǐng)求創(chuàng)建線程掰曾,Tomcat默認(rèn)的線程池最大線程數(shù)是200旭蠕,核心線程數(shù)是10,如何并發(fā)高的場(chǎng)景旷坦,Tomcat就得不斷的創(chuàng)建和銷(xiāo)毀線程掏熬,所以就得自定義線程池提高核心線程數(shù),這樣可以幫助我們提高性能秒梅。但是如果不是是連接請(qǐng)求特別多的場(chǎng)景旗芬,最后別亂改,核心線程是需要占用內(nèi)存的
先看看我們沒(méi)修改前的狀態(tài)捆蜀,我使用Jvisualvm工具監(jiān)控Tomcat的線程狀態(tài)疮丛,我們看到這里默認(rèn)是十個(gè)線程辆琅,由于我用的是Tomcat8.5所以運(yùn)行模式默認(rèn)是nio模式,線程的前綴是exec,記住這個(gè)名字后面用到的
修改server.xml文件这刷,打開(kāi)Executor的注釋婉烟,我配置的參數(shù)是:最大線程數(shù)150,核心線程數(shù)15暇屋,線程池名稱似袁,每個(gè)線程的前綴。
為了區(qū)別咐刨,我把線程的前綴改為tzb-nb-昙衅,接著把默認(rèn)的連接器配置注釋掉,打開(kāi)下面的連接器定鸟,讓自定義的連接線程池生效
Tomcat線程模型
我們主要講Tomcat8后的NIO,因?yàn)镹IO才是主流啼县,Tomcat的線程有很多,主線程叫做main是負(fù)責(zé)啟動(dòng)和關(guān)閉看監(jiān)控工具可以看到Tomcat的主要線程
-
Acceptor
使用NIO的原生ServerSocketChannel的accept()方法監(jiān)聽(tīng)客戶的連接請(qǐng)求沸久,但是這個(gè)ServerSocketChannel是設(shè)置阻塞的季眷,所以當(dāng)沒(méi)有連接請(qǐng)求來(lái)時(shí),線程會(huì)阻塞在accept方法上卷胯。ServerSocketChannel設(shè)置的TCP連接隊(duì)列大小默認(rèn)是100子刮,TCP連接隊(duì)列就是把當(dāng)前連接信息存放到全連接隊(duì)列中,隊(duì)列中的連接信息等待ServerSocket.accpt()處理窑睁,Acceptor隊(duì)列由acceptCount控制挺峡,但是Tomcat保持的通道數(shù)默認(rèn)是10000,也就是ServerSocket.accpt()進(jìn)去的通道Tomcat能保持10000個(gè)担钮,由maxConnections控制橱赠。然后將ServerSocket.accpt()監(jiān)聽(tīng)獲取到的客戶端通道SocketChanel設(shè)置為非阻塞,同時(shí)將SocketChanel和注冊(cè)事件封裝成一個(gè)PollerEvent對(duì)象扔進(jìn)隊(duì)列SynchronizedStack中裳朋,SynchronizedStack內(nèi)部是數(shù)組病线,然后隊(duì)列里的PollerEvent等待Poller線程來(lái)注冊(cè)處理吓著,Poller線程內(nèi)部就是使用了NIO中的Selector多路復(fù)用器鲤嫡,PollerEvent對(duì)象主要是裝載了SocketChanel和注冊(cè)事件OP_REGISTER在Poller內(nèi)部其實(shí)就是給Selector注冊(cè)O(shè)P_READ,PollerEvent其實(shí)本身也是個(gè)線程绑莺。Poller線程是隊(duì)列的消費(fèi)者暖眼,Acceptor是生產(chǎn)者。這個(gè)Acceptor線程默認(rèn)只有1個(gè)纺裁,但是可以在Tomcat中配置數(shù)量
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());//getAcceptCount()默認(rèn)是100
} else {
//............................省略
//設(shè)置為阻塞的連接監(jiān)聽(tīng)通道
serverSock.configureBlocking(true);
//............................省略
protected final void startAcceptorThreads() {
//Acceptor的線程個(gè)數(shù)诫肠,這個(gè)是可以通過(guò)配置文件配置的
int count = getAcceptorThreadCount();
//創(chuàng)建Acceptor線程
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
//啟動(dòng)Acceptor線程
t.start();
}
}
protected class Acceptor extends AbstractEndpoint.Acceptor {
//............................省略
@Override
public void run() {
//............................省略
while (running) {
//計(jì)數(shù)+1司澎,達(dá)到最大值則等待,tomcat設(shè)定個(gè)最大連接數(shù)是10000,達(dá)到這個(gè)閾值后栋豫,就會(huì)拒絕連接請(qǐng)求挤安,進(jìn)行阻塞,這個(gè)是用AQS阻塞隊(duì)列實(shí)現(xiàn)的
countUpOrAwaitConnection();
SocketChannel socket = null;
try {
//ServerSocketChannel開(kāi)始監(jiān)聽(tīng)連接
socket = serverSock.accept();
//............................省略
if (running && !paused) {
if (!setSocketOptions(socket)) {//setSocketOptions設(shè)置socket的方法
closeSocket(socket);
}
//............................省略
private void closeSocket(SocketChannel socket) {
//斷開(kāi)連接計(jì)數(shù)器將會(huì)減1
countDownConnection();
protected boolean setSocketOptions(SocketChannel socket) {
try {
//將監(jiān)聽(tīng)到的SocketChannel 設(shè)置為非阻塞
socket.configureBlocking(false);
Socket sock = socket.socket();
//............................省略
//注冊(cè)到ClientPoller的方法
getPoller0().register(channel);
//............................省略
}
public void register(final NioChannel socket) {
//............................省略
if ( r==null)
//封裝成PollerEvent丧鸯,與OP_REGISTER注冊(cè)事件綁定
r = new PollerEvent(socket,ka,OP_REGISTER);
else r.reset(socket,ka,OP_REGISTER);
//添加到隊(duì)列
addEvent(r);
}
public static class PollerEvent implements Runnable {
//............................省略
@Override
public void run() {
//注冊(cè)事件蛤铜,我們看到注冊(cè)事件其實(shí)就是OP_READ讀事件
if (interestOps == OP_REGISTER) {
try {
//將事件和通道注冊(cè)到ClientPoller中的多路復(fù)用器中
socket.getIOChannel().register(
socket.getPoller().getSelector(), SelectionKey.OP_READ, socketWrapper);
-
ClientPoller
Tomcat8以后的NIO模式比Tomcat7以前強(qiáng)悍就是因?yàn)檫@個(gè)ClientPoller,ClientPoller是實(shí)現(xiàn)了Runnable丛肢,ClientPoller線程內(nèi)部有一個(gè)jdk原生的Selector對(duì)象围肥,也就是NIO的多路復(fù)用器。ClientPoller線程從隊(duì)列SynchronizedQueue中取出PollerEvent逐一啟動(dòng)蜂怎,啟動(dòng)后PollerEvent將SocketChanel和對(duì)應(yīng)的事件逐一注冊(cè)注冊(cè)到ClientPoller的Selector穆刻,Selector找出SocketChanel中就緒的SelectionKey,將SelectionKey和事件類型交給工作線程Exec進(jìn)行處理杠步。整個(gè)過(guò)程就是典型的NIO實(shí)現(xiàn)氢伟。Tomcat默認(rèn)啟動(dòng)Math.min(2,Runtime.getRuntime().availableProcessors())個(gè)ClientPoller線程,絕大部分的情況是2個(gè)ClientPoller幽歼,也就是兩個(gè)Selector需要處理成千上萬(wàn)的事件腐芍,非常繁忙
//processKey源碼大致邏輯,省略詳細(xì)代碼
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
//............................省略
if ( close ) {
//............................省略
} else if ( sk.isValid() && attachment != null ) {
if (sk.isReadable() || sk.isWritable() ) {
// 寫(xiě)事件
if ( attachment.getSendfileData() != null ) {
processSendfile(sk,attachment, false);
} else {
if (sk.isReadable()) {
//讀事件
if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
if (!closeSocket && sk.isWritable()) {
// 寫(xiě)事件
if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
}
if (closeSocket) {
............................
// 關(guān)閉通道
}
..................................
}
-
NioBlockingSelector.BlockPoller
NioBlockingSelector主要處理socketChanel的寫(xiě)操作试躏,也就是servlet的回寫(xiě)過(guò)程猪勇,NioBlockingSelector有兩個(gè)對(duì)象BlockPoller和Selector叫做sharedSelector。BlockPoller中也有一個(gè)Selector對(duì)象颠蕴,這個(gè)Selector主要是在blockingSelector.write時(shí)如果出現(xiàn)寫(xiě)失敗就把socketChanel注冊(cè)到BlockPoller的Selector不在占用ClientPoller的時(shí)間片泣刹,然后就利用CountDownLatch進(jìn)行阻塞這是Tomcat默認(rèn)的寫(xiě)操作過(guò)程,在BlockPoller的Selector注冊(cè)的socketChanel以后的讀寫(xiě)操作都是由這個(gè)Selector阻塞輪詢犀被。同時(shí)NioBlockingSelector是NioSelectorPool中一個(gè)成員對(duì)象椅您,NioSelectorPool中有兩個(gè)成員對(duì)象NioBlockingSelector和一個(gè)Selector叫做SHARED_SELECTOR,SHARED_SELECTOR主要在非默認(rèn)的寫(xiě)過(guò)程中處理由ClientPoller輪詢的socketChanel出現(xiàn)寫(xiě)失敗時(shí)寡键,為了節(jié)省ClientPoller寶貴的時(shí)間片socketChanel寫(xiě)事件會(huì)注冊(cè)到SHARED_SELECTOR上掀泳,這個(gè)SHARED_SELECTOR是叫做輔Selector,值得一提的是NioSelectorPool的SHARED_SELECTOR西轩、NioBlockingSelector的sharedSelector员舵、BlockPoller的Selector都是同一對(duì)象,這個(gè)輔Selector輪詢出現(xiàn)寫(xiě)事件失敗的socketChanel藕畔,由輔Selector負(fù)責(zé)這些socketChanel以后的讀寫(xiě)事件马僻,這樣減少線程間的切換,同時(shí)還可減輕主Selector的負(fù)擔(dān)注服。
public class NioSelectorPool {
//SHARED 默認(rèn)是true
protected static final boolean SHARED =
Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.net.NioSelectorShared", "true"));
protected NioBlockingSelector blockingSelector;
protected volatile Selector SHARED_SELECTOR;
//........................省略
public int write(ByteBuffer buf, NioChannel socket, Selector selector,
long writeTimeout, boolean block) throws IOException {
if ( SHARED && block ) {//block 和SHARED 是默認(rèn)true韭邓,所以寫(xiě)事件默認(rèn)走這里
//阻塞寫(xiě)操作
return blockingSelector.write(buf,socket,writeTimeout);
}
//........................省略
if ( keycount > 0 ) { //only write if we were registered for a write
//寫(xiě)操做措近,cnt -1為失敗
cnt = socket.write(buf); //write the data
if (cnt > 0) {
continue;
}
//非阻塞寫(xiě)
if (cnt==0 && (!block)) break; //don't block
}
if ( selector != null ) {
//非阻塞寫(xiě)失敗后重新注冊(cè)到SHARED_SELECTOR,不占用ClientPoller的時(shí)間片
if (key==null) key = socket.getIOChannel().register(selector, SelectionKey.OP_WRITE);
else key.interestOps(SelectionKey.OP_WRITE);
} else if (writeTimeout<0) {
//SHARED_SELECTOR輪詢
keycount = selector.select();
} else {
keycount = selector.select(writeTimeout);
}
}
//.....................................................................省略
ublic class NioBlockingSelector {
protected Selector sharedSelector;
protected BlockPoller poller;
//阻塞寫(xiě)
public int write(ByteBuffer buf, NioChannel socket, long writeTimeout)
throws IOException {
SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
try {
while ( (!timedout) && buf.hasRemaining()) {
if (keycount > 0) { //only write if we were registered for a write
int cnt = socket.write(buf); //write the data
written += cnt;
//寫(xiě)操做女淑,cnt -1為失敗
if (cnt > 0) {
time = System.currentTimeMillis(); //reset our timeout timer
continue; //we successfully wrote, try again without a selector
}
}
try {
if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);
//失敗則重新注冊(cè)寫(xiě)事件到BlockPoller 中
poller.add(att,SelectionKey.OP_WRITE,reference);
//利用CountDownLatch進(jìn)行阻塞
if (writeTimeout < 0) {
att.awaitWriteLatch(Long.MAX_VALUE,TimeUnit.MILLISECONDS);
} else {
att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);
}
}
-
Exec
Exec是一個(gè)線程池很多博客叫它為Work,默認(rèn)的線程名稱是和server.xml中的一致瞭郑,核心線程默認(rèn)是10,最大線程數(shù)是200鸭你,可以通過(guò)我們上面的方法配置server.xml文件設(shè)置線程的數(shù)量凰浮,它主要是獲取ClientPoller輪詢出來(lái)的socketChanel后,做相應(yīng)的邏輯處理:先進(jìn)行三次握手苇本,然后調(diào)用Http11Processor的service方法解析HTTP協(xié)議袜茧,將http協(xié)議解析成鍵值對(duì)放入request,最終調(diào)用CoyoteAdapter的service方法將request和response傳入Tomcat的各種容器中執(zhí)行servlet的業(yè)務(wù)邏輯瓣窄。
//工作線程執(zhí)行run處理socketChanel
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
if (socketWrapper == null) {
return false;
}
//將Socket封裝到這個(gè)對(duì)象笛厦,然后扔給工作線程執(zhí)行
SocketProcessorBase<S> sc = processorCache.pop();
if (sc == null) {
sc = createSocketProcessor(socketWrapper, event);
} else {
sc.reset(socketWrapper, event);
}
//工作線程,我們?cè)谂渲梦募信渲玫腅xecutor,不配默認(rèn)是10個(gè)俺夕。最大數(shù)200
Executor executor = getExecutor();
//工作線程執(zhí)行工作
if (dispatch && executor != null) {
executor.execute(sc);
} else {
sc.run();
}
//工作線程執(zhí)行的東西裳凸,主要是三次握手后開(kāi)始解析HTTP,然后關(guān)閉socket,省略部分代碼
protected class SocketProcessor extends SocketProcessorBase<NioChannel> {
public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
super(socketWrapper, event);
}
@Override
protected void doRun() {
if (handshake == 0) {//三次握手已完成
SocketState state = SocketState.OPEN;
// Process the request from this socket
if (event == null) {
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
if (state == SocketState.CLOSED) {
close(socket, key);
}
..........................
//邏輯處理主要是調(diào)用了這給ConnectionHandler處理socket的數(shù)據(jù)包
protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {
//省略 ...............................................
//處理socket數(shù)據(jù)包劝贸,將HTTP其解析成request 對(duì)象姨谷,傳給Tomcat容器
@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
//省略 ...............................................
do {
//省略 ...............................................
state = processor.process(wrapper, status);//真正繼續(xù)數(shù)據(jù)包的方法,這個(gè)processor是AbstractProcessorLight 類型的映九,但是process方法主要是調(diào)用子類Http11Processor類實(shí)現(xiàn)的service進(jìn)行處理
//省略 ...............................................
}
}
}
點(diǎn)進(jìn)process方法梦湘,里面調(diào)用了service
public abstract class AbstractProcessorLight implements Processor {
@Override
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
//省略 ...............................................
throws IOException {
} else if (status == SocketEvent.OPEN_READ) {
//這里是調(diào)用Http11Processor 的service方法
state = service(socketWrapper);
}
//省略 ...............................................
Http11Processor 的service方法進(jìn)行系列的解析各種
public class Http11Processor extends AbstractProcessor {
//省略 ...............................................
@Override
public SocketState service(SocketWrapperBase<?> socketWrapper)
throws IOException {
//省略 ...............................................
//經(jīng)過(guò)一系列的解析和設(shè)置后,將http的各種信息都存放到request中調(diào)用CoyoteAdapter的service方法將request, response傳遞到Tomcat容器
getAdapter().service(request, response);
//省略 ...............................................
}
}
NioEndpoint組件
NioEndpoint是Tomcat的NIO關(guān)鍵的類件甥,要理解tomcat的nio最主要就是對(duì)NioEndpoint的理解這個(gè)我是摘抄別人的捌议,出處http://www.reibang.com/p/76ff17bc6dea
,有空得自己去看看《Tomcat的內(nèi)核設(shè)計(jì)剖析》
NioEndpoint它一共包含LimitLatch引有、Acceptor瓣颅、Poller、SocketProcessor譬正、Excutor5個(gè)部分宫补。LimitLatch是連接控制器,它負(fù)責(zé)維護(hù)連接數(shù)的計(jì)算曾我,nio模式下默認(rèn)是10000粉怕,達(dá)到這個(gè)閾值后,就會(huì)拒絕連接請(qǐng)求您单。Acceptor負(fù)責(zé)接收連接斋荞,默認(rèn)是1個(gè)線程來(lái)執(zhí)行荞雏,將請(qǐng)求的事件注冊(cè)到事件列表虐秦。有Poller來(lái)負(fù)責(zé)輪詢平酿,Poller線程數(shù)量是cpu的核數(shù)Math.min(2,Runtime.getRuntime().availableProcessors())。由Poller將就緒的事件生成SocketProcessor同時(shí)交給Excutor去執(zhí)行悦陋。Excutor線程池的大小就是我們?cè)贑onnector節(jié)點(diǎn)配置的maxThreads的值蜈彼。在Excutor的線程中,會(huì)完成從socket中讀取http request俺驶,解析成HttpServletRequest對(duì)象幸逆,分派到相應(yīng)的servlet并完成邏輯,然后將response通過(guò)socket發(fā)回client暮现。在從socket中讀數(shù)據(jù)和往socket中寫(xiě)數(shù)據(jù)的過(guò)程还绘,并沒(méi)有像典型的非阻塞的NIO的那樣,注冊(cè)O(shè)P_READ或OP_WRITE事件到主Selector栖袋,而是直接通過(guò)socket完成讀寫(xiě)拍顷,這時(shí)是阻塞完成的,但是在timeout控制上塘幅,使用了NIO的Selector機(jī)制昔案,但是這個(gè)Selector并不是Poller線程維護(hù)的主Selector,而是BlockPoller線程中維護(hù)的Selector电媳,稱之為輔Selector
Tomcat文件傳輸
sendfile零拷貝:
在普通的輸入輸出流進(jìn)行讀寫(xiě)時(shí)踏揣,實(shí)際上是進(jìn)行了多次上下文切換,應(yīng)用讀取數(shù)據(jù)時(shí)匾乓,先在內(nèi)核態(tài)將數(shù)據(jù)從磁盤(pán)讀取到內(nèi)核緩存捞稿,再切換到用戶態(tài)將數(shù)據(jù)從內(nèi)核緩存讀取到用戶緩存,在用戶態(tài)對(duì)數(shù)據(jù)進(jìn)行加工拼缝,然后再?gòu)挠脩魬B(tài)切換回內(nèi)核態(tài)括享,將用戶緩存數(shù)據(jù)拷貝到內(nèi)核緩存中,然后讀到socket中再到網(wǎng)卡
sendfile實(shí)質(zhì)是linux系統(tǒng)中一項(xiàng)優(yōu)化技術(shù)珍促,用以發(fā)送文件和網(wǎng)絡(luò)通信時(shí)铃辖,減少用戶態(tài)空間與磁盤(pán)倒換數(shù)據(jù),而直接在內(nèi)核級(jí)做數(shù)據(jù)拷貝猪叙,在Tomcat中是可以使用sendfile對(duì)一些靜態(tài)數(shù)據(jù)如:圖片娇斩、文件等進(jìn)行傳輸,這個(gè)sendfile是在Tomcat是默認(rèn)打開(kāi)的穴翩,可以有效的提高Tomcat的傳輸性能compression文件壓縮:
compression可以對(duì)傳輸文件進(jìn)行壓縮有效解決文件傳輸?shù)膸拞?wèn)題犬第,在Tomcat不支持圖片壓縮,因?yàn)橐M(jìn)行上層的用戶態(tài)數(shù)據(jù)加工所以與sendfile互斥芒帕,Tomcat默認(rèn)關(guān)閉歉嗓,開(kāi)啟的話Tomcat會(huì)調(diào)用Filter利用jdk解壓縮流進(jìn)行解壓縮
//源碼中compression支持的壓縮格式中沒(méi)有圖片
private String compressibleMimeType = "text/html,text/xml,text/plain,text/css," +
"text/javascript,application/javascript,application/json,application/xml";
Accept-Encoding 和Content-Encoding
Accept-Encoding 和Content-Encoding是HTTP中用來(lái)對(duì)采用哪種編碼格式傳輸正文進(jìn)行協(xié)定的一對(duì)頭部字段。瀏覽器發(fā)送請(qǐng)求時(shí)背蟆,會(huì)在Request-heads攜帶Accept-Encoding會(huì)說(shuō)明自己支持的編碼列表鉴分,服務(wù)端在接收到請(qǐng)求后哮幢,從中挑選出一種用來(lái)對(duì)響應(yīng)信息進(jìn)行編碼,并通過(guò)Response-heads攜帶Content-Encoding來(lái)說(shuō)明服務(wù)端選擇的編碼信息志珍,瀏覽器在拿到響應(yīng)正文后橙垢,依據(jù)Content-Encoding進(jìn)行解壓。絕大部分是gzip格式
Tomcat重要配置參數(shù)
server.xml
自定義線程池我們上面講過(guò)了伦糯,現(xiàn)在有了一定的基礎(chǔ)更好介紹了
Executor標(biāo)簽是專門(mén)配置Exec線程池的柜某,專門(mén)用于處理多路復(fù)用器傳遞進(jìn)來(lái)的socketChanel,我們上面介紹過(guò)了
<Executor name="tomcatThreadPool"
namePrefix="tzb-nb-"
maxThreads="1000"
minSpareThreads="30"
maxIdleTime="60000"
maxQueueSize="1000000"
className="org.apache.catalina.core.StandardThreadExecutor"/>
name:線程池名稱敛纲,用于 Connector中指定喂击。
namePrefix:所創(chuàng)建的每個(gè)線程的名稱前綴。
maxThreads:池中最大線程數(shù)淤翔。
minSpareThreads:核心池線程數(shù)惭等。
maxIdleTime:線程空閑時(shí)間,超過(guò)該時(shí)間后办铡,非核心線程會(huì)被銷(xiāo)毀辞做,默認(rèn)值為6000(1分鐘),單位毫秒寡具。
maxQueueSize:在被執(zhí)行前最大線程排隊(duì)數(shù)目秤茅,任務(wù)數(shù)超出會(huì)執(zhí)行拒絕策略,默認(rèn)為Int的最大值
className:線程池實(shí)現(xiàn)類童叠,未指定情況下框喳,默認(rèn)實(shí)現(xiàn)類為org.apache.catalina.core.StandardThreadExecutor。
如果想使用自定義線程池首先需要實(shí)現(xiàn) org.apache.catalina.Executor接口厦坛。
Connector用于配置Tomcat運(yùn)行模式五垮、Acceptor、CilentPoller杜秸、sendfile放仗、是否開(kāi)啟文件壓縮
<Connector port="8080"
protocol="HTTP/1.1"
maxThreads="1000"
minSpareThreads="100"
acceptorThreadCount="2"
acceptCount="1000"
maxConnections="1000"
connectionTimeout="20000"
maxHttpHeaderSize="8192"
compression="on"
compressionMinSize="2048"
redirectPort="8443"
URIEncoding="UTF-8" />
參數(shù)很多參考這里吧:https://blog.csdn.net/lijunwyf/article/details/84244209
我只列出幾個(gè)有意思的
port:代表Tomcat監(jiān)聽(tīng)端口,也就是網(wǎng)站的訪問(wèn)端口撬碟,默認(rèn)為8080诞挨,可以根據(jù)需要改成其他。
protocol:運(yùn)行模式呢蛤,可選類型有四種惶傻,分別為BIO,NIO其障,AIO和APR银室。
protocol="org.apache.coyote.http11.Http11NioProtocol" //NIO
protocol="org.apache.coyote.http11.Http11Nio2Protocol" //AIO
protocol="org.apache.coyote.http11.Http11AprProtocol" //ARP
acceptCount:就是Acceptor線程工作時(shí)通道的長(zhǎng)度也就是前面說(shuō)的SynchronizedStack,當(dāng)請(qǐng)求PollerEvent超出隊(duì)列時(shí)請(qǐng)求就會(huì)被拒絕,默認(rèn)是100蜈敢。
一般是設(shè)置的跟 maxThreads一樣或一半辜荠,此值設(shè)置的過(guò)大會(huì)導(dǎo)致排隊(duì)的請(qǐng)求超時(shí)而未被處理。所以這個(gè)值應(yīng)該是主要根據(jù)應(yīng)用的訪問(wèn)峰值與平均值來(lái)權(quán)衡配置扶认。
acceptorThreadCount:Acceptor線程的數(shù)量,默認(rèn)是1侨拦,如果在多核CPU架構(gòu)下殊橙,此值可以設(shè)置為2辐宾,官方不建議設(shè)定超過(guò)2個(gè)的值。
maxConnections:tomcat最大連接數(shù)也就是上面說(shuō)的LimitLatch膨蛮。NIO默認(rèn)是10000叠纹,超出則Acceptor線程就被阻塞了,即不再隊(duì)列中獲取已經(jīng)建立的連接敞葛。
但是它并不阻止新的連接的建立誉察,新的連接的建立過(guò)程不是Acceptor控制的,Acceptor僅僅是從隊(duì)列中獲取新建立的連接惹谐。
所以當(dāng)連接數(shù)已經(jīng)超過(guò)maxConnections后持偏,仍然是可以建立新的連接的,存放在上述acceptCount大小的隊(duì)列中氨肌,這個(gè)隊(duì)列里面的連接沒(méi)有被Acceptor獲取鸿秆,
就處于連接建立了但是不被處理的狀態(tài)。當(dāng)連接數(shù)低于maxConnections之后怎囚,Acceptor線程就不再阻塞
connectionTimeout:當(dāng)請(qǐng)求已經(jīng)被接受卿叽,但未被處理,也就是等待中的超時(shí)時(shí)間恳守。單位為毫秒考婴,默認(rèn)值為60000。
executor:就是把executor標(biāo)簽的內(nèi)容引過(guò)來(lái)催烘,不要它的話可以把executor的內(nèi)容復(fù)制到這里
URIEncoding:URL編碼字符集沥阱。
pollerThreadCount:ClientPoller的線程數(shù),默認(rèn)為2伊群。官方不建議設(shè)置大于2的值喳钟,因?yàn)殒i的競(jìng)爭(zhēng)會(huì)導(dǎo)致性能下降,事實(shí)上一個(gè)線程也足夠快速在岂。
useSendfile:是否開(kāi)啟sendfile特性奔则,默認(rèn)為true。對(duì)于web應(yīng)用而言蔽午,通常project中還會(huì)包含一定數(shù)量的靜態(tài)資源易茬,比如圖片、CSS、js抽莱、html等范抓,sendfile在一定程度上可以提高性能。
compression:是否開(kāi)啟壓縮食铐,這個(gè)屬性與useSendfile互斥匕垫,useSendfile開(kāi)啟了,這個(gè)認(rèn)關(guān)閉虐呻,compression是默認(rèn)關(guān)閉的
compressionMinSize:如果compression="on"象泵,則啟用此項(xiàng)。被壓縮前數(shù)據(jù)的最小值斟叼,也就是超過(guò)這個(gè)值后才被壓縮偶惠。如果沒(méi)有指定,這個(gè)屬性默認(rèn)為“2048”(2K)朗涩,單位為byte忽孽。
實(shí)戰(zhàn):
我們看到Acceptor線程線程是3個(gè)、Client線程是5個(gè)谢床,exec線程是15個(gè)和我上圖的配置一致
catalina.sh
使用JAVA_OPTS配置JVM參數(shù)
//例如:
JAVA_OPTS="-Dfile.encoding=UTF-8 -server -Xms2048m -Xmx2048m
-XX:NewSize=512m -XX:MaxNewSize=1024m
-XX:PermSize=256m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -XX:+DisableExplicitGC"
下面是參考這里的https://blog.csdn.net/fly910905/article/details/78518599
一般說(shuō)來(lái)兄一,您應(yīng)該使用物理內(nèi)存的 80% 作為堆大小。說(shuō)明:以上兩個(gè)參數(shù)關(guān)系到tomcat承受的訪問(wèn)性能识腿,但也要根據(jù)服務(wù)器實(shí)際內(nèi)存情況設(shè)定出革。有人建議Xms和Xmx的值取成一樣比較好,說(shuō)是可以加快內(nèi)存回收速度覆履。
Xms和Xmx這兩個(gè)值的大小一般根據(jù)需要進(jìn)行配置蹋盆。初始化堆的大小執(zhí)行了虛擬機(jī)在啟動(dòng)時(shí)向系統(tǒng)申請(qǐng)的內(nèi)存的大小。一般而言硝全,這個(gè)參數(shù)不重要栖雾。但是有的應(yīng)用程序在大負(fù)載的情況下會(huì)急劇地占用更多的內(nèi)存,此時(shí)這個(gè)參數(shù)就是顯得很重要伟众,假如虛擬機(jī)啟動(dòng)時(shí)配置使用的內(nèi)存比較小而在這種情況下有許多對(duì)象進(jìn)行初始化析藕,虛擬機(jī)就必須重復(fù)地增加內(nèi)存來(lái)滿足使用。由于這種原因凳厢,我們一般把-Xms和-Xmx設(shè)為相同大账胧,而堆的最大值受限于系統(tǒng)使用的物理內(nèi)存。一般使用數(shù)據(jù)量較大的應(yīng)用程序會(huì)使用持久對(duì)象先紫,內(nèi)存使用有可能迅速地增長(zhǎng)治泥。當(dāng)應(yīng)用程序需要的內(nèi)存超出堆的最大值時(shí)虛擬機(jī)就會(huì)提示內(nèi)存溢出,并且導(dǎo)致應(yīng)用服務(wù)崩潰遮精。因此一般建議堆的最大值配置為可用內(nèi)存的最大值的80%居夹。
另外需要考慮的是Java提供的垃圾回收機(jī)制败潦。虛擬機(jī)的堆大小決定了虛擬機(jī)花費(fèi)在收集垃圾上的時(shí)間和頻度。收集垃圾能夠接受的速度和應(yīng)用有關(guān)准脂,應(yīng)該通過(guò)分析實(shí)際的垃圾收集的時(shí)間和頻率來(lái)調(diào)整劫扒。假如堆的大小很大,那么完全垃圾收集就會(huì)很慢狸膏,但是頻度會(huì)降低沟饥。假如您把堆的大小和內(nèi)存的需要一致,完全收集就很快湾戳,但是會(huì)更加頻繁贤旷。調(diào)整堆大小的的目的是最小化垃圾收集的時(shí)間,以在特定的時(shí)間內(nèi)最大化處理客戶的請(qǐng)求院塞。在基準(zhǔn)測(cè)試的時(shí)候遮晚,為確保最好的性能性昭,要把堆的大小設(shè)大拦止,確保垃圾收集不在整個(gè)基準(zhǔn)測(cè)試的過(guò)程中出現(xiàn)。
假如系統(tǒng)花費(fèi)很多的時(shí)間收集垃圾糜颠,請(qǐng)減小堆大小汹族。一次完全的垃圾收集應(yīng)該不超過(guò) 3-5 秒。假如垃圾收集成為瓶頸其兴,那么需要指定代的大小顶瞒,檢查垃圾收集的周詳輸出,研究 垃圾收集參數(shù)對(duì)性能的影響元旬。一般說(shuō)來(lái)榴徐,您應(yīng)該使用物理內(nèi)存的 80% 作為堆大小。當(dāng)增加處理器時(shí)匀归,記得增加內(nèi)存坑资,因?yàn)榉峙淠軌虿⑿羞M(jìn)行,而垃圾收集不是并行的穆端。
Tomcat壓力測(cè)試
使用jmeter可以進(jìn)行對(duì)程序進(jìn)行壓力測(cè)試袱贮,根據(jù)用戶的并發(fā),調(diào)整Tomcat的線程數(shù)量
我使用一千個(gè)線程,一次并發(fā)一千体啰,循環(huán)十次
主要看吞吐量攒巍,根據(jù)吞吐量慢慢調(diào)試Tomcat的線程設(shè)置