Tomcat&Nginx源码笔记分析

tech2023-05-30  87

Tomcat

1.访问执行流程

2.tomcat的执行流程

Tomcat的两个重要身份 1)http服务器 2)Tomcat是⼀个Servlet容器

3.tomcat的容器执行流程

当⽤户请求某个URL资源时

1)HTTP服务器会把请求信息使⽤ServletRequest对象封装起来

2)进⼀步去调⽤Servlet容器中某个具体的Servlet

3)在 2)中,Servlet容器拿到请求后,根据URL和Servlet的映射关系,找到相应的Servlet

4)如果Servlet还没有被加载,就⽤反射机制创建这个Servlet,并调⽤Servlet的init⽅法来完成初始化

5)接着调⽤这个具体Servlet的service⽅法来处理请求,请求处理结果使⽤ServletResponse对象封装

6)把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端

4.tomcat的结构图

5.tomcat两大功能

1.连接器(Coyote)connector

1.运行流程

负责对外交流: 处理Socket连接,负责⽹络字节流与Request和Response对象的转化

(1)Coyote 封装了底层的⽹络通信(Socket 请求及响应处理)

(2)Coyote 使Catalina 容器(容器组件)与具体的请求协议及IO操作⽅式完全解耦

(3)Coyote 将Socket 输⼊转换封装为 Request 对象,进⼀步封装后交由Catalina 容器进⾏处理,处

理请求完成后, Catalina 通过Coyote 提供的Response 对象将结果写⼊输出流

(4)Coyote 负责的是具体协议(应⽤层)和IO(传输层)相关内容

2.组件

EndPoint EndPoint 是 Coyote 通信端点,即通信监听的接⼝,是具体Socket接收和发送处理器,是对传输层的抽象,因此EndPoint⽤来实现TCP/IP协议的

Processor Processor⽤来实现HTTP协议,Processor接收来⾃EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,Processor是对应⽤层协议的抽象

ProtocolHandler Coyote 协议接⼝, 通过Endpoint 和 Processor , 实现针对具体协议的处理能⼒

Adapter CoyoteAdapter负责将Tomcat Request转成ServletRequest,再调⽤容器

2.容器组件container

负责内部处理:加载和管理Servlet,以及具体处理Request请求;

6.server.xml

1.每⼀个Service实例下可以有多个Connector实例和⼀个Container实例

