#1 gecko概述react
最近在研究metaq消息隊列,它裏面用到的NIO通訊框架是gecko,文檔是這麼描述的數組
Gecko是一個Java NIO的通信組件,它在一個輕量級的NIO框架的基礎上提供了更高層次的封裝和功能。 支持的RPC調用方式包括RR(request-response)和pipeline。
本文就按照平常NIO通訊框架和RPC所面臨的問題來看下gecko是怎麼解決和實現的服務器
#2 gecko實現NIO通訊框架多線程
##2.1 NIO類庫的使用併發
像Netty、Mina這種NIO通訊框架都是不使用Jdk自帶的NIO類庫,本身重寫NIO類庫。而gecko則是使用的是jdk自帶的NIO類庫,具體的差異,我如今還不太瞭解負載均衡
##2.2 線程模型的選擇框架
先簡單描述下NIO形式下的線程模型,具體參考這篇文章infoq:Netty系列之Netty線程模型異步
比較簡單,每來一個鏈接就建立一個線程來處理。這時候最大線程數就成了鏈接數的限制。oop
使用一個線程,使用Selector來監聽鏈接的創建和鏈接的讀寫事件,而後仍然是該線程去完成事件的處理。url
使用一個主線程,該線程的Selector只負責接收鏈接的創建,創建鏈接後將該鏈接的讀寫事件註冊到其餘線程的Selector,目前大部分都是採用這種方式。
下面再舉幾個例子:ZooKeeper通訊使用的線程模型、Netty、Gecko
###2.2.1 ZooKeeper通訊使用的線程模型
ZooKeeper通訊使用的線程模型就比較簡單,默認使用上述Reacter單線程模型。而且是採用jdk自帶的NIO類庫來實現的。
服務器端開啓一個線程,建立出一個Selector,在該線程中不斷的檢測鏈接的創建和讀寫事件。代碼大體以下
一旦有客戶端創建鏈接,則獲取SocketChannel,並設置非阻塞,註冊到seclector上,綁定到SelectionKey
一旦是讀寫事件則從SelectionKey中取出進行數據的讀寫
上述事件都是由一個線程來完成的,要求不高的狀況下就能夠知足了。如大部分框架使用ZooKeeper的場景都是做爲一個協調服務,訪問量很小的。若是ZooKeeper承載了不少不少的服務,上述單線程通訊方式知足不了了,就須要使用Reactor多線程模型,如採用netty做爲NIO通信框架。
###2.2.2 Netty採用的線程模型
能夠簡單就說成採用Reactor多線程模型。會有一個boss線程池和worker線程池。boss線程池上的Selector專門負責鏈接的創建,一旦鏈接創建起來以後,從worker線程池上獲取一個線程(獲取過程實現簡單的負載均衡),將鏈接註冊到該線程上的Selector上,讓worker線程池負責讀寫操做,具體以下:
1 每個NioEventLoop:包含一個線程和一個Selector selector複用器
所以一個NioEventLoop就能夠處理不少鏈路,每一個鏈路的讀寫操做所有交給這一個線程來處理,避免了併發操做同一個鏈路的可能性
2 每當有一個新的客戶端接入,則從NioEventLoop線程組中順序獲取一個可用的NioEventLoop,當到達數組上限以後,從新返回到0,通 過這種方式,能夠基本保證各個NioEventLoop的負載均衡。一個客戶端鏈接只註冊到一個NioEventLoop上,這樣就避免了多個IO線程去 併發操做它
###2.2.3 Gecko採用的線程模型
也是採用的是Reactor多線程模型。有一個SelectorManager,它含一個Reactor[] reactorSet,每個Reactor都是一個線程,而且擁有本身的Selector,和上述netty的NioEventLoop差很少。
SelectorManager將第一個Reactor專門用做接收客戶端鏈接的做用,鏈接創建起來以後,將鏈接註冊到其他的Reactor的Selector上。 在選擇Reactor也是採用簡單的順序輪訓的策略。
SelectorManager代碼以下:
選擇Reactor代碼以下:
上述netty中,有關某個鏈接的讀寫事件所有交給了該鏈接註冊的worker線程上,對某個鏈接的操做只會在同一個線程中進行,避免了併發操做,可是每一個線程必須完成當前的讀寫操做後才能去執行接下來的另外的讀寫事件。
而Gecko則是將讀寫事件交給了專門的讀寫線程池,這樣的話Reactor線程只管發出讀寫任務(一個Runnable對象),真正的讀寫操做交給讀寫線程池來完成,Reactor線程處理事件的併發量就大了,可是這樣的話就是就可能出現多個IO線程併發操做同一個鏈接,加大了出錯的風險。
##2.3 編解碼和序列化反序列化的處理
這一部分就須要處理粘包問題、採用的協議。不一樣的協議就要使用不一樣編解碼器,因此文章開頭所說的可插拔的協議設計就是指用戶能夠本身指定本身協議所使用的編解碼器
就來詳細看下處理粘包的過程:
1 從SocketChannel中取出所有的可讀數據,將數據寫到一個buffer中,若是buffer承載不下,暫時就不讀了,先處理buffer中的數據,處理完成以後再來寫剩餘的數據到buffer中
2 將buffer執行flip操做,由寫狀態進入讀狀態
3 buffer中有不少數據,多是客戶端發送過來的多條消息數據,而目前採用的大部分協議都是固定長度的Header加Body的形式,Header中指明瞭Body的長度。這樣的話,肯能就存在幾種狀況:
3.3 Header中讀取完畢,就知道了Body的長度,還須要判斷下buffer中剩餘可讀數據的長度是否大於剛纔解析出的Header中給出的Body的長度,若是不足的話,此時有兩種方式:
一種就是回溯所讀取的數據,在解析Header以前,先保留當前的index,解析header以後,發現buffer中剩餘長度不足則將buffer重置讀取位置到上述index。
另外一種就是將解析出來的Header信息先綁定到鏈接上,每次解析數據前,先從鏈接中獲取Header信息,若是有,說明上次已經解析完Header信息了,可是Body中信息不足,那就直接開始解析Body了
第一種方式:一旦出現Body信息不足,就回溯了,當信息充足後,會重複解析Header中的內容。第二種方式則不會進行重複解析
而dubbo中處理方式就是採用的第一種,保留一個index。一旦消息不完整則回退到該index。
目前Gecko就是採用的第二種方式,將Header先暫時保存在鏈接中
來詳細看下dubbo中的處理方式:
再詳細看下Gecko對粘包的處理方式:
buffer中多條消息的處理:
來看下一個解碼器的實現,啓動不一樣場景下的Server使用不一樣的編解碼器,如metaq命令行方式就是使用以下編解碼器,專門用於處理請求命令的:
對於RPC,不只要進行編解碼,同時還要涉及序列化和發序列化的問題:
dubbo中都是經過url中參數值來定製化編解碼器和序列化反序列化方式,提供了各類豐富多樣的編解碼和序列化反序列化方式,而Gecko實現的RPC也能夠實現定製化。默認方式是使用jdk自帶的序列化和反序列化方式,即ObjectInputStream方式,簡單代碼以下
Gecko RPC解碼過程:
Gecko RPC序列化過程:
#3 後續
重連管理、同步轉異步、異步回調、分組管理和負載均衡以後再說