介紹
大部分一開始接觸WEB服務器的人可能和我一樣對為什么有Apache又有Tomcat服務器感到奇怪(它們還都是Apache開發的呵呵),其實他們不是冗余的服務器,雖然他們都能對外提供WEB服務器,但總的來說還是各有側重點。下面做個簡單介紹。
1. Apache是世界排名第一的Web服務器;在Apache基金會里其永遠被第一支持。而開源的Apache Tomcat也非常關注,由于開源,用戶最支持。
2. Apache支持靜態頁面,Tomcat支持動態頁面(servlet, JSP);一般在使用Apache + Tomcat的配置下,Apache都是實現對JSP的轉發,交給Tomcat來處理。Apache可以支持PHP,但如果要支持JAVA,就需要Tomcat來處理。
3. Apache是Web服務器,Tomcat是應用(Java)服務器,它只是一個servlet容器,是Apache的擴展,但可以獨立于Apache運行;如果以獨立方式運行,功能與Apache等效,但對靜態頁面的處理不太理想。
通過上面分析,可以知道:
1. Apache能有很好的靜態頁面處理能力,如果搭配Tomcat能同時支持靜態和動態頁面
2. Tomcat能夠承擔性能要求較低的WEB應用。
負載均衡
為了提高整個系統的可用性和性能,常常會用到負載均衡,而Apache和Tomcat經常聯合在一起來實現負載均衡技術。
主機Apache作為前段負載平衡服務器對用戶進行分配,后端不同的Tomcat服務器最終處理請求,其中根據側重點不同可以有兩種不同的配置。
1. 增加系統可用性
保證多臺Tomcat服務器之間會話的同步,確保任何一臺當機都不會影響系統的運行,從而提高系統的可用性。
2. 提高系統性能
在負責負載平衡的Apache主機上記錄每個請求的Session ID(這個ID是由Tomcat分配的)及回應Session的Tomcat Server對應關系,在下一個請求到來時先判斷每個Session ID,如果有標識連接已經建立,那么轉到對應服務器;負責就是新的連接,根據每個后臺Tomcat服務器的狀態分配一個服務器并記錄Session ID.
如下圖:

Apache + Tomcat配置
正如上面中看到的可以通過Apache +Tomcat來配置來達到較高WEB服務器同時,也能高正較高的可用性和系統性能。這些是通過Apache與Tomcat之間的通訊來完成的。Apache和Tomcat之間的通訊基本有三種方式:
1. Mod_Jk
2. HTTP_Proxy
3. AJP_Proxy
這里主要介紹Mod_JK方式,其也是最流行的方式,并且官方的文檔也非常全。
Mod_JK
Mod_JK是Apache的一個模塊,其通過AJP協議實現Apache與Tomcat之間的通訊,而Tomcat默認監聽AJP連接器的8009端口來接受AJP的連接請求;一般這些請求來自前端的Apache服務器。
由于Mod_JK已經被設計成Apache的一個模塊,因此可以通過一些相對簡單的配置就能達到Apache和Tomcat之間通訊的目的;同樣包含負載均衡。
配置步驟
安裝
1. 安裝Apache HTTP服務器,從Apache官方網站下載;我安裝的是Apache2.0.64
2. 安裝Apache Tomcat,可以安裝在Apache同一個機器,也可以在其他的機器,版本要與Mod_JK匹配
3. 下載Mod_Jk;這里注意要與Apache Tomcat匹配。
配置
加在Mod_Jk
Mod_Jk是在Apache HTTP服務器上被加載的,需要將下載下來的Mod_Jk.so放到Apache/Modules目錄下,并修改httpd.conf,增加下面一行:
Include conf/mod_jk.conf
使用include命令包含mod_jk.conf,這個是我們自己添加的關于mod_jk的配置文件。
新建Mod_jk.conf,并添加如下內容:
#Load mod_jk module.
LoadModule jk_module modules/mod_jk.so
#Where to find workers.properties
JkWorkersFile conf/workers2.properties
JkMountFile conf/urimap.properties
#Where to put jk logs
JkLogFile logs/mod_jk.log
#Set the jk log level[debug/error/info]
JkLogLevel info
#Select the log format
JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
#JkRequestLogFormat set the request format
JkRequestLogFormat "%w %V %T"
JkMount /* loadbalancer |
上面包含了兩個properties文件,分別是workers2.properties和urimap.properties。
Workers2.properties用于說明Tomcat服務器的負載均衡配置方法,具體如下:#worker.list=loadbalancer
worker.list=loadbalancer,tomcat1,tomcat2 #server 列表
#define the first node
worker.tomcat1.port=8009
worker.tomcat1.host=10.224.70.57
worker.tomcat1.type=ajp13
worker.tomcat1.lbfactor=1
#define the first node
worker.tomcat2.port=8009
worker.tomcat2.host=10.224.70.57
worker.tomcat2.type=ajp13
worker.tomcat2.lbfactor=1
#Now we define the load-balancing behavior
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=tomcat1, tomcat2
worker.loadbalancer.sticky_session=true
worker.loadbalancer.sticky_session_force=true
在Mod_jk.conf中有JkMount /* loadbalancer,這個loadbalancer正好對應上面worker.list一行的loadbalancer。
另外tomcat1和tomcat2是配置的后臺tomcat服務器;當然這里只有一臺服務器,所以兩個worker都是同樣的地址。lbfactor表示在負載均衡算法中采用的權重。
另外在urimap.properties文件中說明了哪些請求會轉交給Tomcat服務器,配置項為:
!/*.htm=loadbalancer
注意這里的表示*.htm的請求都到loadbalancer;這個loadbalancer是上文中的loadbalancer。
至此Apache和Tomcat之間的負載均衡已經被配置好了。
運行時觀察
這里主要觀察session的stickiness是如何被實現的;已經Apache服務器如何了解到關于Session ID相關的信息。
實際上對于負載均衡來說,如果上面的例子中你配置了兩個Tomcat,那么關閉sticky_session的情況下,每個Tomcat服務器將輪流收到請求。
其實在試驗的時候發現session ID是Tomcat服務器分配創建的,Apache服務器只是用來管理其與Tomcat服務器之間的關系;當然Apache服務器需要檢查每次請求中的Session ID。這個ID是由Tomcat分配的;Tomcat對于每個第一次請求的動態頁面都會生成JSESSIONID;而對于靜態頁面是不會生成JESSIONID的。
具體運行過程:
1. Apache服務器接收到來自client的請求,檢查HTTP請求是否包含SESSIONID
2. 如果不包含,采用負載均衡算法選擇一個Tomcat服務器
3. Apache服務器將這個client請求轉發到選定Tomcat服務器的8009端口,數據包的格式是AJP協議
4. Tomcat解析AJP協議,生成SESSION ID并且取得響應,將結果發送給Apache服務器,這個是通過之前的8009連接過來的鏈接發送回去的。
5. Apache服務器解析AJP包,并得到SESSION ID信息,并將其與對應的服務器(和會話)關聯起來。
6. Apache將SESSION ID轉發給client
7. Client將Session ID保存到Cookie中用于下次使用。
Packet No |
Apache |
Tomcat:8009 |
1 |
2:REQ:GET /index.jsp HTTP/1.1 à |
|
2 |
|
ß2:RSP:send headers:200 OK |
3 |
|
ß2:RSP:SEND BODY CHUNK |
4 |
|
ß2:RSP:END RESPONSE |
通常這個連接建立好了之后短時間是不會斷,不過這是可以通過參數來配置的。
在上面的包交互過程中:第2個包包含了SESSION ID的信息;內容類似于下面:
Set-Cookie..3JSESSIONID=955AAE2AA22B074BD1824395B6E4835A; Path=/...Content-Type...text/html...Content-Length...141.AB.....
AJP(Apache JServer Protocol)
AJPv13協議是面向包的。WEB服務器和Servlet容器通過TCP連接來交互;為了節省SOCKET創建的昂貴代價,WEB服務器會嘗試維護一個永久TCP連接到servlet容器,并且在多個請求和響應周期過程會重用連接。
一旦一個連接被分配給特定的請求,在請求處理周期結束之前這個連接不能被重用;換句話說,請求不是連接多工的。這樣編碼比較簡單。
一旦連接被分配給特定的請求,基本請求信息(HTTP頭)以壓縮的個性(通常字符串會編碼為整數);如果有請求實體,會隨后立即發送。
這個時候,servlet容器已經準備好了處理請求并發送西面的信息回WEB服務器:
1. SEND_HEADERS:發回瀏覽器的響應頭
2. SEND_BODY_CHUNK:將實體發送給瀏覽器
3. GET_BODY_CHUNK:獲取更多的請求數據,這些數據沒有被發送。
4. END_RESPONSE:表示請求處理周期結束。
WEB服務器發送給Servlet容器的請求包括:
1. 轉發請求開始
2. 關閉
3. Ping
4. CPing
AJP13_FORWARD_REQUEST的格式
AJP13_FORWARD_REQUEST :=
Magic: (word)
Length: (word)
prefix_code (byte) 0x02 = JK_AJP13_FORWARD_REQUEST
method (byte) HTTP請求方法的字節編碼
protocol (string)
req_uri (string)
remote_addr (string)
remote_host (string)
server_name (string)
server_port (integer)
is_ssl (boolean)
num_headers (integer)
request_headers *(req_header_name req_header_value)
attributes *(attribut_name attribute_value)
request_terminator (byte) OxFF
AJP13_SEND_HEADERS
AJP13_SEND_HEADERS :=
prefix_code 4
http_status_code (integer)
http_status_msg (string)
num_headers (integer)
response_headers *(res_header_name header_value)
更多信息,參見:http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html#header_encoding