<?xml version="1.0" encoding="UTF-8"?> <!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!-- Note: A "Server" is not itself a "Container", so you may not define subcomponents such as "Valves" at this level. Documentation at /docs/config/server.html --> <!-- Server 根元素,创建⼀个Server实例,⼦标签有 Listener、GlobalNamingResources、 Service --> <!-- port:关闭服务器的监听端⼝ shutdown:关闭服务器的指令字符串 --> <Server port="8005" shutdown="SHUTDOWN"> <!-- 以⽇志形式输出服务器 、操作系统、JVM的版本信息 --> <Listener className="org.apache.catalina.startup.VersionLoggerListener" /> <!-- Security listener. Documentation at /docs/config/listeners.html <Listener className="org.apache.catalina.security.SecurityListener" /> --> <!--APR library loader. Documentation at /docs/apr.html --> <!-- 加载(服务器启动) 和 销毁 (服务器停⽌) APR。 如果找不到APR库, 则会输出⽇志, 并 不影响 Tomcat启动 --> <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> <!-- Prevent memory leaks due to use of particular java/javax APIs--> <!-- 避免JRE内存泄漏问题 --> <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /> <!-- 加载(服务器启动) 和 销毁(服务器停⽌) 全局命名服务 --> <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> <!-- 在Context停⽌时重建 Executor 池中的线程, 以避免ThreadLocal 相关的内存泄漏 --> <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> <!-- Global JNDI resources Documentation at /docs/jndi-resources-howto.html --> <!--定义服务器的全局JNDI资源 --> <GlobalNamingResources> <!-- Editable user database that can also be used by UserDatabaseRealm to authenticate users --> <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" /> </GlobalNamingResources> <!-- A "Service" is a collection of one or more "Connectors" that share a single "Container" Note: A "Service" is not itself a "Container", so you may not define subcomponents such as "Valves" at this level. Documentation at /docs/config/service.html --> <!-- 该标签⽤于创建 Service 实例,默认使⽤ org.apache.catalina.core.StandardService。 默认情况下,Tomcat 仅指定了Service 的名称, 值为 "Catalina"。 Service ⼦标签为 : Listener、Executor、Connector、Engine, 其中: Listener ⽤于为Service添加⽣命周期监听器, Executor ⽤于配置Service 共享线程池, Connector ⽤于配置Service 包含的链接器, Engine ⽤于配置Service中链接器对应的Servlet 容器引擎 --> <Service name="Catalina"> <!--The connectors can use a shared executor, you can define one or more named thread pools--> <!-- <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/> --> <!-- 默认情况下,Service 并未添加共享线程池配置。 如果我们想添加⼀个线程池, 可以在<Service> 下添加如下配置: name:线程池名称,⽤于 Connector中指定 namePrefix:所创建的每个线程的名称前缀,⼀个单独的线程名称为 namePrefix+threadNumber maxThreads:池中最⼤线程数 minSpareThreads:活跃线程数,也就是核⼼池线程数,这些线程不会被销毁,会⼀直存在 maxIdleTime:线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为6000(1分钟),单位毫秒 maxQueueSize:在被执⾏前最⼤线程排队数⽬,默认为Int的最⼤值,也就是⼴义的⽆限。除⾮特 殊情况,这个值 不需要更改,否则会有请求不会被处理的情况发⽣ prestartminSpareThreads:启动线程池时是否启动 minSpareThreads部分线程。默认值为false,即不启动 threadPriority:线程池中线程优先级,默认值为5,值从1到10 className:线程池实现类,未指定情况下,默认实现类为 org.apache.catalina.core.StandardThreadExecutor。如果想使⽤⾃定义线程池⾸先需要实现org.apache.catalina.Executor接⼝ --> <Executor name="commonThreadPool" namePrefix="thread-exec-" maxThreads="200" minSpareThreads="100" maxIdleTime="60000" maxQueueSize="Integer.MAX_VALUE" prestartminSpareThreads="false" threadPriority="5" className="org.apache.catalina.core.StandardThreadExecutor"/> <!-- port: 端⼝号,Connector ⽤于创建服务端Socket 并进⾏监听, 以等待客户端请求链接。如果该属性设置 为0, Tomcat将会随机选择⼀个可⽤的端⼝号给当前Connector 使⽤ protocol: 当前Connector ⽀持的访问协议。 默认为 HTTP/1.1 , 并采⽤⾃动切换机制选择⼀个基于 JAVA NIO 的链接器或者基于本地APR的链接器(根据本地是否含有Tomcat的本地库判定) connectionTimeOut: Connector 接收链接后的等待超时时间, 单位为 毫秒。 -1 表示不超时。 redirectPort: 当前Connector 不⽀持SSL请求, 接收到了⼀个请求, 并且也符合security-constraint 约束, 需要SSL传输,Catalina⾃动将请求重定向到指定的端⼝。 executor: 指定共享线程池的名称, 也可以通过maxThreads、minSpareThreads 等属性配置内部线程池。 URIEncoding: ⽤于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 , Tomcat7.x版本默认为ISO- 8859-1 --> <!--org.apache.coyote.http11.Http11NioProtocol , ⾮阻塞式 Java NIO 链接器--> <!-- A "Connector" represents an endpoint by which requests are received and responses are returned. Documentation at : Java HTTP Connector: /docs/config/http.html Java AJP Connector: /docs/config/ajp.html APR (HTTP/AJP) Connector: /docs/apr.html Define a non-SSL/TLS HTTP/1.1 Connector on port 8080 --> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <!-- A "Connector" using the shared thread pool--> <!-- <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> --> <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 This connector uses the NIO implementation. The default SSLImplementation will depend on the presence of the APR/native library and the useOpenSSL attribute of the AprLifecycleListener. Either JSSE or OpenSSL style configuration may be used regardless of the SSLImplementation selected. JSSE style configuration is used below. --> <!-- <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" SSLEnabled="true"> <SSLHostConfig> <Certificate certificateKeystoreFile="conf/localhost-rsa.jks" type="RSA" /> </SSLHostConfig> </Connector> --> <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2 This connector uses the APR/native implementation which always uses OpenSSL for TLS. Either JSSE or OpenSSL style configuration may be used. OpenSSL style configuration is used below. --> <!-- <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol" maxThreads="150" SSLEnabled="true" > <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" /> <SSLHostConfig> <Certificate certificateKeyFile="conf/localhost-rsa-key.pem" certificateFile="conf/localhost-rsa-cert.pem" certificateChainFile="conf/localhost-rsa-chain.pem" type="RSA" /> </SSLHostConfig> </Connector> --> <!-- Define an AJP 1.3 Connector on port 8009 --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> <!-- An Engine represents the entry point (within Catalina) that processes every request. The Engine implementation for Tomcat stand alone analyzes the HTTP headers included with the request, and passes them on to the appropriate Host (virtual host). Documentation at /docs/config/engine.html --> <!-- You should set jvmRoute to support load-balancing via AJP ie : <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1"> --> <!-- name: ⽤于指定Engine 的名称, 默认为Catalina defaultHost:默认使⽤的虚拟主机名称, 当客户端请求指向的主机⽆效时, 将交由默认的虚拟主机处 理, 默认为localhost --> <Engine name="Catalina" defaultHost="localhost"> <!--For clustering, please take a look at documentation at: /docs/cluster-howto.html (simple how to) /docs/config/cluster.html (reference documentation) --> <!-- <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/> --> <!-- Use the LockOutRealm to prevent attempts to guess user passwords via a brute-force attack --> <Realm className="org.apache.catalina.realm.LockOutRealm"> <!-- This Realm uses the UserDatabase configured in the global JNDI resources under the key "UserDatabase". Any edits that are performed against this UserDatabase are immediately available for use by the Realm. --> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <!--Host 标签⽤于配置⼀个虚拟主机 --> <Host name="www.2.com" appBase="webapps" unpackWARs="true" autoDeploy="true"> <!-- SingleSignOn valve, share authentication between web applications Documentation at: /docs/config/valve.html --> <!-- <Valve className="org.apache.catalina.authenticator.SingleSignOn" /> --> <!-- docBase:Web应⽤⽬录或者War包的部署路径。可以是绝对路径,也可以是相对于 Host appBase的 相对路径。 path:Web应⽤的Context 路径。如果我们Host名为localhost, 则该web应⽤访问的根路径为: http://localhost:8080/web_demo。 --> <Context docBase="/Users/yingdian/web_demo" path="/web3"></Context> <!-- Access log processes all example. Documentation at: /docs/config/valve.html Note: The pattern used is equivalent to using pattern="common" --> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" /> </Host> <Host name="www.1.com" appBase="webapps" unpackWARs="true" autoDeploy="true"> <!-- SingleSignOn valve, share authentication between web applications Documentation at: /docs/config/valve.html --> <!-- <Valve className="org.apache.catalina.authenticator.SingleSignOn" /> --> <!-- docBase:Web应⽤⽬录或者War包的部署路径。可以是绝对路径,也可以是相对于 Host appBase的 相对路径。 path:Web应⽤的Context 路径。如果我们Host名为localhost, 则该web应⽤访问的根路径为: http://localhost:8080/web_demo。 --> <!-- Access log processes all example. Documentation at: /docs/config/valve.html Note: The pattern used is equivalent to using pattern="common" --> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" /> </Host> </Engine> </Service> </Server>

