最近在開發的項目須要承受很高的併發量。綜合各類狀況,決定使用Apache+Tomcat+JK的方式實現負載均衡,而且做爲一個統一的服務還要實現羣集(同步Session)。 css
在網上找了不少資料,都是零零散散的,沒有一個完整的過程。經過幾天的努力,完成了從編譯、部署到配置的整個過程,期間也遇到了一些問題。在接下來的文字中將這些過程記錄下來,作個筆記同時也分享給你們。 html
gcc、g++和java是必須的,若是運行上述命令提示command not found,則須要安裝。具體安裝方法這裏不作介紹,請參閱相關文檔。 java
接下來要準備的是apache服務器、tomcat服務器和JK鏈接器 mysql
1.下載apache服務器源碼包 linux
apache服務器官方沒有發佈編譯好的linux二進制包,只能經過下載源代碼,而後本身編譯。所以須要先下載源碼。 c++
訪問網址http://httpd.apache.org/download.cgi,能夠看到apache服務器目前放出的版本信息,推薦使用穩定版的release。 web
而後選擇Unix版源碼: sql
2.下載tomcat服務器源碼包 apache
目前tomcat服務器我的仍是以爲6.0比較穩定。7.0畢竟是新出的東西,須要必定的生產實踐考驗才能達到理想的狀態。所以這裏選擇tomcat 6.0。 bootstrap
訪問網址http://tomcat.apache.org/download-60.cgi,能夠看到目前穩定的版本爲6.0.33:
這裏強烈建議下載tar.gz格式的壓縮包。在Linux下,文件訪問有着嚴格的權限限制。一個文件是否容許以二進制或者腳本的形式執行,徹底取決於其是否擁有執行缺陷,這與Windows識別文件後綴名(.exe、.bat)的方式不一樣。zip格式的壓縮包中是不保留文件的權限信息的,而tar.gz格式的壓縮包是保存有文件的權限信息的。
3.下載JK鏈接器源碼包
做爲apache與tomcat鏈接的橋樑,JK鏈接器使用C語言編寫,與apache緊密結合,做爲模塊裝載到apache服務器中,經過配置實現與特定的tomcat服務器進行通訊,從而實現負載均衡的功能。
訪問網址http://tomcat.apache.org/download-connectors.cgi,能夠找到最新最穩定的JK鏈接器版本:
這裏仍是推薦下載tar.gz格式的源碼。緣由同上。
4.解壓
apache服務器、tomcat服務器和JK鏈接器都已經下載好了,以下圖所示:
而後將這三個包都解壓出來:
5.編譯apache服務器
首先編譯apache服務器。在編譯以前須要執行其自帶的檢測配置腳本。對於不一樣發行版本的Linux,默認安裝的庫都有所差異,即使是同一個發行版本,因爲用戶安裝軟件的軟件不一樣,也會致使系統內包含的庫有所區別。所以apache做爲開源服務器,在編譯前須要瞭解系統的庫安裝狀況,某些模塊須要依賴於特定的庫,若是這些庫不存在,配置腳本將自動忽略這些庫的編譯。通過檢測時候會生成合適的MakeFile文件。這裏特別提醒一句,若是直接執行配置腳本,是不會編譯額外的模塊的,咱們但願使用額外模塊時,須要在運行配置腳本命令後加入參數,讓其盡最大可能編譯可用的庫。關於這方面的介紹能夠參閱個人另一篇文章「Linux下編譯apache服務器modules文件夾缺乏模塊(.so)的問題」(http://blog.csdn.net/chaijunkun/article/details/6977466)。下面進入apache服務器源碼目錄並執行配置腳本:
加入--with-mpm=worker是修改apache服務器的工做模式。默認模式是prefork。prefork採用預派生子進程方式,用單獨的子進程來處理 不一樣的請求,進程之間彼此獨立。相對於prefork,worker是全新的支持多線程和多進程混合模型的MPM(多路處理模塊)。因爲使用線程來處理,因此能夠處理相對海量的請求,而系統資源的開銷要小於基於進程的服務器。可是,worker也使用了多進程,每一個進程又生成多個線程,以得到基於進程服務器的穩定性。
若是配置過程當中出現
這樣的錯誤信息,說明本機沒有安裝apr運行庫,須要下載並安裝。訪問網址:http://apr.apache.org/download.cgi,下載apr和apr-util:
解壓apr和apr-util
進入apr,並編譯
生成了MakeFile後直接編譯
編譯好以後使用root權限安裝:
而後使用相似的方法配置apr-util:
編譯好以後使用root權限安裝:
固然若是你在配置apache服務器編譯的時候沒有提示缺乏「APR」,請忽略上面關於APR編譯的幾步。
回到apache服務器源碼所在目錄,開始編譯:
編譯過程大概不到十分鐘,完成以後使用root權限進行安裝
若是不出意外,至此apache就安裝成功了。來測試一下:
進入apache服務器的bin目錄,並啓動服務器:
在本地打開瀏覽器,訪問http://127.0.0.1
若是出現「It Works!」則表示啓動成功了
這裏要注意一點就是Linux的防火牆問題。若是你的Linux服務器啓動了防火牆,本地訪問上面的網址是沒有問題的,但若是其它計算機訪問你的服務器有可能會鏈接失敗。
出現這種狀況的緣由是防火牆將入站80端口封鎖了。解決方法是將80端口加入到容許列表中:
進入防火牆設置後,若是發現Firewall狀態爲Enabled,表示防火牆已啓用,須要將WWW(HTTP)服務標記爲信任,若是須要使用hhtps協議,還要將Secure WWW(HTTPS)服務也標記爲信任。以下圖所示:
另外,此時若是有其餘程序佔用80端口也是會影響到apache服務器的,須要確保這個端口沒有被佔用。
還有我還要補充一點,在Mac OS中按照上述方法安裝apache服務器是不行的。開始的時候我不想搭建Linux服務器,想到Mac OS也是類Unix的系統,操做命令什麼的都同樣,就先在Mac上實驗了。結果安裝上apache服務器後啓動了,每次訪問都提示505錯誤,service temporarily unavailable。通過查閱不少資料和嘗試才發現,原來Mac系統中已經自帶了apache服務器。具體應用是在「系統設置」中的「共享」功能。這個功能裏有「Web共享」方式。其實現時使用的服務器就是apache。它採用的配置文件在/etc/httpd/目錄中。這裏的配置文件和本身安裝的apache服務器配置文件衝突了,所以形成505錯誤。這一點須要注意。(注:我是用的Mac系統爲Mac OS X Lion 10.7.2)
若是你但願把apache服務器註冊爲系統服務,讓它隨着系統啓動而啓動,則須要在/etc/init.d/目錄中創建服務管理腳本,咱們將其命名爲httpd:
這一步直接拷貝:
cp /usr/local/apache2/bin/apachectl /etc/rc.d/init.d/apache
vi /etc/rc.d/init.d/apache
在開頭的#!/bin/sh 下面加上
#chkconfig: 2345 85 15
6.編譯JK鏈接器
剛剛完成了apache服務器的編譯,接下來順便把JK鏈接器也編譯出來。
進入剛剛解壓出來的tomcat-connector目錄,再進入native目錄。執行配置:
這裏須要注意的是配置腳本要添加一個apxs完整路徑做爲參數。apxs是一個爲Apache HTTP服務器編譯和安裝擴展模塊的工具,用於編譯一個或多個源程序或目標代碼文件爲動態共享對象,使之能夠用由mod_so提供的LoadModule指令在運行時加載到Apache服務器中。
另外,配置腳本運行時會檢查g++所在的目錄,若是沒有安裝g++,則會顯示:
請檢查是否已經正確安裝了c++編譯器。
由於實驗用的服務器安裝的是X86_64版的Red Hat Enterprise Linux Server ,所以要安裝以下的包:
libstdc++-devel-4.1.2-46.el5.x86_64.rpm
gcc-c++-4.1.2-46.el5.x86_64.rpm
若是使用rpm命令沒法安裝,能夠在http://szmov.net/centos5464/CentOS/裏查找到相應的資源,下載下來安裝也是同樣的。
配置無誤後就能夠編譯了,執行make命令:
7.JK鏈接器模塊的部署
編譯完成後使用ls命令來列出native目錄下的全部目錄和文件。注意有apache-1.3和apache-2.0兩個目錄。因爲在配置編譯的時候指定了apxs工具的位置。配置腳本會根據apxs的反饋結果自動識別目標apache服務器爲2.x版本,所以本次編譯生成的mod_jk.so模塊會放在apache-2.0目錄中,apache-1.3目錄中是沒有mod_jk.so的,這一點請注意。以下所示:
咱們如今將編譯好的mod_jk.so拷貝到apache服務器的modules目錄中,這個目錄是專門用來存放擴展模塊的:
至此JK鏈接器模塊就部署完成了,可是還須要配置,具體配置將在下文中詳細描述。
8.部署tomcat服務器
因爲要在本地開啓兩個tomcat服務器實例以模擬負載均衡+羣集的效果,所以咱們須要將以前解壓出來的tomcat複製成兩份,進入解壓時的目錄,重命名解壓出來的原始目錄爲tomcat_server_1,而後複製此目錄,副本目錄名稱爲tomcat_server_2:
如今測試tomcat_server_1是否可以正常工做。
測試用例,隨便用eclipse建立一個web項目,寫一個頁面,打成war包,放到tomcat_server_1下的,webapps目錄中。而後切換到tomcat_server_1的bin目錄下,啓動tomcat_server_1:
此處要注意的地方同測試apache服務器是否正常工做時是同樣的,須要注意防火牆是否阻塞了tomcat服務器默認採用的8080端口,是否有其餘程序佔用此端口。
看到沒什麼問題,咱們先吧tomcat_server_1關閉
9.apache服務器的配置
apache服務器、tomcat服務器和JK鏈接器都部署完成並能正確執行後就能夠開始配置了
用vi或者其它編輯器打開/usr/local/apache2/conf/httpd.conf文件(因爲該文件權限屬性爲rw-r--r--,所以要想修改此文件須要root權限),這就是apache服務器的主配置文件了。
這裏我推薦使用圖形化的編輯器來編輯它。由於這個文件不少行,若是用文本模式的編輯器編輯我的感受很繁瑣。
在有不少LoadModule語句的地方,末尾追加一行
LoadModule jk_module modules/mod_jk.so
而後在寫有<IfModule XXXX>的區域追加一行以下配置
<IfModule jk_module>
JkWorkersFile conf/workers.properties
JkMountFile conf/uriworkermap.properties
JkLogFile logs/mod_jk.log
JkLogLevel warn
</IfModule>
下面給出了一個我寫的配置。注意配置中有註釋的地方。「#」開頭的行爲註釋行。已經去除了原有的配置中的多餘註釋。
LoadModule表示當apache服務啓動時要加載模塊 jk_module爲模塊的別名,後面跟的modules/mod_jk.so就是相對於apache服務器所在目錄(/usr/local/apache2/)的模塊文件名。
<IfModule jk_module>區域表示當apache服務器加載jk_module(在LoadModule指令中指定的模塊別名)模塊時所作的配置。
其中:
JkWorkersFile 指定負載均衡服務器的配置文件,文件名爲相對於apache服務器所在目錄的conf/workers.properties文件
JkMountFile 指定那些請求交由負載均衡服務器來處理,那些由apache服務器來處理,配置文件爲相對於apache服務器所在目錄的conf/uriworkermap.properties文件
JkLogFile 指定JK鏈接器的日誌輸出文件,文件爲相對於apache服務器所在目錄的logs/mod_jk.log文件
JkLogLevel 指定JK鏈接器輸出日誌的級別,級別爲warn以上的日誌將被輸出到日誌文件中,可選的值級別由低到高分別爲:TRACE DEBUG INFO WARN ERROR FATAL
------------------------------------------------------------------------------------------------------------------------------------------------
<IfModule worker.c>區域表示當apache服務器以worker模式工做時使用的配置。
指令說明:
StartServers:設置服務器啓動時創建的子進程數量。由於子進程數量動態的取決於負載的輕重,全部通常沒有必要調整這個參數。
ServerLimit:服務器容許配置的進程數上限。只有在你須要將MaxClients和ThreadsPerChild設置成須要超過默認值16個子進程的時候才須要使用這個指令。不要將該指令的值設置的比MaxClients 和ThreadsPerChild須要的子進程數量高。修改此指令的值必須徹底中止服務後再啓動才能生效,以restart方式重啓動將不會生效。
ThreadLimit:設置每一個子進程可配置的線程數ThreadsPerChild上限,該指令的值應當和ThreadsPerChild可能達到的最大值保持一致。修改此指令的值必須徹底中止服務後再啓動才能生效,以restart方式重啓動將不會生效。
MaxClients:用於伺服客戶端請求的最大接入請求數量(最大線程數)。任何超過MaxClients限制的請求都將進入等候隊列。默認值是"400",16 (ServerLimit)乘以25(ThreadsPerChild)的結果。所以要增長MaxClients的時候,你必須同時增長 ServerLimit的值。筆者建議將初始值設爲(以Mb爲單位的最大物理內存/2),而後根據負載狀況進行動態調整。好比一臺4G內存的機器,那麼初始值就是4000/2=2000。
MinSpareThreads:最小空閒線程數,默認值是"75"。這個MPM將基於整個服務器監視空閒線程數。若是服務器中總的空閒線程數太少,子進程將產生新的空閒線程。
MaxSpareThreads:設置最大空閒線程數。默認值是"250"。這個MPM將基於整個服務器監視空閒線程數。若是服務器中總的空閒線程數太多,子進程將殺死多餘的空閒線程。MaxSpareThreads的取值範圍是有限制的。Apache將按照以下限制自動修正你設置的值:worker要求其大於等於 MinSpareThreads加上ThreadsPerChild的和。
ThreadsPerChild:每一個子進程創建的線程數。默認值是25。子進程在啓動時創建這些線程後就再也不創建新的線程了。每一個子進程所擁有的全部線程的總數要足夠大,以即可以處理可能的請求高峯。
MaxRequestsPerChild:設置每一個子進程在其生存期內容許伺服的最大請求數量。到達MaxRequestsPerChild的限制後,子進程將會結束。若是MaxRequestsPerChild爲"0",子進程將永遠不會結束。將MaxRequestsPerChild設置成非零值有兩個好處:能夠防止(偶然的)內存泄漏無限進行而耗盡內存;
給進程一個有限壽命,從而有助於當服務器負載減輕的時候減小活動進程的數量。
若是設置爲非零值,筆者建議設爲10000-30000之間的一個值。
公式:
ThreadLimit >= ThreadsPerChild
MaxClients <= ServerLimit * ThreadsPerChild,而且MaxClients必須是ThreadsPerChild的倍數
MaxSpareThreads >= MinSpareThreads+ThreadsPerChild
------------------------------------------------------------------------------------------------------------------------------------------------
接下來配置上面提到的conf/workers.properties文件和conf/uriworkermap.properties文件:
進入apache服務器的conf目錄
創建workers.properties和uriworkermap.properties文件
下面給出我已經配置好的兩個文件
worker.list 首先配置了兩個worker,一個用於負載均衡,一個用於監視負載均衡狀態。別名分別爲loadBalanceServers和jk_watcher
而後分別配置位於本機的兩個負載均衡服務器
worker.s1.port:第一臺負載均衡服務器AJP協議鏈接器的鏈接端口,這裏配置爲8109
worker.s1.host:第一臺負載均衡服務器的主機名、域名或者IP地址,這裏配置爲本機localhost
worker.s1.type:JK模塊實現負載均衡採用的是AJP協議1.3版本,所以第一臺負載均衡服務器的類型配置爲ajp13
worker.s1.lbfactor:第一臺負載均衡服務器在整個負載均衡系統中所佔的權重,這裏配置爲10,權重越大,越有可能處理更多的請求,建議給性能好的機器配置更高的權重。
worker.s1.cachesize:apache服務器是多線程的,tomcat可以利用這一優點來維持必定數量的鏈接做爲緩存。根據用戶的多少來配置一個合適緩存鏈接數量有助於提升性能。這裏配置爲5
s1是第一臺負載均衡服務器的別名,這個別名要牢記,由於在接下來的配置中還會用到。
s2做爲第二臺負載均衡服務器,配置與s1大體相同。區別是AJP協議鏈接器的鏈接端口與s1的不一樣,這是由於要在同一臺物理機上部署兩個tomcat服務器的緣故。若是是兩臺物理機,則能夠配置相同的端口,那麼host屬性就應該不同了。兩個tomcat服務器的權重都是10,則兩個tomcat服務器將會有相同的處理請求的機會。
worker.loadBalanceServers.type:設置名稱爲「loadBalanceServers」的worker類型,這裏配置爲lb,也就是Load Balance負載均衡
worker.loadBalanceServers.balanced_workers:設置名稱爲「loadBalanceServers」的worker擁有哪些負責負載均衡的服務器實例,這裏配置爲s1和s2
worker.loadBalanceServers.sticky_session:設置負載均衡是否採用粘性會話。若是該屬性設置爲true,假設一個請求被s1處理了,下次來源於同一個客戶端的請求也將被s1處理。直到s1已經達到最大鏈接數,JK纔會將會話切換到其餘服務器上。可是若是恰巧一直負責處理該會話的服務器down掉了,則會話將會丟失,明顯的故障現象就是關於session的操做會出現莫名其妙的錯誤(例如你所運行的應用中用戶可能已經登陸了,但忽然在一次訪問後莫名其妙地提示沒有登陸)。這裏配置爲false,不啓用粘性會話,讓服務器都有機會處理請求,提升了系統的穩定性。
worker.jk_watcher.type:設置名稱爲「jk_watcher」的worker類型,這裏配置爲status,用於監視各個負載均衡服務器實例的運行狀態
# worker.jk_watcher.read_only:設置名稱爲「jk_watcher」的worker是否爲只讀。上面已經將這個worker設置爲了監控worker,若是設置爲只讀,就不能對負載均衡服務器參數進行配置了,這裏先將這條配置註釋掉,默認值爲false,表示能夠配置參數。
worker.jk_watcher.mount:設置名稱爲「jk_watcher」的worker(負載均衡服務器實例監視器)的掛載路徑,這裏配置爲/admin/jk。這樣就能夠經過http://127.0.0.1/admin/jk來訪問監視工具了,能夠很方便地看到各個負載均衡服務器的工做狀況。
worker.retries:這是worker全局的重試次數。在apache服務器啓動後,會最多嘗試若干次去鏈接這些負載均衡服務器,若鏈接不上就認爲是down掉了,這裏配置爲3
下面給出配置,其做用是告訴apache服務器哪些請求由負載均衡服務器處理:
在配置文件中,以「!」開頭的條件表示「不要」,「=」表示交給。
所以條件「/*=loadBalanceServers」表示將任何請求交給負載均衡服務器。
條件「!/*.jpg=loadBalanceServers」表示不要將.jpg結尾的請求交給負載均衡服務器
apache服務器接收到一個請求後會按照配置文件中的約束條件一個一個地檢查,而後按照最後知足的匹配條件來決定由哪一個worker來處理請求。
個人測試用例中須要輸入http://127.0.0.1/TestProject/showInfo.do來查看信息。那麼接下來就將這個請求做爲示例來解釋上面配置文件的工做過程:
通過上面的條件篩選,最符合條件的就是「/*=loadBalanceServers」。所以將請求轉給了負載均衡服務器。
試想一下,若是在apache主目錄下放置了一個名爲a.jpg的圖片,訪問路徑爲http://127.0.0.1/a.jpg,請求通過該配置的檢查,最後知足的條件就是「!/*.jpg=loadBalanceServers」,不要將.jpg結尾的請求交給負載均衡服務器,所以apache服務本身處理了該請求。
.jpg是靜態數據,apache由C語言實現,直接針對系統底層進行IO操做,所以靜態性能優良。而tomcat做爲Servlet容器,擅長的是J2EE相關業務的解析。所以經過這樣配置能夠實現應用的「動靜態分離」,相互取長補短,優化了性能。相似地也能夠將.js、.css和.html等等靜態文件按照上述格式填寫到uriworkermap.properties配置文件中。
10.tomcat服務器的配置
因爲在同一臺物理機中部署了兩個tomcat服務器實例,所以須要對端口相關的設置特別當心。tomcat服務器的主配置文件server.xml位於conf目錄內。爲了配置簡單,我將最原始server.xml配置文件中的全部註釋刪除,而後配置好了一個模板,該模板是s1((即tomcat_server_1)的配置文件,以下所示:
配置好s1服務器後再配置s2服務器。按照上面模板中的註釋要求,修改相應的端口就能夠了。PS:<Cluster ...></Cluster>節點之間部分沒必要本身動手敲進去,在tomcat服務器目錄的/webapps/docs/cluster-howto.html文件中有這一段文字,拷貝出來貼到server.xml文件中便可。
tomcat6中,單機開發時爲了保證GET請求參數採用UTF8編碼(用於支持中文參數),在server.xml中會進行了以下設置:
<Connector port="8080"maxThreads="150" minSpareThreads="25"
maxSpareThreads="75" enableLookups="false" redirectPort="8443"
acceptCount="100" debug="99" connectionTimeout="20000"
disableUploadTimeout="true" URIEncoding="UTF-8" />
可是,當使用apache + tomcat 組成羣集與負載均衡系統時,apache會將servlet/jsp請求轉發給Tomcat。此時是經過AJP協議來轉發的,所以對應的請求其實是被轉發到Tomcat監聽的AJP端口上的,因此這裏針對8080的設置天然就無效了。正確的方法是進行下面的設置:
<Connector port="8009"enableLookups="false" redirectPort="8443"
debug="0" protocol="AJP/1.3" URIEncoding="UTF-8" />
須要在羣集環境中修改默認URL編碼的朋友們在這裏須要注意一下
最近剛剛發現tomcat的關閉腳本shutdown.sh有問題,常常不能徹底回收資源。tomcat是基於Java編寫的,固然其運行也就脫離不了java的JVM。當執行完shutdown.sh腳本後,tomcat服務器表面上是關閉了,然而JVM並無徹底退出,還在清理並回收資源,若是這個時候當即使用startup.sh進行啓動,很容易致使再啓動一個新的JVM實例,若是維護次數增多就會致使系統內存耗盡,我今天就經歷了以下的錯誤:
registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped.
這條錯誤發現於logs目錄下的catalina.日期.log文件中。不管再怎麼啓動tomcat都啓動不了。後來重啓了服務器竟然能夠啓動了,後來同事說shutdown.sh腳本有問題。因而我通過實驗,果真是這樣。所以在這裏奉勸讀者,若是須要從新啓動tomcat服務器,除了先執行shutdown.sh腳本外,還應該運行ps ax | grep java 來看看有沒有殘留的java進程,若是有,使用kill命令將其殺死,這纔是正確的關閉tomcat服務器方法。
tomcat 6.0.25之後引入了內存泄露偵測,對於垃圾回收不能處理的對像,它就會作日誌。
在tomcat的server.xml文件中,以下配置就是用來作內存泄露偵測的
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>;
也幸好有了這個東西才提醒了我正在運行着多個JVM實例。
開啓了tomcat服務器以後,就能夠開啓apache服務器了(注意,順序很重要!必定要先開tomcat)