4、Tomcat源碼分析
4.1源碼構(gòu)建
下載
下載地址 https://tomcat.apache.org/download-80.cgi 下載src源碼然后解壓
配置文件
將配置文件轉(zhuǎn)移新的文件夾下避免沖突:
在 apache-tomcat-8.5.50-src ?錄中創(chuàng)建 source ?件夾
將 conf、webapps ?錄移動(dòng)到剛剛創(chuàng)建的 source ?件夾中
配置JSP初始化器
ContextConfig類中的configureStart?法中增加??代碼將 Jsp 引擎初始化。
webConfig();
//初始化jsp引擎
context.addServletContainerInitializer(new JasperInitializer(), null);
啟動(dòng)參數(shù)
-Dcatalina.home=D:/Study/tomcat-source/source
-Dcatalina.base=D:/Study/tomcat-source/source
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=D:/Study/tomcat-source/source/conf/logging.properties
D:/Study/tomcat-source/source這個(gè)就是剛剛咱們創(chuàng)建的文件夾 到時(shí)候更換成自己的文件夾
添加PomXml,將tomcat交給maven管理
POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.tomcat</groupId>
<artifactId>apache-tomcat-8.5.50-src</artifactId>
<name>Tomcat8.5</name>
<version>8.5</version>
<build>
<!--指定源?錄-->
<finalName>Tomcat8.5</finalName>
<sourceDirectory>java</sourceDirectory>
<resources>
<resource>
<directory>java</directory>
</resource>
</resources>
<plugins>
<!--引?編譯插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
<!--tomcat 依賴的基礎(chǔ)包-->
<dependencies>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>javax.xml.soap</groupId>
<artifactId>javax.xml.soap-api</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
</project>
注意這個(gè)Pom需要自己創(chuàng)建。
運(yùn)行
直接運(yùn)行Bootstrap中的main函數(shù)即可。
4.2Tomcat啟動(dòng)流程源碼分析
4.2.1Tomcat啟動(dòng)時(shí)序圖
通過(guò)時(shí)序圖觀察,通過(guò)Bootstrap中的main方法啟動(dòng),先進(jìn)行初始化,在通過(guò)load方法逐級(jí)的往下加載,加載完成之后通過(guò)start逐級(jí)的往下啟動(dòng)。
所有的組件接口都繼承了Lifecycle頂級(jí)接口,Lifecycle頂級(jí)接口指定了啟動(dòng)、銷毀、停止等規(guī)范,相當(dāng)于Spring中的BeanFactory;Tomcat通過(guò)Lifecycle統(tǒng)一規(guī)定了各個(gè)組件的生命周期。
4.2.2啟動(dòng)源碼追蹤
1、加載流程
對(duì)應(yīng)時(shí)序圖中1~13步
Bootstrap
public static void main(String args[]) {
//添加了一把鎖
synchronized (daemonLock) {
if (daemon == null) {
// 創(chuàng)建一個(gè)BootStreap實(shí)例
Bootstrap bootstrap = new Bootstrap();
try {
//調(diào)用bootrap的實(shí)例化方法
bootstrap.init();
} catch (Throwable t) {
...
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to
// prevent a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
try {
String command = "start";
...
if (command.equals("start")) {
daemon.setAwait(true);
//加載流程
daemon.load(args);
//啟動(dòng)流程
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
}
...
} catch (Throwable t) {
...
}
}
BootStrap#init方法
public void init() throws Exception {
//初始化classloader 沒(méi)有使用系統(tǒng)的使用自定義的classLoader后邊在講解
initClassLoaders();
//將自定義的classloader綁定到線程上在使用的時(shí)候可以直接拿來(lái)使用
Thread.currentThread().setContextClassLoader(catalinaLoader);
//將自定義的classLoader設(shè)置到安全的classLoader上
SecurityClassLoad.securityClassLoad(catalinaLoader);
//通過(guò)自定義的classLoader加載Catalina Class
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
//進(jìn)行實(shí)例化
Object startupInstance = startupClass.getConstructor().newInstance();
//獲取到父類的類加載器方法-- 將自定義的類加載器設(shè)置到父類中
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
//將Catalina實(shí)例賦值到 catalinaDaemon屬性上
catalinaDaemon = startupInstance;
}
BootStrap#load方法
private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
//【通過(guò)反射的方法調(diào)用Catalina的load方法】
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
method.invoke(catalinaDaemon, param);
}
Catalina#load方法
public void load() {
//判斷是否加載過(guò) 默認(rèn)為false
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
//初始化文件夾
initDirs();
// 初始名字Before digester - it may be needed
initNaming();
//保存Tomcat所需要的組件和監(jiān)聽(tīng)器的ClassName,比如StandardServer、LifecycleListener...
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
//讀取conf/server.xml配置文件
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {}
//創(chuàng)建輸入流
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {}
}
// 如果inputstream還不存在繼續(xù)創(chuàng)建
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {}
}
//如果還存在就返回吧 【省略了一些日志打印】
if (inputStream == null || inputSource == null) { return; }
try {
inputSource.setByteStream(inputStream);
//將當(dāng)前對(duì)象推入到digester
digester.push(this);
//解析server.xml配置文件
digester.parse(inputSource);
} catch (Exception e) {return; }
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {}
}
}
//給server設(shè)置一些屬性
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection 給System設(shè)置Out和ErrSystemLogHandler、SystemLogHandler
initStreams();
// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
}
LifecycleBase的init方法和initInternal方法
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
protected abstract void initInternal() throws LifecycleException;
使用典型的模板設(shè)計(jì)模式!
調(diào)用StandardServer中的initInternal方法
protected void initInternal() throws LifecycleException {
//調(diào)用父類LifecycleBase的initInternal初始化屬性
super.initInternal();
// Register global String cache
// 初始化緩存組件
onameStringCache = register(new StringCache(), "type=StringCache");
// 注冊(cè) MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
//初始化全局naming
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared
//填充一些拓展jar包校驗(yàn)
// class loaders
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// 初始化service
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
services[i].init();實(shí)際上還是走的LifecycleBase的init方法通過(guò)模板調(diào)用StandardService中的initIternal方法。
StandardService#initInternal方法
protected void initInternal() throws LifecycleException {
super.initInternal();
//初始化engine 里邊會(huì)初始化Engine、Host、Context
if (engine != null) {
engine.init();
}
// Initialize any Executors 線程池
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize mapperlistener
mapperListener.init();
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
//初始化connector
connector.init();
} catch (Exception e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
初始化Engine、Executor就追蹤了直接觀察初始化Connector方法。
Connector#initInernal方法
protected void initInternal() throws LifecycleException {
super.initInternal();
// 創(chuàng)建一個(gè)CoyoteAdapter的適配器
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// 確保 parseBodyMethodsSet has a default
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
//判斷AprLifecycleListener不可用拋出異常
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
getProtocolHandlerClassName()));
}
if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
protocolHandler instanceof AbstractHttp11JsseProtocol) {
AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
(AbstractHttp11JsseProtocol<?>) protocolHandler;
if (jsseProtocolHandler.isSSLEnabled() &&
jsseProtocolHandler.getSslImplementationName() == null) {
// OpenSSL is compatible with the JSSE configuration, so use it if APR is available
jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
}
}
try {
//重點(diǎn)來(lái)看protocolHandler初始化方法
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
調(diào)用AbstracProtocol中的初始化方法
public void init() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
}
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();
if (oname != null) {
Registry.getRegistry(null, null).registerComponent(this, oname, null);
}
}
if (this.domain != null) {
rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null);
}
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
//初始化endpoint的init方法
endpoint.init();
}
調(diào)用父類的AbstractEndpoing的init方法
public void init() throws Exception {
if (bindOnInit) {
//綁定端口
bind();
bindState = BindState.BOUND_ON_INIT;
}
//注冊(cè)一些socketProperties
if (this.domain != null) {
// Register endpoint (as ThreadPool - historical name)
oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
Registry.getRegistry(null, null).registerComponent(this, oname, null);
ObjectName socketPropertiesOname = new ObjectName(domain +
":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties");
socketProperties.setObjectName(socketPropertiesOname);
Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);
for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
registerJmx(sslHostConfig);
}
}
}
默認(rèn)調(diào)用NIOEndpoint的bind方法
public void bind() throws Exception {
//配置Socket的端口號(hào)和一些屬性
if (!getUseInheritedChannel()) {
//創(chuàng)建一個(gè)serverSocket
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!=null?
new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
//綁定端口號(hào)
serverSock.socket().bind(addr,getAcceptCount());
} else {
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {
serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {
throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
}
serverSock.configureBlocking(true); //mimic APR behavior
// Initialize thread count defaults for acceptor, poller
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
if (pollerThreadCount <= 0) {
//minimum one poller thread
pollerThreadCount = 1;
}
setStopLatch(new CountDownLatch(pollerThreadCount));
// Initialize SSL if needed
initialiseSsl();
selectorPool.open();
}
總結(jié) :加載流程將所有的各個(gè)組件都進(jìn)行創(chuàng)建和初始化配置。
2、啟動(dòng)流程
BootStrap.Start方法
public void start() throws Exception {
//如果Catalina實(shí)例為null在從新創(chuàng)建
if (catalinaDaemon == null) {
init();
}
//通過(guò)反射的方法調(diào)用Catalina的start方法
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
Catalina#Start方法
public void start() {
//校驗(yàn)server為null從新賦值
if (getServer() == null) {
load();
}
if (getServer() == null) {
return;
}
//計(jì)時(shí)
long t1 = System.nanoTime();
// Start the new server
try {
//調(diào)用StandardServer的start方法
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
//出現(xiàn)異常優(yōu)雅關(guān)閉
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
//打印計(jì)時(shí)
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook 注冊(cè)一個(gè)關(guān)機(jī)鉤子 進(jìn)行優(yōu)雅關(guān)機(jī)
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
//等候 默認(rèn)為false 當(dāng)調(diào)用關(guān)機(jī)的時(shí)候會(huì)執(zhí)行這些方法進(jìn)行優(yōu)雅關(guān)機(jī)
if (await) {
//調(diào)用的是StanardServer#Await 將socket進(jìn)行監(jiān)聽(tīng)
await();
//關(guān)閉所有進(jìn)程并且銷毀容器
stop();
}
}
StandardServer#startInternal方法
protected void startInternal() throws LifecycleException {
//出發(fā)監(jiān)聽(tīng)的事件
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
//啟動(dòng)一些namingResource
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
//調(diào)用StandardService的startInternal方法
services[i].start();
}
}
}
StandardService#startInternal
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
//engineStart方法 會(huì)調(diào)用StandardEngine#startInternal方法 在方法內(nèi)部調(diào)用父類的ContainerBase#startInternal
①engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
//啟動(dòng)線程池這一步在初始化的時(shí)候已經(jīng)啟動(dòng)了在這里不會(huì)被啟動(dòng)了
executor.start();
}
}
mapperListener.start();
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
//啟動(dòng)連接器
②connector.start();
}
} catch (Exception e) {}
}
}
}
①engine.start()
會(huì)調(diào)StandardEngine的startInernal里邊會(huì)調(diào)用父類ContainerBase#startInternal的方法
父類ContainerBase#startInternal的方法
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null;
getLogger();
//獲取集群模式下進(jìn)行啟動(dòng)
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
//啟動(dòng)Realm 認(rèn)證用戶的作用
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// 找到所有的子類也就是StanardHost
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
//通過(guò)線程池異步啟動(dòng)子類Host Context Wrapper
/**
* StartChild里邊找到所有子類也就是Host去啟動(dòng),調(diào)用StandardHost中的startInternal方法啟動(dòng)之后在調(diào)用父類的
* startInternal,進(jìn)一步找子類進(jìn)行啟動(dòng)一直找到所有的Servlet被啟動(dòng)
*/
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
MultiThrowable multiThrowable = null;
//對(duì)啟動(dòng)的結(jié)果進(jìn)行判斷
for (Future<Void> result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
//設(shè)置啟動(dòng)狀態(tài)
setState(LifecycleState.STARTING);
// Start our thread
threadStart();
}
②connector.start
調(diào)用的測(cè)試Connector#startInternal方法
protected void startInternal() throws LifecycleException {
// Validate settings before starting
if (getPort() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
}
setState(LifecycleState.STARTING);
try {
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
調(diào)用AbstractProtocol的start方法
public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
}
//啟動(dòng)endpoint
endpoint.start();
// Start timeout thread
asyncTimeout = new AsyncTimeout();
Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
int priority = endpoint.getThreadPriority();
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
priority = Thread.NORM_PRIORITY;
}
timeoutThread.setPriority(priority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}
endpoint.start默認(rèn)調(diào)用的是NioEndpoint#startInternal方法
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// Create worker collection
if ( getExecutor() == null ) {
createExecutor();
}
initializeConnectionLatch();
// Start poller threads PollerThread是個(gè)實(shí)現(xiàn)了Runnable的線程類用來(lái)處理過(guò)來(lái)的用戶請(qǐng)求
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
//開(kāi)啟NIO模式線程監(jiān)聽(tīng)
startAcceptorThreads();
}
}
總結(jié) :?jiǎn)?dòng)流程和加載流程模式差不多,只不過(guò)是設(shè)置了必要的啟動(dòng)的一些參數(shù)。
4.3Tomcat請(qǐng)求流程分析
Tomcat請(qǐng)求處理流程。 瀏覽器請(qǐng)求
Tomcat中采用Mapper組件來(lái)映射Engine和Host之間的關(guān)系。
4.3.1Mapper組件
在Mapper類中有四個(gè)內(nèi)部類,
MapElement作為基類其中只包含name、Object屬性供其他三個(gè)類使用。整個(gè)Mapper組件就是起了一個(gè)映射的作用,能夠通過(guò)Engine層層往下查找。
4.3.2請(qǐng)求處理流程
4.3.3準(zhǔn)備
創(chuàng)建一個(gè)helloworldWeb項(xiàng)目pom中引入servlet并編寫(xiě)Hello然后將編譯好的項(xiàng)目復(fù)制到源碼中。進(jìn)行啟動(dòng)并訪問(wèn)。
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().write("<p1>hello web!</p1>");
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().write("<p1>hello web!</p1>");
}
}
放入到源碼工程中。
啟動(dòng)源碼的tomcat訪問(wèn)是否能夠看到自己的helloWorld程序
4.3.4請(qǐng)求流程源碼分析
1、入口
在NioEndpoint的startInternal方法中,調(diào)用父類startAcceptorThreads開(kāi)啟了線程監(jiān)聽(tīng)。
父類AbstractEndpoint#startAcceptorThreads 方法
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
//創(chuàng)建一個(gè)socket監(jiān)聽(tīng)器
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());
t.start();
}
}
NioEndpoint的createAcceptor方法
protected AbstractEndpoint.Acceptor createAcceptor() {
return new Acceptor();
}
Acceptor繼承圖
可以看出來(lái)Acceptor屬于一個(gè)線程類,在父類的startAcceptorThreads方法中最后調(diào)用了start直接會(huì)執(zhí)行Acceptor中的run方法。
Acceptor#run方法
public void run() {
int errorDelay = 0;
// 一直循環(huán)一直到我們關(guān)閉容器會(huì)停止。
while (running) {
...
try {
//如果到達(dá)了請(qǐng)求連接數(shù)就需要等待。
countUpOrAwaitConnection();
SocketChannel socket = null;
try {
// socket 接收Socket請(qǐng)求
socket = serverSock.accept();
} catch (IOException ioe) {
...
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (running && !paused) {
// setSocketOptions() will hand the socket off to
// 將獲取到請(qǐng)求塞入到當(dāng)前隊(duì)列中 返回false為失敗 會(huì)進(jìn)行重試。
if (!setSocketOptions(socket)) {
//close本次socket
closeSocket(socket);
}
}
...
}
state = AcceptorState.ENDED;
}
setSocketOptions方法
protected boolean setSocketOptions(SocketChannel socket) {
// Process the connection
try {
//設(shè)置堵塞為flase 因?yàn)槭峭椒亲枞腘IO
socket.configureBlocking(false);
Socket sock = socket.socket();
socketProperties.setProperties(sock);
//從SynchronizedStack nioChannels獲取到一個(gè)nioChannel
NioChannel channel = nioChannels.pop();
//channel為null的時(shí)候從新創(chuàng)建一個(gè)channel
if (channel == null) {
SocketBufferHandler bufhandler = new SocketBufferHandler(
socketProperties.getAppReadBufSize(),
socketProperties.getAppWriteBufSize(),
socketProperties.getDirectBuffer());
if (isSSLEnabled()) {
channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);
} else {
channel = new NioChannel(socket, bufhandler);
}
} else {
//將socket放入到ioChannel
channel.setIOChannel(socket);
channel.reset();//重置順序
}
//從poller吃中獲取到一個(gè)Poller 并將channel進(jìn)行通知
getPoller0().register(channel);
} catch (Throwable t) {
...
return false;
}
return true;
}
public void register(final NioChannel socket) {
socket.setPoller(this);
NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this);
socket.setSocketWrapper(ka);
ka.setPoller(this);
ka.setReadTimeout(getSocketProperties().getSoTimeout());
ka.setWriteTimeout(getSocketProperties().getSoTimeout());
ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
ka.setSecure(isSSLEnabled());
ka.setReadTimeout(getConnectionTimeout());
ka.setWriteTimeout(getConnectionTimeout());
PollerEvent r = eventCache.pop();
ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
else r.reset(socket,ka,OP_REGISTER);
//上邊是包裝一些時(shí)間,這一步添加到事件中
addEvent(r);
}
private final SynchronizedQueue<PollerEvent> events =
new SynchronizedQueue<>();
//添加到事件中
private void addEvent(PollerEvent event) {
events.offer(event);
if ( wakeupCounter.incrementAndGet() == 0 ) selector.wakeup();
}
Poller的run方法
一直在循環(huán)去檢查有沒(méi)有事件
public void run() {
// Loop until destroy() is called
while (true) {
try {
if (!close) {
hasEvents = events();
...
}
if (close) {
...
}
} catch (Throwable x) {
...
continue;
}
//判斷是否有事件 events是查看隊(duì)列中是否存在事件
if ( keyCount == 0 ) hasEvents = (hasEvents | events());
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
// Walk through the collection of ready keys and dispatch
// any active event.
while (iterator != null && iterator.hasNext()) {
if (attachment == null) {
iterator.remove();
} else {
iterator.remove();
//真正開(kāi)始執(zhí)行的邏輯
processKey(sk, attachment);
}
}
}
}
2、從Endpoin查找到Servlet
從是processKey開(kāi)始分析。因?yàn)榇a比較繁瑣直接采用時(shí)序圖來(lái)分析。
總結(jié):Tomcat在執(zhí)行的過(guò)程中運(yùn)用了比較多的模版設(shè)計(jì)模式,方便拓展,也一層一層的往下去委派給下一層,其中最為核心的就是在Mapper組件11-12步將請(qǐng)求參數(shù)解析成Request對(duì)象,并且找好了對(duì)應(yīng)的Engine、Host、Context、Wrapper。有空可以在多多看看源碼學(xué)習(xí)人家的設(shè)計(jì)思路。
5、Tomcat類加載機(jī)制
5.1Jvm類加載機(jī)制回顧
什么是類加載機(jī)制?通俗的說(shuō)將編譯好的class文件加載到j(luò)vm內(nèi)存中的這個(gè)過(guò)程稱之為類加載過(guò)程。這個(gè)機(jī)制就是類加載機(jī)制。執(zhí)行這個(gè)操作的稱之為類加載器(Class Loader)。
5.1.1Jvm內(nèi)置的類加載器
jvm內(nèi)置的類加載器有:引導(dǎo)類加載器、擴(kuò)展類加載器、系統(tǒng)類加載器。
類加載器作用引導(dǎo)啟動(dòng)類加載器BootstrapClassLoaderc++編寫(xiě),加載java核?庫(kù) java.,?如rt.jar中的類,構(gòu)造ExtClassLoader和AppClassLoader擴(kuò)展類加載器 ExtClassLoaderjava編寫(xiě),加載擴(kuò)展庫(kù) JAVA_HOME/lib/ext?錄下的jar中的類,如classpath中的jre ,javax.或者java.ext.dir指定位置中的類系統(tǒng)類加載器SystemClassLoader/AppClassLoader默認(rèn)的類加載器,搜索環(huán)境變量 classpath 中指明的路徑
當(dāng)然用戶也可以自定義類加載器,直接加載指定路徑下的class文件。
1) ?戶??的類加載器,把加載請(qǐng)求傳給?加載器,?加載器再傳給其?加載器,?直到加載器樹(shù)的頂層。
2)最頂層的類加載器?先針對(duì)其特定的位置加載,如果加載不到就轉(zhuǎn)交給?類。
3)如果?直到底層的類加載都沒(méi)有加載到,那么就會(huì)拋出異常 ClassNotFoundException。
5.1.2雙親委派機(jī)制
什么是雙親委派機(jī)制?
說(shuō)白類就是在加載某個(gè)類時(shí),類加載器?先把這個(gè)任務(wù)委托給他的上級(jí)類加載器,遞歸這個(gè)操作,一直到最頂層,如果上級(jí)的類加載器沒(méi)有加載,??才會(huì)去加載這個(gè)類。
雙親委派機(jī)制的作用?
1)防?重復(fù)加載同?個(gè).class。通過(guò)委托去向上?問(wèn)?問(wèn),加載過(guò)了,就不?再加載?遍。保證數(shù)據(jù)安全。
2)保證核?.class文件【一般指的是jdk自定的】不能被篡改。通過(guò)委托?式,不會(huì)去篡改核?.class,即使篡改也不會(huì)去加載,即使加載也不會(huì)是同?個(gè).class對(duì)象了。不同的加載器加載同?個(gè).class也不是同?個(gè).class對(duì)象。這樣保證了class執(zhí)?安全(如果?類加載器先加載,那么我們可以寫(xiě)?些與java.lang包中基礎(chǔ)類同名
的類, 然后再定義?個(gè)?類加載器,這樣整個(gè)應(yīng)?使?的基礎(chǔ)類就都變成我們??定義的類了。)
5.2Tomcat類加載機(jī)制
5.2.1Tomcat為啥自定義實(shí)現(xiàn)類加載
Tomcat自定義實(shí)現(xiàn)了類加載器,并未嚴(yán)格按照雙親委派模式的方式進(jìn)行加載。那么思考下如果Tomcat嚴(yán)格按照雙親委派機(jī)制加載類的話會(huì)有啥問(wèn)題?
比如 Tomcat中引入了兩個(gè)項(xiàng)目只有版本不同比如,hellowolrd-1.0.jar 和 helloworld-2.0.jar那么兩個(gè)項(xiàng)目中都存在com.demo.ClassA,兩個(gè)類的因?yàn)榘姹静煌詢?nèi)容可能不同,如果采取雙親委派機(jī)制使用SystemClassLoader的話加載1.0版本之后在去加載2.0版本的時(shí)候發(fā)現(xiàn)已經(jīng)加載過(guò)了,就不會(huì)進(jìn)行加載了這樣子就會(huì)有問(wèn)題。
5.2.2Tomcat類加載器繼承體系
說(shuō)明:
1)其中引導(dǎo)類加載器和擴(kuò)展類加載器作用不變
2)系統(tǒng)類加載器正常情況下加載的是 CLASSPATH 下的類,但是 Tomcat 的啟動(dòng)腳本并未使?該變量,?是通過(guò)系統(tǒng)類加載器加載tomcat啟動(dòng)的類,?如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于bin目錄下。
3)Common 通?類加載器加載Tomcat依賴的jar包以及應(yīng)?通?的?些類,位于lib目錄下,?如servlet-api.jar
4)Catalina ClassLoader ?于加載服務(wù)器內(nèi)部可?類,這些類應(yīng)?程序不能訪問(wèn)。就是編寫(xiě)Tomcat的類。
5)Shared ClassLoader ?于加載應(yīng)?程序共享類,這些類服務(wù)器不會(huì)依賴。
6)Webapp ClassLoader,給每個(gè)應(yīng)?程序都會(huì)配置?個(gè)獨(dú)???的Webapp ClassLoader,他?來(lái)單獨(dú)加載本應(yīng)?程序 /WEB-INF/classes 和 /WEB-INF/lib 下的類。
Tomcat加載部署的項(xiàng)目加載順序
第一步:從 Bootstrap Classloader加載指定的類。
第二步:如果未加載到,則從 /WEB-INF/classes加載
第三步:如果未加載到,則從 /WEB-INF/lib/*.jar 加載
第四步:如果未加載到,則依次從 System、Common、Shared 加載(在這最后?步,遵從雙親委派機(jī)制)
6、Tomcat對(duì)Https的支持
6.1Https簡(jiǎn)介
百度百科給出的https解釋,
簡(jiǎn)單來(lái)說(shuō),http屬于超文本傳輸協(xié)議,是明文傳輸?shù)?,傳輸不安全,https在傳輸數(shù)據(jù)的時(shí)候會(huì)對(duì)數(shù)據(jù)進(jìn)行一層加密。采用加密的協(xié)議為SSL(Secure Socket Layer)?,F(xiàn)在也衍生出了TLS(Transport Layer Security)協(xié)議。
Http和Https主要的區(qū)別?
- HTTPS協(xié)議使?時(shí)需要到電?商務(wù)認(rèn)證授權(quán)機(jī)構(gòu)(CA)申請(qǐng)SSL證書(shū)
- HTTP默認(rèn)使?8080端?,HTTPS默認(rèn)使?(8)443端?
- HTTPS則是具有SSL加密的安全性傳輸協(xié)議,對(duì)數(shù)據(jù)的傳輸進(jìn)?加密,效果上相當(dāng)于HTTP的升級(jí)版
- HTTP的連接是?狀態(tài)的,不安全的;HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進(jìn)?加密傳輸、身份認(rèn)證的?絡(luò)協(xié)議,?HTTP協(xié)議安全
6.2Https工作原理
簡(jiǎn)單來(lái)說(shuō),第一次握手會(huì)找到適合當(dāng)前瀏覽器的加密算法,并返回一個(gè)公鑰給瀏覽器;第二次握手瀏覽器先校驗(yàn)公鑰證書(shū),然后生成一串隨機(jī)數(shù) 拿著公鑰進(jìn)行加密,第三握手服務(wù)器通過(guò)私鑰進(jìn)行解密,并拿著私鑰在進(jìn)行加密返回給客戶端,客戶端拿著服務(wù)器返回的信息進(jìn)行hash比對(duì),如果統(tǒng)一的話就進(jìn)行數(shù)據(jù)傳輸。
6.3Tomcat配置Https
使用jdk中的keytool生成一個(gè)自定義的證書(shū)(商用的話找準(zhǔn)認(rèn)證機(jī)構(gòu))
打開(kāi)終端或者Command輸入下邊命令。
keytool -genkey -alias helloworld -keyalg RSA -keystore helloworld.keystore
在conf/server.xml中配置
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" schema="https" secure="true" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="D:/helloworld.keystore" <!--自己的證書(shū)的目錄-->
certificateKeystorePassword="helloworld" type="RSA"/>
</SSLHostConfig>
</Connector>
啟動(dòng)Tomcat訪問(wèn)https:localhost:8443即可訪問(wèn)。
7、Tomcat性能優(yōu)化
調(diào)優(yōu)只提供思路,不同配置調(diào)優(yōu)的數(shù)值是不固定的。
了解一些基礎(chǔ)概念:
1)響應(yīng)時(shí)間:執(zhí)?某個(gè)操作的耗時(shí);
2)吞吐量:系統(tǒng)在給定時(shí)間內(nèi)能夠?持的事務(wù)數(shù)量,單位為T(mén)PS(Transactions PerSecond的縮寫(xiě), 也就是事務(wù)數(shù)/秒,?個(gè)事務(wù)是指?個(gè)客戶機(jī)向服務(wù)器發(fā)送請(qǐng)求然后服務(wù)器做出反應(yīng)的過(guò)程。
7.1Jvm性能調(diào)優(yōu)
Java 虛擬機(jī)的運(yùn)?優(yōu)化主要是內(nèi)存分配和垃圾回收策略的優(yōu)化:
1、內(nèi)存直接影響服務(wù)的運(yùn)?效率和吞吐量
2、垃圾回收機(jī)制會(huì)不同程度地導(dǎo)致程序運(yùn)?中斷(垃圾回收策略不同,垃圾回收次數(shù)和回收效率都是不同的)
1、Java 虛擬機(jī)內(nèi)存相關(guān)參數(shù)
參數(shù)參數(shù)作?優(yōu)化建議-server啟動(dòng)Server,以服務(wù)端模式運(yùn)?服務(wù)端模式建議開(kāi)啟-Xms最?堆內(nèi)存建議與-Xmx設(shè)置相同-Xmx最?堆內(nèi)存建議設(shè)置為可?內(nèi)存的80%-XX:MetaspaceSize元空間初始值一般不會(huì)修改-XX:MaxMetaspaceSize元空間最?內(nèi)存默認(rèn)?限-XX:NewRatio年輕代和?年代???值,取值為整數(shù),默認(rèn)為2[2:1]不需要修改-XX:SurvivorRatioEden區(qū)與Survivor區(qū)??的?值,取值為整數(shù),默認(rèn)為8[8:1:1]不需要修改
上圖為CMS和G1的JVM內(nèi)存空間分配。
調(diào)整Demo:
JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
直接配置到bin/catalina.bat或者catalina.sh目錄下即可。
查看:使用jdk自帶的工具查看
jhsdb jmap -heap -pid xxx【jdk9 之后使用jhsdb】jmap -heap <pid> 打印堆的使用情況
2、垃圾回收策略
垃圾回收性能指標(biāo)
1、吞吐量:?作時(shí)間(排除GC時(shí)間)占總時(shí)間的百分?, ?作時(shí)間并不僅是程序運(yùn)?的時(shí)間,還包含內(nèi)存分配時(shí)間。
2、暫停時(shí)間:由垃圾回收導(dǎo)致的應(yīng)?程序停?響應(yīng)次數(shù)/時(shí)間
垃圾回收器
1、串?收集器(Serial Collector):?jiǎn)尉€程執(zhí)?所有的垃圾回收?作, 適?于單核CPU服務(wù)器
?作進(jìn)程-----|(單線程)垃圾回收線程進(jìn)?垃圾收集|---?作進(jìn)程繼續(xù)
2、并?收集器(Parallel Collector):?稱為吞吐量收集器(關(guān)注吞吐量), 以并?的?式執(zhí)?年輕代的垃圾回收, 該?式可以顯著降低垃圾回收的開(kāi)銷(指多條垃圾收集線程并??作,但此時(shí)?戶線程仍然處于等待狀態(tài))。適?于多處理器或多線程硬件上運(yùn)?的數(shù)據(jù)量較?的應(yīng)?。
?作進(jìn)程-----|(多線程)垃圾回收線程進(jìn)?垃圾收集|---?作進(jìn)程繼續(xù)
3、并發(fā)收集器(Concurrent Collector):以并發(fā)的?式執(zhí)??部分垃圾回收?作,以縮短垃圾回收的暫停時(shí)間。適?于那些響應(yīng)時(shí)間優(yōu)先于吞吐量的應(yīng)?, 因?yàn)樵撌占麟m然最?化了暫停時(shí)間(指?戶線程與垃圾收集線程同時(shí)執(zhí)?,但不?定是并?的,可能會(huì)交替進(jìn)?), 但是會(huì)降低應(yīng)?程序的性能。
4、CMS收集器(Concurrent Mark Sweep Collector):并發(fā)標(biāo)記清除收集器, 適?于那些更愿意縮短垃圾回收暫停時(shí)間并且負(fù)擔(dān)的起與垃圾回收共享處理器資源的應(yīng)?。
5、G1收集器(Garbage-First Garbage Collector):適?于?容量?jī)?nèi)存的多核服務(wù)器, 可以在滿?垃圾回收暫停時(shí)間?標(biāo)的同時(shí), 以最?可能性實(shí)現(xiàn)高吞吐量(JDK1.7之后)。
配置啟動(dòng)參數(shù)
參數(shù)描述-XX:+UseSerialGC啟?串?收集器-XX:+UseParallelGC啟?并?垃圾收集器,配置了該選項(xiàng),那么 -XX:+UseParallelOldGC默認(rèn)啟?。-XX:+UseParNewGC年輕代采?并?收集器,如果設(shè)置了 -XX:+UseConcMarkSweepGC選項(xiàng),?動(dòng)啟?。-XX:ParallelGCThreads年輕代及?年代垃圾回收使?的線程數(shù)。默認(rèn)值依賴于JVM使?的CPU個(gè)數(shù)。-XX:+UseConcMarkSweepGC(CMS)對(duì)于?年代,啟?CMS垃圾收集器。 當(dāng)并?收集器?法滿?應(yīng)?的延遲需求是,推薦使?CMS或G1收集器。啟?該選項(xiàng)后, -XX:+UseParNewGC?動(dòng)啟?。-XX:+UseG1GC?G1收集器。 G1是服務(wù)器類型的收集器, ?于多核、?內(nèi)存的機(jī)器。它在保持?吞吐量的情況下,?概率滿?GC暫停時(shí)間的?標(biāo)。
在bin/catalina.sh的腳本中 , 追加如下配置 :
JAVA_OPTS="-XX:+UseConcMarkSweepGC"
可以通過(guò)jconsle工具的配置概況查看todo。
7.2Tomcat本身配置調(diào)優(yōu)
1、調(diào)整tomcat線程池
2、調(diào)整tomcat的連接器
調(diào)整tomcat/conf/server.xml 中關(guān)于鏈接器的配置可以提升應(yīng)?服務(wù)器的性能。
參數(shù)說(shuō)明maxConnections最?連接數(shù),當(dāng)?shù)竭_(dá)該值后,服務(wù)器接收但不會(huì)處理更多的請(qǐng)求, 額外的請(qǐng)求將會(huì)阻塞直到連接數(shù)低于maxConnections 。可通過(guò)ulimit -a 查看服務(wù)器限制。對(duì)于CPU要求更?(計(jì)算密集型)時(shí),建議不要配置過(guò)? ; 對(duì)于CPU要求不是特別?時(shí),建議配置在2000左右(受服務(wù)器性能影響)。 當(dāng)然這個(gè)需要服務(wù)器硬件的?持maxThreads最?線程數(shù),需要根據(jù)服務(wù)器的硬件情況,進(jìn)??個(gè)合理的設(shè)置acceptCount最?排隊(duì)等待數(shù),當(dāng)服務(wù)器接收的請(qǐng)求數(shù)量到達(dá)maxConnections ,此時(shí)Tomcat會(huì)將后?的請(qǐng)求,存放在任務(wù)隊(duì)列中進(jìn)?排序, acceptCount指的就是任務(wù)隊(duì)列中排隊(duì)等待的請(qǐng)求數(shù) 。 ?臺(tái)Tomcat的最?的請(qǐng)求處理數(shù)量,是maxConnections+acceptCount
3、禁? AJP 連接器
4、調(diào)整IO模型
Tomcat8之前的版本默認(rèn)使?BIO(阻塞式IO),對(duì)于每?個(gè)請(qǐng)求都要?jiǎng)?chuàng)建?個(gè)線程來(lái)處理,不適
合?并發(fā);Tomcat8以后的版本默認(rèn)使?NIO模式(?阻塞式IO)
當(dāng)Tomcat并發(fā)性能有較?要求或者出現(xiàn)瓶頸時(shí),我們可以嘗試使?APR模式,APR(Apache Portable
Runtime)是從操作系統(tǒng)級(jí)別解決異步IO問(wèn)題,使?時(shí)需要在操作系統(tǒng)上安裝APR和Native(因?yàn)锳PR
原理是使?使?JNI技術(shù)調(diào)?操作系統(tǒng)底層的IO接?)。
5、動(dòng)靜分離
可以使?Nginx+Tomcat相結(jié)合的部署?案,Nginx負(fù)責(zé)靜態(tài)資源訪問(wèn),Tomcat負(fù)責(zé)Jsp等動(dòng)態(tài)資
源訪問(wèn)處理(因?yàn)門(mén)omcat不擅?處理靜態(tài)資源)。
</article>