早先es的http協定支援還是依賴jetty的,現在不管是rest還是rpc都是直接基于netty了。
另外值得一提的是,es 是使用google的guice 進行子產品管理,是以了解guice的基本使用方式有助于你了解es的代碼組織。
es 的啟動類是 org.elasticsearch.bootstrap.bootstrap。在這裡進行一些配置和環境初始化後會啟動org.elasticsearch.node.node。node 的概念還是蠻重要的,節點的意思,也就是一個es執行個體。rpc 和 http的對應的監聽啟動都由在該類完成。
node 屬性裡有一個很重要的對象,叫client,類型是 nodeclient,我們知道es是一個叢集,是以每個node都需要和其他的nodes 進行互動,這些互動則依賴于nodeclient來完成。是以這個對象會在大部分對象中傳遞,完成相關的互動。
先簡要說下:
nettytransport 對應rpc 協定支援
nettyhttpservertransport 則對應http協定支援
首先,nettyhttpservertransport 會負責進行監聽http請求。通過配置http.netty.http.blocking_server 你可以選擇是nio還是傳統的阻塞式服務。預設是nio。該類在配置pipeline的時候,最後添加了httprequesthandler,是以具體的接受到請求後的處理邏輯就由該類來完成了。
httprequesthandler 實作了标準的 messagereceived(channelhandlercontext ctx, messageevent e) 方法,在該方法中,httprequesthandler 會回調nettyhttpservertransport.dispatchrequest方法,而該方法會調用httpserveradapter.dispatchrequest,接着又會調用httpserver.internaldispatchrequest方法(額,好吧,我承認嵌套挺深有點深):
這個方法裡我們看到了plugin等被有限處理。最後請求又被轉發給 restcontroller。
restcontroller 大概類似一個微型的controller層架構,實作了:
存儲了 method + path -> controller 的關系
提供了注冊關系的方法
執行controller的功能。
那麼各個controller(action) 是怎麼注冊到restcontroller中的呢?
在es中,rest*action 命名的類的都是提供http服務的,他們會在restactionmodule 中被初始化,對應的構造方法會注入restcontroller執行個體,接着在構造方法中,這些action會調用controller.registerhandler 将自己注冊到restcontroller。典型的樣子是這樣的:
每個rest*action 都會實作一個handlerequest方法。該方法接入實際的邏輯處理。
首先是會把 請求封裝成一個searchrequest對象,然後交給 nodeclient 執行。
如果用過es的nodeclient java api,你會發現,其實上面這些東西就是為了暴露nodeclient api 的功能,使得你可以通過http的方式調用。
我們先跑個題,在es中,transport*action 是比較核心的類集合。這裡至少有兩組映射關系。
第一層映射關系由類似下面的代碼在actionmodule中完成:
第二層映射則在類似 searchservicetransportaction 中維護。目前看來,第二層映射隻有在查詢相關的功能才有,如下:
searchservicetransportaction 可以看做是searchservice進一步封裝。其他的transport*action 則隻調用對應的service 來完成實際的操作。
對應的功能是,可以通過action 找到對應的transportaction,這些transportaction 如果是query類,則會調用searchservicetransportaction,并且通過第二層映射找到對應的handler,否則可能就直接通過對應的service完成操作。
下面關于rpc調用解析這塊,我們會以查詢為例。
前面我們提到,rest接口最後會調用nodeclient來完成後續的請求。對應的代碼為:
這裡的action 就是我們提到的第一層映射,找到transport*action.如果是查詢,則會找到transportsearchaction。調用對應的doexecute 方法,接着根據searchrequest.searchtype找到要執行的實際代碼。下面是預設的:
我們看到transport*action 是可以嵌套的,這裡調用了
在asyncaction中完成三個步驟:
query
fetch
merge
為了分析友善,我們隻分析第一個步驟。
這是asyncaction 中執行query的代碼。我們知道es是一個叢集,是以query 必然要發到多個節點去,如何知道某個索引對應的shard 所在的節點呢?這個是在asyncaction的父類中完成,該父類分析完後會回調子類中的對應的方法來完成,譬如上面的sendexecutefirstphase 方法。
說這個是因為需要讓你知道,上面貼出來的代碼隻是針對一個節點的查詢結果,但其實最終多個節點都會通過相同的方式進行調用。是以才會有第三個環節 merge操作,合并多個節點傳回的結果。
其實會調用transportservice的sendrequest方法。大概值得分析的地方有兩個:
我們先分析,如果是本地的節點,則sendlocalrequest是怎麼執行的。如果你跑到senlocalrequest裡去看,很簡單,其實就是:
reg 其實就是前面我們提到的第二個映射,不過這個映射其實還包含了使用什麼線程池等資訊,我們在前面沒有說明。
這裡 reg.gethandler == searchservicetransportaction.searchquerytransporthandler,是以messagereceived 方法對應的邏輯是:
這裡,我們終于看到searchservice。 在searchservice裡,就是整兒八景的lucene相關查詢了。這個我們後面的系列文章會做詳細分析。
如果不是本地節點,則會由nettytransport.sendrequest 發出遠端請求。假設目前請求的節點是a,被請求的節點是b,則b的入口為messagechannelhandler.messagereceived。在nettytransport中你可以看到最後添加的pipeline裡就有messagechannelhandler。我們跑進去messagereceived 看看,你會發現基本就是一些協定解析,核心方法是handlerequest,接着就和本地差不多了,我提取了關鍵的幾行代碼:
這裡被requesthandler包了一層,其實内部執行的就是本地的那個。requesthandler 的run方法是這樣的:
這個就和前面的sendlocalrequest裡的一模一樣了。
到目前為止,我們知道整個es的rest/rpc 的起點是從哪裡開始的。rpc對應的endpoint 是messagechannelhandler,在nettytransport 被注冊。rest 接口的七點則在nettyhttpservertransport,經過層層代理,最終在restcontroller中被執行具體的action。 action 的所有執行都會被委托給nodeclient。 nodeclient的功能執行單元是各種transport*action。對于查詢類請求,還多了一層映射關系。