java grizzly_Grizzly简介 | Java Game
用Java技術來編寫一個擴展性能很高的服務器軟件是件很困難的事情。Java虛擬機的線程管理機制使得純Java寫的HTTP引擎很難響應成千上
萬的并發用戶。正如Tomcat一樣,在并發用戶數不是很高的情況下能夠獲得很高的吞吐量,但是在高并發的情況下性能下降很快,變得不太穩定。
在JDK
1.4推出NIO之后,有很多基于NIO的框架出現,利用NIO的新特性,來編寫高性能的HTTP引擎。其中以Jean-Francois
Arcand的Grizzly最為引人矚目。Grizzly最早被用于Sun Java System Application Server,
Platform Edition 8.1。隨后成為開源軟件GlassFish的一部分。在今后,Sun Java System
Application Server 9.x的Platform Edition和Enterprise
Edition都會使用Grizzly作為HTTP引擎。
17.2.1? Grizzly的基本架構
圖17-1描述了Grizzly的基本架構。
圖17-1? Grizzly的基本架構
Grizzly的基本架構主要包含以下幾個方
面:Pipeline、SelectorThread和Task。下面分別加以介紹。
1. Pipeline
在
com.sun.enterprise.web.connector.grizzly包下,有許多與Pipeline相關的類,例如Pipeline、
KeepAlivePipeline、ThreadPoolExecutorPipeline、LinkedListPipeline等。
Pipeline是個不太好理解的詞匯,其實把這些類叫做ThreadPoolWrapper可能更加合適和容易理解。只要熟悉服務器端的軟件,對
Thread
Pool(線程池)一定不會陌生。線程比起進程來說,消耗的資源要少,共享數據更加簡單。因此,現在大多數服務器軟件(特別是HTTP服務器)都會采用多
線程模式。但是線程的創建和關閉仍然是比較慢的系統服務,聰明的服務器軟件設計者會在系統啟動的時候,預先創建一些線程,并且將這些線程管理起來,在系統
正常運行的時候服務于客戶的請求。通過這樣的手段,線程不需要在使用的時候臨時創建,大大提高了軟件的運行速度和效率。對這種線程的管理方法叫做線程池。
線程池中的線程需要互相協作,有序地執行客戶的請求。一般用于同步線程的結構叫任務隊列。客戶的請求根據先后順序被放到了任務隊列中,線程池中空閑的線程
會從任務隊列中獲得任務并執行。
Grizzly中的Pipeline實際上封裝
了一個Thread
Pool(線程池)和一個任務隊列。Pipeline的主要目的是封裝了一個統一的接口,可以讓Grizzly根據配置文件任意選擇不同算法的線程池,來
獲得不同的特點和性能。在Grizzly中已經實現了好幾種線程池。其中有ThreadPoolExecutorPipeline(基于
java.util.concurrent.ThreadPoolExecutor來實現的線程池),還有LinkedListPipeline(使用簡
單的linklist數據結構管理的線程池)。在早期的Grizzly中還會看到一些其他的實現。經過測試以后,淘汰了一些性能不好的算法,目前只剩下這
兩種Pipeline了。事實上在大并發用戶的測試中,LinkedListPipeline的性能是最好的,因此被設置為默認的選擇。在以后的版本
中,ThreadPoolExecutorPipeline也可能會消失,只保留性能最好的算法是明智的選擇。但是現在還存在兩種算法,其主要原因是
java.util.concurrent.ThreadPoolExecutor的名聲太響,所有的文章和測試都曾經證明過它的高性能。就連
Grizzly的作者本身都不相信LinkedListPipeline的性能要比ThreadPoolExecutorPipeline好,只不過當前
的測試結果事實如此。因此該作者自己也說,一旦有證據證明ThreadPoolExecutorPipeline的性能又重新超過
LinkedListPipeline,他會立即將默認的設置指向ThreadPoolExecutorPipeline。
KeepAlivePipeline是一個特例,它
并不是用來執行特定任務的,而是用來維護HTTP協議中的持久連接的狀態,例如維護最大的持久連接數,持久連接的timeout時間等。另外,異步的
socketChannel中缺少一個類似socket.setSoTimeout的函數,這個函數在保證服務器軟件的可靠性和安全性(抗DOS攻擊)
上,具有重要的作用。Grizzly是用KeepAlivePipeline類來模擬socket.setSoTimeout的作用。
2. SelectorThread
這是Grizzly的主要入口類,位于
com.sun.enterprise.web.connector.grizzly的包下。在SelectorThread
中,SocketChannel和Selector被創建并被初始化。當網絡有請求進來的時候,Selector會根據不同的請求類型和NIO的不同事件
進行不同的處理。
當NIO的事件為OP_READ的時候,表明是原有
的連接中有新的請求數據傳過來了。這類請求屬于ReadTask,應該交給負責處理ReadTask的處理器來處理。ReadTask有自己的
Pipeline(也就是線程池)來處理,這樣就不會占用主線程來處理Read的請求。
當NIO的事件為OP_ACCEPT的時候,表明是
有新的請求進來了,這類請求屬于AcceptTask,應該交給負責處理AcceptTask的處理器來處理。在老版本的GlassFish
中,AcceptTask也有自己的Pipeline來處理,這樣就讓AcceptTask在主線程以外的線程中執行。但是經過多次性能測試和比較,發現
當AcceptTask在主線程(SelectorThread)中執行的時候,性能最好。因此,在讀最新的Grizzly源代碼的時候,會發現圖
17-1中的AcceptPipeline根本不存在,因為AcceptTask已經由SelectThread類中HandleAccept函數來執行
了。
當ReadTask執行完以后,表明整個請求的數據
已經完全接收到,就可以進行請求處理了,請求處理屬于ProcessTask,交給負責處理ProcessTask的處理器來處理。
ProcessTask有自己的Pipeline(也就是線程池)來處理,這樣就不會占用主線程來處理請求。
3. Task
在Grizzly的框架中包含下面幾種任務。
(1)
AcceptTask:用于響應新的連接請求。前面已經說過,這個任務的類事實上已經不存在,沒有單獨抽象出來。因為處理Accept已經成為
SelectThread內部的一部分了。
(2)
ProcessTask:用于處理并且響應請求。這個任務通常是對請求的數據進行解析,解析完后再將請求傳遞給其他服務的容器(如Servlet容器)進
行真正的業務處理。
(3)
ReadTask:用于SocketChannel最初的讀取操作。由于NIO是非阻塞的操作,最初的讀取往往不能獲得全部的請求數據,這時
候,ReakTask會將任務委托給StreamAlgorithm,根據不同實現,用不同的方法將剩下的請求數據獲取。
在
com.sun.enterprise.web.connector.grizzly.algorithms的包下,Grizzly默認實現了4個算法:
l?? ContentLengthAlgorithm
l?? SeekHeaderAlgorithm
l?? StateMachineAlgorithm
l?? NoParsingAlgorithm
前3個算法主要是圍繞HTTP請求中的
Content-length字段來進行解析。只要能讀到這個字段的值,那么我們就可以預先判斷整個請求的長度,從而確定什么時候完成請求讀取,接著進行
請求處理了。第4個算法是對請求數據根本不進行預處理,假設所有的數據都讀進來了。如果最后發現請求數據讀得不完全,再交給請求處理任務
(ProcessTask)來負責將剩下的內容讀取過來。
17.2.2? 源碼閱讀指南
根據圖17-1的結構,結合Grizzly的源代
碼,可以看到Grizzly的大致脈絡。
SelectorThread是個入口,根據
Grizzly所在的不同環境,啟動的方法有所不同。如果Grizzly作為單獨可運行的應用(Grizzly可以從GlassFish中獨立出來),在
com.sun.enterprise.
web.connector.grizzly.standalone包下的Main類是這樣使用SelectorThread的:
【例17.4】單獨運行的Grizzly對
SelectorThread的調用:
private static void start(String args[]) throws Exception
{
…
SelectorThread selectorThread = null;
String selectorThreadClassname =
System.getProperty(SELECTOR_THREAD);
if (selectorThreadClassname != null){
selectorThread =
loadInstance(selectorThreadClassname);
} else {
selectorThread = new SelectorThread();
}
selectorThread.setPort(port);
StaticResourcesAdapter adapter = new
StaticResourcesAdapter();
adapter.setRootFolder(folder);
selectorThread.setAdapter(adapter);
selectorThread.setDisplayConfiguration(true);
selectorThread.initEndpoint();
selectorThread.startEndpoint();
}
如果Grizzly是在GlassFish中,它作
為服務線程,run()方法是整個線程啟動的鑰匙。從源碼中很容易看出在run()方法中調用了startEndpoint()方
法,startEndpoint()在做好一些準備工作之后,調用了startListener()。startListener()便進入了主線程的循
環之中。在循環中只有一個方法,那就是doSelect()方法。
在doSelect()中,可以很清楚地看到NIO
的框架結構。
【例17.5】SelectorThread中的
doSelect():
selectorState = selector.select(selectorTimeout);
…
readyKeys = selector.selectedKeys();
iterator = readyKeys.iterator();
while (iterator.hasNext()) {
key = iterator.next();
iterator.remove();
if (key.isValid()) {
handleConnection(key);
} else {
cancelKey(key);
}
}
與大多數NIO的架構一樣,先是調用
selector.select(selectorTimeout),看看當前的頻道有沒有數據準備好了。如果有的話,通過
selector.selectedKeys()將準備好的這些頻道的SelectionKey取到。對這些頻道的處理就交給
handleConnection(key)函數了。
【例17.6】SelectorThread中的
handleConnection:
protected void
handleConnection(SelectionKey key) throws
IOException,InterruptedException
{
Task task = null;
if ((key.readyOps() &
SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT){
handleAccept(key);
return;
} else
if ((key.readyOps() & SelectionKey.OP_READ) ==
SelectionKey.OP_READ){
task =
handleRead(key);
}
if (((SocketChannel)key.channel()).isOpen()) {
task.execute();
} else {
cancelKey(key);
}
}
handleConnection
函數很短,但是有一些重要的特點需要指出來。handleConnection的主要功能是區分那些已經準備好的頻道,看看它們是屬于新的連接
(OP_ACCEPT)還是有新的請求數據(OP_READ)。
如果是
OP_ACCEPT,那么就調用函數handleAccept(key)。這個函數會在當前的線程內執行,主要的功能就是根據新來的連接創建新的頻道,再
將這個頻道注冊到Selector中。如果是OP_READ,那么就調用函數handleRead(key)。這個函數返回了一個Task。通過
task.execute()將這個任務的實際運行交給Pipeline中的線程池來執行。換句話說,對新的請求數據的處理是在另外的線程中來處理的,而
不是當前的線程。
事實上,在
早期的Grizzly的版本中,對OP_ACCEPT的處理與OP_READ一樣,也是有單獨的任務(AcceptTask)和單獨的線程來執行。但是經
過性能測試,證明當對OP_ACCEPT的處理在主線程的時候性能最好。因此就取消了AcceptTask在單獨線程中的處理,演化為當前的模型。
再隨后的工
作主要就交給ReadTask和ProcessTask去做了。這里不作詳細的介紹。
總結
以上是生活随笔為你收集整理的java grizzly_Grizzly简介 | Java Game的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSS3通用顶部固定导航栏代码
- 下一篇: java企业考勤,基于jsp的企业员工考