Tomcat 系統(tǒng)架構(gòu)與原理剖析
注意:瀏覽器訪問服務(wù)器使?的是Http協(xié)議巡揍,Http是應(yīng)?層協(xié)議官紫,?于定義數(shù)據(jù)通信的格式老充,具體的數(shù)據(jù)傳輸使?的是TCP/IP協(xié)議
Tomcat 系統(tǒng)總體架構(gòu)
Tomcat是?個Http服務(wù)器(能夠接收并且處理http請求胞谈,所以tomcat是?個http服務(wù)器)我們使?瀏覽器向某?個?站發(fā)起請求薇宠,發(fā)出的是Http請求,那么在遠(yuǎn)程统舀,Http服務(wù)器接收到這個請求之后匆骗,會調(diào)?具體的程序(Java類)進(jìn)?處理,往往不同的請求由不同的Java類完成處理誉简。
Tomcat 設(shè)計了兩個核?組件連接器(Connector)和容器(Container)來完成 Tomcat 的兩?核?功能绰筛。
連接器,負(fù)責(zé)對外交流: 處理Socket連接描融,負(fù)責(zé)?絡(luò)字節(jié)流與Request和Response對象的轉(zhuǎn)化铝噩;
容器,負(fù)責(zé)內(nèi)部處理:加載和管理Servlet,以及具體處理Request請求骏庸;
Tomcat 連接器組件 Coyote
Coyote 簡介
Coyote 是Tomcat 中連接器的組件名稱 , 是對外的接?毛甲。客戶端通過Coyote與服務(wù)器建?連接具被、發(fā)送請
求并接受響應(yīng) 玻募。
(1)Coyote 封裝了底層的?絡(luò)通信(Socket 請求及響應(yīng)處理)
(2)Coyote 使Catalina 容器(容器組件)與具體的請求協(xié)議及IO操作?式完全解耦
(3)Coyote 將Socket 輸?轉(zhuǎn)換封裝為 Request 對象,進(jìn)?步封裝后交由Catalina 容器進(jìn)?處理一姿,處
理請求完成后, Catalina 通過Coyote 提供的Response 對象將結(jié)果寫?輸出流
(4)Coyote 負(fù)責(zé)的是具體協(xié)議(應(yīng)?層)和IO(傳輸層)相關(guān)內(nèi)容
在 8.0 之前 七咧,Tomcat 默認(rèn)采?的I/O?式為 BIO,之后改為 NIO叮叹。 ?論 NIO艾栋、NIO2 還是 APR, 在性能??均優(yōu)于以往的BIO蛉顽。 如果采?APR蝗砾, 甚?可以達(dá)到 Apache HTTP Server 的影響性能。
Tomcat 服務(wù)器核?配置詳解
?寫實現(xiàn)迷你版 Tomcat
Tomcat 源碼構(gòu)建及核?流程源碼剖析
Tomcat 類加載機(jī)制剖析
Tomcat 對 Https 的?持及 Tomcat 性能優(yōu)化策略
nginx 相關(guān)
Nginx基礎(chǔ)回顧(Nginx是什么携冤?能做什么事情(應(yīng)?在什么場合)悼粮?常?命令是什么?)
正向代理
在瀏覽器中配置代理服務(wù)器的相關(guān)信息曾棕,通過代理服務(wù)器訪問?標(biāo)?站扣猫,代理服務(wù)器收
到?標(biāo)?站的響應(yīng)之后,會把響應(yīng)信息返回給我們??的瀏覽器客戶端
反向代理
瀏覽器客戶端發(fā)送請求到反向代理服務(wù)器(?如 Nginx)翘地,由反向代理服務(wù)器選擇原始
服務(wù)器提供服務(wù)獲取結(jié)果響應(yīng)苞笨,最終再返回給客戶端瀏覽器
負(fù)載均衡服務(wù)器
負(fù)載均衡,當(dāng)?個請求到來的時候(結(jié)合上圖)子眶,Nginx反向代理服務(wù)器根據(jù)請求去找到?個原始服務(wù)器來處理當(dāng)前請求,那么這叫做反向代理序芦。那么臭杰,如果?標(biāo)服務(wù)器有多臺(?如上圖中的tomcat1,tomcat2谚中,tomcat3...)渴杆,找哪?個?標(biāo)服務(wù)器來處理當(dāng)前請求呢,這樣?個尋找確定的過程就叫做負(fù)載均衡宪塔。
?活中也有很多這樣的例?磁奖,?如,我們?nèi)ャy?某筐,可以處理業(yè)務(wù)的窗?有多個比搭,那么我們會被分配到哪個窗?呢到底,這樣的?個過程就叫做負(fù)載均衡南誊。
Nginx 核?配置?件解讀
Nginx的核?配置?件 conf/nginx.conf
包含三塊內(nèi)容:全局塊身诺、events塊蜜托、http塊
全局塊
從配置?件開始到 events 塊之間的內(nèi)容,此處的配置影響nginx服務(wù)器整體的運?霉赡,?如worker進(jìn)
程的數(shù)量橄务、錯誤?志的位置等
event 模塊
events塊主要影響 nginx 服務(wù)器與?戶的?絡(luò)連接,?如 worker_connections 1024穴亏,標(biāo)識每個 workderprocess ?持的最?連接數(shù)為1024
http 模塊
http塊是配置最頻繁的部分济丘,虛擬主機(jī)的配置硼被,監(jiān)聽端?的配置,請求轉(zhuǎn)發(fā)、反向代理吉执、負(fù)載均衡等
Nginx應(yīng)?場景之反向代理
再部署?臺tomcat,保持默認(rèn)監(jiān)聽8081端?
修改nginx配置三痰,并重新加載
這?主要就是多l(xiāng)ocation的使?腰懂,這?的nginx中server/location就好?tomcat中的
Host/Context location 語法如下:
在nginx配置?件中,location主要有這?種形式(優(yōu)先級由高到低):
- 精確匹配 location = /lagou { }
- 匹配路徑的前綴 location ^~ /lagou { }
- 不區(qū)分??寫的正則匹配 location ~* /lagou { }
- 正則匹配 location ~ /lagou { }
- 普通路徑前綴匹配 location /lagou { }
Nginx應(yīng)?場景之負(fù)載均衡
第一步是定義 upstream, 起一個名字. 然后再使用 proxy_pass 即可.
Nginx負(fù)載均衡策略:
- 輪詢
默認(rèn)策略隅津,每個請求按時間順序逐?分配到不同的服務(wù)器诬垂,如果某?個服務(wù)器下線,能?動剔除
location /abc {
proxy_pass http://myServer/;
}
upstream myServer{
server 111.229.248.243:8080;
server 111.229.248.243:8082;
}
- 權(quán)重 weight
weight代表權(quán)重伦仍,默認(rèn)每?個負(fù)載的服務(wù)器都為1结窘,權(quán)重越?那么被分配的請求越多(?于服務(wù)器性能不均衡的場景)
upstream myServer{
server 111.229.248.243:8080 weight=1;
server 111.229.248.243:8082 weight=2;
}
- ip_hash
每個請求按照ip的hash結(jié)果分配,每?個客戶端的請求會固定分配到同?個?標(biāo)服務(wù)器處理充蓝,可以解決session問題
第五部分 Nginx應(yīng)?場景之動靜分離
upstream myServer{
ip_hash;
server 111.229.248.243:8080;
server 111.229.248.243:8082;
}
Nginx 應(yīng)?場景之動靜分離
動靜分離就是講動態(tài)資源和靜態(tài)資源的請求處理分配到不同的服務(wù)器上隧枫,?較經(jīng)典的組合就是
Nginx+Tomcat架構(gòu)(Nginx處理靜態(tài)資源請求,Tomcat處理動態(tài)資源請求)
Nginx 底層進(jìn)程機(jī)制剖析
Nginx啟動后谓苟,以daemon多進(jìn)程?式在后臺運?官脓,包括?個Master進(jìn)程和多個Worker進(jìn)程,Master 進(jìn)程是領(lǐng)導(dǎo)涝焙,是??卑笨,Worker進(jìn)程是?活的?弟。
master進(jìn)程
主要是管理worker進(jìn)程仑撞,?如:
接收外界信號向各worker進(jìn)程發(fā)送信號(./nginx -s reload)
監(jiān)控worker進(jìn)程的運?狀態(tài)赤兴,當(dāng)worker進(jìn)程異常退出后Master進(jìn)程會?動重新啟動新的worker進(jìn)程等
worker進(jìn)程
worker進(jìn)程具體處理?絡(luò)請求。多個worker進(jìn)程之間是對等的隧哮,他們同等競爭來?客戶端的請求桶良,各進(jìn)程互相之間是獨?的。?個請求沮翔,只可能在?個worker進(jìn)程中處理陨帆,?個worker進(jìn)程,不可能處理其它進(jìn)程的請求。worker進(jìn)程的個數(shù)是可以設(shè)置的歧譬,?般設(shè)置與機(jī)器cpu核數(shù)?致岸浑。
Nginx進(jìn)程模型示意圖如下
以 ./nginx -s reload 來說明nginx信號處理這部分
1)master進(jìn)程對配置?件進(jìn)?語法檢查
2)嘗試配置(?如修改了監(jiān)聽端?,那就嘗試分配新的監(jiān)聽端?)
3)嘗試成功則使?新的配置瑰步,新建worker進(jìn)程
4)新建成功矢洲,給舊的worker進(jìn)程發(fā)送關(guān)閉消息
5)舊的worker進(jìn)程收到信號會繼續(xù)服務(wù),直到把當(dāng)前進(jìn)程接收到的請求處理完畢后關(guān)閉
所以reload之后worker進(jìn)程pid是發(fā)?了變化的
worker進(jìn)程處理請求部分的說明
例如缩焦,我們監(jiān)聽9003端?读虏,?個請求到來時,如果有多個worker進(jìn)程袁滥,那么每個worker進(jìn)程都有可能處理這個鏈接盖桥。
master進(jìn)程創(chuàng)建之后,會建?好需要監(jiān)聽的的socket题翻,然后從master進(jìn)程再fork出多個worker進(jìn)程揩徊。所以,所有worker進(jìn)程的監(jiān)聽描述符listenfd在新連接到來時都變得可讀嵌赠。
nginx使?互斥鎖來保證只有?個workder進(jìn)程能夠處理請求塑荒,拿到互斥鎖的那個進(jìn)程注冊 listenfd 讀事件,在讀事件?調(diào)?accept接受該連接姜挺,然后解析齿税、處理、返回客戶端
nginx多進(jìn)程模型好處
每個worker進(jìn)程都是獨?的炊豪,不需要加鎖凌箕,節(jié)省開銷
每個worker進(jìn)程都是獨?的,互不影響词渤,?個異常結(jié)束牵舱,其他的照樣能提供服務(wù)
多進(jìn)程模型為reload熱部署機(jī)制提供了?撐
其他
URL 編碼和解碼問題
http://www.ruanyifeng.com/blog/2010/02/url_encoding.html
作業(yè)
作業(yè)?(編程題):
開發(fā)Minicat V4.0,在已有Minicat基礎(chǔ)上進(jìn)?步擴(kuò)展缺虐,模擬出webapps部署效果 磁盤上放置?個webapps?錄芜壁,webapps中可以有多個項?,?如demo1,demo2,demo3... 具體的項??如demo1中有serlvet(也即為:servlet是屬于具體某?個項?的servlet)志笼,這樣的話在 Minicat初始化配置加載,以及根據(jù)請求url查找對應(yīng)serlvet時都需要進(jìn)?步處理
0崖āH依!!重要
備注:讀取項目磁盤統(tǒng)一路徑: ****appBase="/Users/webapps"韧掩,并且提交自己的webapps以及訪問路徑****
作業(yè)?(簡答題): 請詳細(xì)描述Tomcat體系結(jié)構(gòu)(圖?并茂)
作業(yè)具體要求參考以下鏈接文檔:
https://gitee.com/lagouedu/alltestfile/raw/master/tomcat/Tomcat%E4%BD%9C%E4%B8%9A%E5%A4%A7%E9%A2%98.pdf
作業(yè)資料說明:
1紊浩、提供資料:工程代碼和自己的webapps以及訪問路徑、功能演示和原理講解視頻,簡答題資料坊谁。
2费彼、講解內(nèi)容包含:題目分析、實現(xiàn)思路口芍、代碼講解箍铲。
3、效果視頻驗證:實現(xiàn)模擬tomcat多項目部署效果鬓椭,訪問多個項目獲得動態(tài)返回的內(nèi)容颠猴。
- 增加server.xml配置?件,server.xml保持基本結(jié)構(gòu)即可小染,如下
<?xml version="1.0" encoding="utf-8" ?>
<Server>
<services>
<!--監(jiān)聽端口號-->
<Connector port="80"/>
<Engine>
<!--appBase 為部署目錄-->
<Host name="localhost" appBase="C:/Users/hp/Desktop/第二階段模塊1/第二階段模塊1/code/myWebapps" />
</Engine>
</services>
</Server>
- 制作 demo1 和 demo2 目錄
demo1/
index.html
edu/lagou/server/LagouServlet01.class
edu/lagou/server/LagouServlet02.class
web.xml
demo2/
index.html
edu/lagou/server/LagouServlet03.class
web.xml
- 在 Bootstrap 類中添加 loadAppBase 方法翘瓮,可得到 port 和 appBase的值.
簡易的 Mapper 類
public class Mapper {
/**
* 一級目錄 以及 對應(yīng)的動態(tài)資源
*/
private Map<String, Map<String, HttpServlet>> content2Wrapper = new HashMap<>();
/**
* 一級目錄 以及 對應(yīng)的相對路徑
*/
private Map<String, String> resourcesMap = new HashMap<>();
public void putStaticResources(String key, String value) {
resourcesMap.put("/" + key, value);
}
public void putServletMap(String key, Map<String, HttpServlet> value) {
content2Wrapper.put("/" + key, value);
}
public Map<String, HttpServlet> getServletMap(String key) {
return content2Wrapper.get(key);
}
public String getStaticPath(String key) {
return resourcesMap.get(key);
}
}
自定義的 ClassLoader 類: 用于加載特定目錄的 class
public class MyClassLoader extends ClassLoader {
public MyClassLoader(String basePath) {
this.basePath = basePath;
}
/**
* @description 解析類文件獲得當(dāng)前解析后的類
* @param fullClassName 檔期類的完全限定名, 例如 edu.lagou.server.my.Person
* @return 使用類加載器后獲取的類
* @throws Exception 解析錯誤
*/
public Class<?> transClassFile(String fullClassName) throws Exception {
byte[] classBytes = this.loadBinaryClassFile(fullClassName);
//主要通過父類來解析當(dāng)前的class二進(jìn)制文件
return super.defineClass(fullClassName, classBytes, 0, classBytes.length);
}
/**
* @description 讀取并加載類文件獲得byte數(shù)組返回
* @return byte[] 數(shù)組
* @throws Exception 讀取失敗
*/
private byte[] loadBinaryClassFile(String packageName) throws Exception {
String relativePath = packageName.replaceAll("\\.", "/");
String classFilePath = this.basePath + "/" +relativePath + ".class";// 設(shè)置當(dāng)前class文件的路徑
File classFile = new File(classFilePath);
if (!classFile.exists()) {
throw new FileNotFoundException(classFile.getAbsolutePath() + "文件不存在裤翩。资盅。。踊赠。呵扛。。臼疫。择份。。烫堤。");
}
InputStream fis = null;
ByteArrayOutputStream bos = null;// 內(nèi)存流
byte[] bytes = new byte[(int) classFile.length()]; // 設(shè)置緩沖區(qū)
byte[] readBytes = null;
try {
bos = new ByteArrayOutputStream();
// 開始實例化流荣赶,并加載流
fis = new FileInputStream(classFile);// 這里必須為文件的實際路徑
while (( fis.read(bytes)) != -1) {
bos.write(bytes);
}
readBytes = bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
fis.close();
}
if (bos != null) {
bos.close();
}
}
return readBytes;
}
private final String basePath;
}
創(chuàng)建 loadServerXML 方法,將 server.xml 加載到內(nèi)存
private void loadServerXML() {
InputStream inputStream = Bootstrap.class.getClassLoader().getResourceAsStream("server.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
Node portNode = rootElement.selectSingleNode("/Server/services/Connector/@port");
this.port = Integer.parseInt(portNode.getStringValue());
System.out.println("配置 port = " + this.port);
Node appBaseNode = rootElement.selectSingleNode("/Server/services/Engine/Host/@appBase");
this.appBase = appBaseNode.getStringValue();
System.out.println("配置 appBase = " + this.appBase);
} catch (Exception e) {
e.printStackTrace();
}
}
加載 appBase 目錄下各項目 以及 對應(yīng)的 url-pattern和HttpServlet 動態(tài)資源 和 靜態(tài)資源
private void loadAppBaseServlet() {
File file = new File(this.appBase);
for (File f : Objects.requireNonNull(file.listFiles())) {
if (f.isDirectory()) {
// 解析 web.xml 并加載對應(yīng)的 HttpServlet 類
String webXmlFileName = f.getAbsolutePath() + "/" + "web.xml";
File webXmlFile = new File(webXmlFileName);
// 如果連 web.xml 不存在 則跳過該目錄鸽斟。
if (webXmlFile.exists()) {
String fileName = f.getName();
System.out.println(fileName);
System.out.println("--------------");
System.out.println("...加載該目錄" + f.getPath());
System.out.println(webXmlFileName);
// 加載對應(yīng)的 各項目父路徑 和 對應(yīng)的 url-patten與servlet動態(tài)資源的對應(yīng)關(guān)系, 例如 /demo1 -> (/lagou -> LagouServlet對象)
try {
InputStream inputStream = new FileInputStream(webXmlFile);
Map<String, HttpServlet> stringHttpServletMap = this.loadServletFromInputStream(inputStream,
new MyClassLoader(f.getAbsolutePath()));
this.mapper.putServletMap(fileName, stringHttpServletMap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 加載對應(yīng)的 各項目父路徑 和 對應(yīng)靜態(tài)資源父目錄的對應(yīng)關(guān)系, 例如 /demo1 -> appBase/demo1
this.mapper.putStaticResources(fileName, f.getAbsolutePath());
} else {
System.out.println("...跳過該目錄" + f.getPath());
}
}
}
}
RequestProcessor 的 run 方法
// url
String url = request.getUrl();
// 截取兩端/index.html / + index.html
// /demo1/lagou
String[] split = url.split("/");
String part1;
String part2;
if (split.length == 2) {
part1 = "/" + split[0];
part2 = "/" + split[1];
} else {
part1 = "/" + split[1];
part2 = "/" + split[2];
}
Map<String, HttpServlet> stringHttpServletMap = this.mapper.getServletMap(part1);
if (stringHttpServletMap != null) {
HttpServlet httpServlet = stringHttpServletMap.get(part2);
// process static resources
if (null == httpServlet) {
String filePath = this.mapper.getStaticPath(part1);
response.outputHtml(filePath, part2);
} else {
httpServlet.service(request, response);
}
} else {
// 404
response.outputHtml("NOT FOUND");
}
思路:Mapper類—>Host->Context->Wrapper->Servlet
開始測試
正面案例
http://localhost/index.html
http://localhost/lagou
http://localhost/demo1/index.html
http://localhost/demo1/lagou001
http://localhost/demo1/lagou002
http://localhost/demo2/index.html
http://localhost/demo2/lagou333
反面案例
http://localhost/demo1/abc.html
http://localhost/demo2/lagou003
其中遇到的問題
StaticResourceUtil#getAbsolutePath
如果包含特殊字符拔创,需要進(jìn)行一次 URL 解碼工作
String resourcePath = StaticResourceUtil.class.getResource("/").getPath();
System.out.println(resourcePath);
// 如果包含特殊字符,需要進(jìn)行一次 URL 解碼工作
try {
resourcePath = java.net.URLDecoder.decode(resourcePath, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
作業(yè)2 解答
Tomcat 連接器組件 Coyote
Coyote 是Tomcat 中連接器的組件名稱 , 是對外的接?富蓄∈T铮客戶端通過Coyote與服務(wù)器建?連接、發(fā)送請
求并接受響應(yīng) 立倍。
(1)Coyote 封裝了底層的?絡(luò)通信(Socket 請求及響應(yīng)處理)
(2)Coyote 使Catalina 容器(容器組件)與具體的請求協(xié)議及IO操作?式完全解耦
(3)Coyote 將Socket 輸?轉(zhuǎn)換封裝為 Request 對象灭红,進(jìn)?步封裝后交由Catalina 容器進(jìn)?處理,處
理請求完成后, Catalina 通過Coyote 提供的Response 對象將結(jié)果寫?輸出流
Coyote 負(fù)責(zé)的是具體協(xié)議(應(yīng)?層)和IO
即一個由 Server->Service->Engine->Host->Context 組成的結(jié)構(gòu)口注,從里層向外層分別是:
?Server:服務(wù)器Tomcat的頂級元素变擒,它包含了所有東西。
?Service:一組 Engine(引擎) 的集合寝志,包括線程池 Executor 和連接器 Connector 的定義娇斑。
?Engine(引擎):一個 Engine代表一個完整的 Servlet 引擎策添,它接收來自Connector的請求,并決定傳給哪個Host來處理毫缆。
?Container(容器):Host唯竹、Context、Engine和Wraper都繼承自Container接口苦丁,它們都是容器浸颓。
?Connector(連接器):將Service和Container連接起來,注冊到一個Service芬骄,把來自客戶端的請求轉(zhuǎn)發(fā)到Container猾愿。
?Host:即虛擬主機(jī),所謂的”一個虛擬主機(jī)”可簡單理解為”一個網(wǎng)站”账阻。
?Context(上下文 ): 即 Web 應(yīng)用程序蒂秘,一個 Context 即對于一個 Web 應(yīng)用程序。Context容器直接管理Servlet的運行淘太,Servlet會被其給包裝成一個StandardWrapper類去運行姻僧。
?Wrapper負(fù)責(zé)管理一個Servlet的裝載、初始化蒲牧、執(zhí)行以及資源回收撇贺,它是最底層容器。
Container包含以下結(jié)構(gòu)
?Engine
表示整個Catalina的Servlet引擎冰抢,?來管理多個虛擬站點松嘶,?個Service最多只能有?個Engine, 但是?個引擎可包含多個Host
?Host
代表?個虛擬主機(jī)挎扰,或者說?個站點翠订,可以給Tomcat配置多個虛擬主機(jī)地址,??個虛擬主機(jī)下可 包含多個Context
?Context
表示?個Web應(yīng)?程序遵倦, ?個Web應(yīng)?可包含多個Wrapper
?Wrapper
表示?個Servlet尽超,Wrapper