2.自定义tomcat

2.1 执行流程图

2.2 执行流程

以ServerSocket为核心,导向

ServerSocket serverSocket = new ServerSocket(port);//8080 Socket socket = serverSocket.accept(); socket.close();

1.加载配置文件

读取web.xml文件

key 为 /lagou (url-pattern路径)

value是 实例化后的servlet(LagouServlet)实例 存储到Map集合中

<?xml version="1.0" encoding="UTF-8" ?> <web-app> <servlet> <servlet-name>lagou</servlet-name> <servlet-class>server.LagouServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>lagou</servlet-name> <url-pattern>/lagou</url-pattern> </servlet-mapping> </web-app> private void loadServlet() { InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml"); SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(resourceAsStream); Element rootElement = document.getRootElement(); //获取节点是servlet的标签 List<Element> selectNodes = rootElement.selectNodes("//servlet"); for (int i = 0; i < selectNodes.size(); i++) { Element element = selectNodes.get(i); // <servlet-name>lagou</servlet-name> Element servletnameElement = (Element) element.selectSingleNode("servlet-name"); String servletName = servletnameElement.getStringValue(); // <servlet-class>server.LagouServlet</servlet-class> Element servletclassElement = (Element) element.selectSingleNode("servlet-class"); String servletClass = servletclassElement.getStringValue(); // 根据servlet-name的值找到url-pattern Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']"); // /lagou String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue(); servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance()); } } catch (DocumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }

2. 定义线程池

(RequestProcessor)

// 定义一个线程池 int corePoolSize = 10; int maximumPoolSize =50; long keepAliveTime = 100L; TimeUnit unit = TimeUnit.SECONDS; BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50); ThreadFactory threadFactory = Executors.defaultThreadFactory(); RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler );

3. 请求进,调用线程池的线程

*/ while(true) { Socket socket = serverSocket.accept(); RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap); //requestProcessor.start(); threadPoolExecutor.execute(requestProcessor); } //======================================== @Override public void run() { try{ InputStream inputStream = socket.getInputStream(); // 封装Request对象和Response对象 Request request = new Request(inputStream); Response response = new Response(socket.getOutputStream()); // 静态资源处理 if(servletMap.get(request.getUrl()) == null) { response.outputHtml(request.getUrl()); }else{ // 动态资源servlet请求 HttpServlet httpServlet = servletMap.get(request.getUrl()); httpServlet.service(request,response); } socket.close(); }catch (Exception e) { e.printStackTrace(); } }

1.Request请求

将请求头封装 获取Get请求方式以及路径

// 构造器,输入流传入 public Request(InputStream inputStream) throws IOException { this.inputStream = inputStream; // 从输入流中获取请求信息 int count = 0; while (count == 0) { count = inputStream.available(); } byte[] bytes = new byte[count]; inputStream.read(bytes); /** /GET /lagou HTTP/1.1 Host: localhost:8080 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,;q=0.8,application/signed-exchange;v=b3 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9**/ String inputStr = new String(bytes); // 获取第一行请求头信息 String firstLineStr = inputStr.split("\\n")[0]; // GET / HTTP/1.1 String[] strings = firstLineStr.split(" "); this.method = strings[0]; this.url = strings[1]; System.out.println("=====>>method:" + method); System.out.println("=====>>url:" + url); }

2.Response请求

获取到绝对路径,判断File.isFile()然后输出静态文件

public class Response { private OutputStream outputStream; public Response() { } public Response(OutputStream outputStream) { this.outputStream = outputStream; } // 使用输出流输出指定字符串 public void output(String content) throws IOException { outputStream.write(content.getBytes()); } /** * * @param path url,随后要根据url来获取到静态资源的绝对路径,进一步根据绝对路径读取该静态资源文件,最终通过 * 输出流输出 * /-----> classes */ public void outputHtml(String path) throws IOException { // 获取静态资源文件的绝对路径 String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path); // 输入静态资源文件 File file = new File(absoluteResourcePath); if(file.exists() && file.isFile()) { // 读取静态资源文件,输出静态资源 StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream); }else{ // 输出404 output(HttpProtocolUtil.getHttpHeader404()); } } }

4.调用LagouServlet

package server; import java.io.IOException; public class LagouServlet extends HttpServlet { @Override public void doGet(Request request, Response response) { // try { // Thread.sleep(100000); // } catch (InterruptedException e) { // e.printStackTrace(); // } String content = "<h1>LagouServlet get</h1>"; try { response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content)); } catch (IOException e) { e.printStackTrace(); } } @Override public void doPost(Request request, Response response) { String content = "<h1>LagouServlet post</h1>"; try { response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content)); } catch (IOException e) { e.printStackTrace(); } } @Override public void init() throws Exception { } @Override public void destory() throws Exception { } }

####工具类

package server; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class StaticResourceUtil { /** * 获取静态资源文件的绝对路径 * @param path * @return */ public static String getAbsolutePath(String path) { String absolutePath = StaticResourceUtil.class.getResource("/").getPath(); return absolutePath.replaceAll("\\\\","/") + path; } /** * 读取静态资源文件输入流,通过输出流输出 */ public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException { int count = 0; while(count == 0) { count = inputStream.available(); } int resourceSize = count; // 输出http请求头,然后再输出具体内容 outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes()); // 读取内容输出 long written = 0 ;// 已经读取的内容长度 int byteSize = 1024; // 计划每次缓冲的长度 byte[] bytes = new byte[byteSize]; while(written < resourceSize) { if(written + byteSize > resourceSize) { // 说明剩余未读取大小不足一个1024长度,那就按真实长度处理 byteSize = (int) (resourceSize - written); // 剩余的文件内容长度 bytes = new byte[byteSize]; } inputStream.read(bytes); outputStream.write(bytes); outputStream.flush(); written+=byteSize; } } } package server; /** * http协议工具类,主要是提供响应头信息,这里我们只提供200和404的情况 */ public class HttpProtocolUtil { /** * 为响应码200提供请求头信息 * @return */ public static String getHttpHeader200(long contentLength) { return "HTTP/1.1 200 OK \n" + "Content-Type: text/html \n" + "Content-Length: " + contentLength + " \n" + "\r\n"; } /** * 为响应码404提供请求头信息(此处也包含了数据内容) * @return */ public static String getHttpHeader404() { String str404 = "<h1>404 not found</h1>"; return "HTTP/1.1 404 NOT Found \n" + "Content-Type: text/html \n" + "Content-Length: " + str404.getBytes().length + " \n" + "\r\n" + str404; } }

================

迭代升级

1.口述流程

项目启动bootstrap.start()

加载server.xml,读取端口port 8080以及HostName和appBase的绝对路径遍历文件夹,读取项目的web.xml文件,获取setvlet的class的类路径,以及url-pattern,实例化存储到对象中请求进来时,截取Url判断相应的servlet,执行相应的请求

2.读取server.xml,web.xml

/** * 加载解析web.xml,初始化Servlet */ private void loadConfig() { InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("server.xml"); SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(resourceAsStream); Element rootElement = document.getRootElement(); List<Element> selectNodes = rootElement.selectNodes("//Host"); for (int i = 0; i < selectNodes.size(); i++) { Host host = new Host(); Element element = selectNodes.get(i); String hostName = element.attributeValue("name"); String appBase = element.attributeValue("appBase"); // 扫描appBase下的文件夹,每一个文件夹认为是一个项目(Context) File appBaseFolder = new File(appBase); File[] files = appBaseFolder.listFiles(); for(File file: files) { if(file.isDirectory()) { Context context = new Context(); String contextPath = file.getName(); context.setPath(contextPath); // 构建Wrappers,一个Wrapper对应一个Servlet File webFile = new File(file,"web.xml"); List<Wrapper> list = loadWebXml(webFile.getAbsolutePath()); context.setWrappers(list); host.getContexts().add(context); } } host.setName(hostName); mapper.getHosts().add(host); } } catch (DocumentException | FileNotFoundException e) { e.printStackTrace(); } } /** * 解析web.xml,构建Wrappers */ public List<Wrapper> loadWebXml(String webXmlPath) throws FileNotFoundException { List<Wrapper> list = new ArrayList<>(); InputStream resourceAsStream = new FileInputStream(webXmlPath); SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(resourceAsStream); Element rootElement = document.getRootElement(); MyClassLoader myClassLoader = new MyClassLoader(); List<Element> selectNodes = rootElement.selectNodes("//servlet"); for (int i = 0; i < selectNodes.size(); i++) { Element element = selectNodes.get(i); // <servlet-name>lagou</servlet-name> Element servletnameElement = (Element) element.selectSingleNode("servlet-name"); String servletName = servletnameElement.getStringValue(); // <servlet-class>server.LagouServlet</servlet-class> Element servletclassElement = (Element) element.selectSingleNode("servlet-class"); String servletClass = servletclassElement.getStringValue(); // 根据servlet-name的值找到url-pattern Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']"); // /lagou String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue(); Class<?> aClass = myClassLoader.findClass(webXmlPath.replace("web.xml", "") + "/",servletClass); HttpServlet servlet = (HttpServlet) aClass.newInstance(); Wrapper wrapper = new Wrapper(); wrapper.setUrlPattern(urlPattern); wrapper.setServlet(servlet); list.add(wrapper); } }catch (Exception e) { e.printStackTrace(); } return list; }

3.类的实例化加载,动态加载.class

Class<?> aClass = myClassLoader.findClass(webXmlPath.replace("web.xml", "") + "/",servletClass); HttpServlet servlet = (HttpServlet) aClass.newInstance(); //======================================================= public class MyClassLoader extends ClassLoader { /** * name class 类的绝对路径 */ @Override protected Class<?> findClass(String basePath,String name) { String myPath = "file://" + basePath + name.replaceAll("\\.","/") + ".class"; System.out.println(myPath); byte[] cLassBytes = null; Path path = null; try { path = Paths.get(new URI(myPath)); cLassBytes = Files.readAllBytes(path); } catch (IOException | URISyntaxException e) { e.printStackTrace(); } Class clazz = defineClass(name, cLassBytes, 0, cLassBytes.length); return clazz; } }

4.判断URL的路径

Servlet servlet = resolveServlet(request.getUrl()); //========================= /** * 从mapper中取出需要的servlet * @return */ private Servlet resolveServlet(String url) { String[] split = url.split("/"); String contextFlag = split[1]; String servletFlag = url.substring(("/" + contextFlag).length()); // 此处认为只有这一个host List<Context> contexts = mapper.getHosts().get(0).getContexts(); for(Context context: contexts) { if(contextFlag.equalsIgnoreCase(context.getPath())) { List<Wrapper> wrappers = context.getWrappers(); for(Wrapper wrapper:wrappers) { if(wrapper.getUrlPattern().equalsIgnoreCase(servletFlag)) { return wrapper.getServlet(); } } } } return null; }

5.封装的类

5.1Host类

public class Host { private String name; private List<Context> contexts = new ArrayList<>(); public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Context> getContexts() { return contexts; } public void setContexts(List<Context> contexts) { this.contexts = contexts; } }

5.2Mapper类

public class Mapper { private List<Host> hosts = new ArrayList<>(); public List<Host> getHosts() { return hosts; } public void setHosts(List<Host> hosts) { this.hosts = hosts; } }

5.3wapper类

public class Wrapper { private String urlPattern; private Servlet servlet; public String getUrlPattern() { return urlPattern; } public void setUrlPattern(String urlPattern) { this.urlPattern = urlPattern; } public Servlet getServlet() { return servlet; } public void setServlet(Servlet servlet) { this.servlet = servlet; } }

3.jvm的类加载机制

3.1加载类

引导启动类加载器(BootstrapClassLoader) c++编写,加载java核⼼库 java.*,⽐如rt.jar中的类,构造ExtClassLoader和AppClassLoader

扩展类加载器(ExtClassLoader) java编写,加载扩展库 JAVA_HOME/lib/ext⽬录下的jar中的类,如classpath中的jre ,javax.*或者java.ext.dir指定位置中的类

系统类加载器 (SystemClassLoader/AppClassLoader)默认的类加载器,搜索环境变量 classpath 中指明的路径

3.2 双亲委派机制

3.2.1 什么是双亲委派

当某个类加载器需要加载某个.class⽂件时,它⾸先把这个任务委托给他的上级类加载器,递归这个操

作,如果上级的类加载器没有加载,⾃⼰才会去加载这个类。

3.2.2 作用

防⽌重复加载同⼀个.class。通过委托去向上⾯问⼀问,加载过了,就不⽤再加载⼀遍。保证数据安全。

2.保证核⼼.class不能被篡改。通过委托⽅式,不会去篡改核⼼.class,即使篡改也不会去加载,即使

加载也不会是同⼀个.class对象了。不同的加载器加载同⼀个.class也不是同⼀个.class对象。这样

保证了class执⾏安全(如果⼦类加载器先加载,那么我们可以写⼀些与java.lang包中基础类同名

的类, 然后再定义⼀个⼦类加载器,这样整个应⽤使⽤的基础类就都变成我们⾃⼰定义的类了。

Object类 -----> ⾃定义类加载器(会出现问题的,那么真正的Object类就可能被篡改了)

3.2.3 Tomcat 的类加载机制

tomcat 8.5 默认改变了严格的双亲委派机制

⾸先从 Bootstrap Classloader加载指定的类

如果未加载到,则从 /WEB-INF/classes加载

如果未加载到,则从 /WEB-INF/lib/*.jar 加载

如果未加载到,则依次从 System、Common、Shared 加载(在这最后⼀步,遵从双亲委派

机制)

Common 通⽤类加载器加载Tomcat使⽤以及应⽤通⽤的⼀些类,位于CATALINA_HOME/lib下,⽐如servlet-api.jar

Catalina ClassLoader ⽤于加载服务器内部可⻅类,这些类应⽤程序不能访问

Shared ClassLoader ⽤于加载应⽤程序共享类,这些类服务器不会依赖

Webapp ClassLoader,每个应⽤程序都会有⼀个独⼀⽆⼆的Webapp ClassLoader,他⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。

4.tomcat对Https支持

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" schema="https" secure="true" SSLEnabled="true"> <SSLHostConfig> <Certificate certificateKeystoreFile="/Users/yingdian/workspace/servers/apache-tomcat-8.5.50/conf/lagou.keystore" certificateKeystorePassword="lagou123" type="RSA"/> </SSLHostConfig> </Connector>

5.tocmat的优化策略

5.1Tomcat⾃身配置的优化

(⽐如是否使⽤了共享线程池?IO模型?)

5.1.1调整tomcat线程池

<Executor name="commonThreadPool" namePrefix="thread-exec-" maxThreads="200" minSpareThreads="100" maxIdleTime="60000" maxQueueSize="Integer.MAX_VALUE" prestartminSpareThreads="false" threadPriority="5" className="org.apache.catalina.core.StandardThreadExecutor"/> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" executor="commonThreadPool"/>

5.1.2禁⽤ A JP 连接器

禁用下面这段 <!-- Define an AJP 1.3 Connector on port 8009 --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

5.1.3调整 IO 模式

Tomcat8之前的版本默认使⽤BIO(阻塞式IO),对于每⼀个请求都要创建⼀个线程来处理,不适

合⾼并发;Tomcat8以后的版本默认使⽤NIO模式(⾮阻塞式IO)

protocol就是Io模型

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" executor="commonThreadPool"/>

5.1.4动静分离

可以使⽤Nginx+Tomcat相结合的部署⽅案,Nginx负责静态资源访问,Tomcat负责Jsp等动态资源访问处理(因为Tomcat不擅⻓处理静态资源)。

5.2 jvm优化

jvm内存模型

5.2.1设置虚拟机参数

JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"

5.2.2垃圾回收策略

JDK1.7之后使用G1收集器

JAVA_OPTS="-XX:+UseConcMarkSweepGC"

垃圾回收性能指标

吞吐量:⼯作时间(排除GC时间)占总时间的百分⽐, ⼯作时间并不仅是程序运⾏的时间,还包

含内存分配时间。

暂停时间:由垃圾回收导致的应⽤程序停⽌响应次数/时间。

垃圾收集器

串⾏收集器(Serial Collector)

单线程执⾏所有的垃圾回收⼯作, 适⽤于单核CPU服务器

⼯作进程(单线程)垃圾回收线程进⾏垃圾收集**|—**⼯作进程继续

并⾏收集器(Parallel Collector)

⼯作进程 (多线程)垃圾回收线程进⾏垃圾收集**|—**⼯作进程继续

⼜称为吞吐量收集器(关注吞吐量), 以并⾏的⽅式执⾏年轻代的垃圾回收, 该⽅式可以显著降

低垃圾回收的开销(指多条垃圾收集线程并⾏⼯作,但此时⽤户线程仍然处于等待状态)。适⽤于多

处理器或多线程硬件上运⾏的数据量较⼤的应⽤

并发收集器(Concurrent Collector)

以并发的⽅式执⾏⼤部分垃圾回收⼯作,以缩短垃圾回收的暂停时间。适⽤于那些响应时间优先于

吞吐量的应⽤, 因为该收集器虽然最⼩化了暂停时间(指⽤户线程与垃圾收集线程同时执⾏,但不⼀

定是并⾏的,可能会交替进⾏), 但是会降低应⽤程序的性能

CMS收集器(Concurrent Mark Sweep Collector)

并发标记清除收集器, 适⽤于那些更愿意缩短垃圾回收暂停时间并且负担的起与垃圾回收共享处

理器资源的应⽤

G1收集器(Garbage-First Garbage Collector)

适⽤于⼤容量内存的多核服务器, 可以在满⾜垃圾回收暂停时间⽬标的同时, 以最⼤可能性实现

⾼吞吐量(JDK1.7之后)

6.nginx

6.1特点

跨平台:Nginx可以在⼤多数类unix操作系统上编译运⾏,⽽且也有windows版本

Nginx的上⼿⾮常容易,配置也⽐较简单

⾼并发,性能好

稳定性也特别好,宕机概率很低

6.3 nginx.conf

#===================start=全局快 #user nobody; #运行用户 #worker进程数量 通常设置和Cpu数量相等 worker_processes 1; #全局错误日志以及Pid文件位置 #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; #===================end=全局快 #events块主要影响nginx服务器与⽤户的⽹络连接,⽐如worker_connections 1024,标识每个workderprocess⽀持的最⼤连接数为1024 events { worker_connections 1024; } #负载均衡策略 upstream lagouServer{ #每个请求按照ip的hash结果分配,每⼀个客户端的请求会固定分配到同⼀个⽬标服务器处理,可以解决session问题 ip_hash; #weight代表权重,默认每⼀个负载的服务器都为1,权重越⾼那么被分配的请求越多(⽤于服务器性能不均衡的场景) server 62.234.115.217:8080 weight=1; server 62.234.115.217:8082 weight=2; } #http块http块是配置最频繁的部分,虚拟主机的配置,监听端⼝的配置,请求转发、反向代理、负载均衡等 http { #引入mime类型的文件 include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #连接超时时间 #keepalive_timeout 0; keepalive_timeout 65; #开启gzip压缩 #gzip on; server { #建通端口 listen 80; #使用localhost访问 server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; #默认请求 location / { root html; #默认的网站根目录位置 index index.html index.htm;#索引页,欢迎页 } #反向代理,请求转发到其他服务器,根据前缀 location /abc { proxy_pass http://127.0.0.1:8080; } location /def { proxy_pass http://127.0.0.1:8080; } #使用负载浚航 location /def { proxy_pass http://myServer/; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html #错误提示页面 error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } #动静态分离 静态资源处理,直接去nginx服务器目录中加载 location /static/ { root staticData } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } # another virtual host using mix of IP-, name-, and port-based configuration # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} # HTTPS server # #server { # listen 443 ssl; # server_name localhost; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; # location / { # root html; # index index.html index.htm; # } #} }

6.4命令

./nginx -s reload 来说明nginx信号处理这部分

1)master进程对配置⽂件进⾏语法检查

2)尝试配置(⽐如修改了监听端⼝,那就尝试分配新的监听端⼝)

3)尝试成功则使⽤新的配置,新建worker进程

4)新建成功,给旧的worker进程发送关闭消息

5)旧的worker进程收到信号会继续服务,直到把当前进程接收到的请求处理完毕后关闭

所以reload之后worker进程pid是发⽣了变化的

6.5nginx底层刨析

Nginx启动后,以daemon多进程⽅式在后台运⾏,包括⼀个Master进程和多个Worker进程,Master进程是领导,是⽼⼤,Worker进程是⼲活的⼩弟。

master进程

​ 主要是管理worker进程,⽐如:

​ 接收外界信号向各worker进程发送信号(./nginx -s reload)

​ 监控worker进程的运⾏状态,当worker进程异常退出后Master进程会⾃动重新启动新的worker进程等

worker进程

​ worker进程具体处理⽹络请求。多个worker进程之间是对等的,他们同等竞争来⾃客户端的请

​ 求,各进程互相之间是独⽴的。⼀个请求,只可能在⼀个worker进程中处理,⼀个worker进程,

​ 不可能处理其它进程的请求。worker进程的个数是可以设置的,⼀般设置与机器cpu核数⼀致。

拓展

1.AJP和HTTP连接器区别

tomcat的server.xml中的AJP和HTTP连接器区别

HTTP协议:连接器监听8080端口,负责建立HTTP连接。在通过浏览器访问Tomcat服务器的Web应用时,使用的就是这个连接器。   AJP协议:连接器监听8009端口,负责和其他的HTTP服务器建立连接。在把Tomcat与其他HTTP服务器集成时,就需要用到这个连接器。

AJP(Apache JServ Protocol)是定向包协议。因为性能原因,使用二进制格式来传输可读性文本。WEB服务器通过TCP连接和SERVLET容器连接。

WEB服务器一般维持和Web容器的多个TCP Connecions,即TCP连接池,多个request/respons循环重用同一个Connection。

但是当Connection被分配(Assigned)到某个请求时,该请求完成之前,其他请求不得使用该连接。

单词

Protocol 协议 ==>ProtocolHandler

Rejected 拒绝

Block 块,阻塞 Policy 政策,方针 trigger触发

protocols 协议

extension 延伸

Executor 执行者

available 可用 digest 消化,摘要

internal 内部

standard 标准

Upgrade升级,往上

artifact 成品

最新回复(0)