elasticsearch是什么不再贅述。本文希望通過對ES源代碼進行一些簡單的分析,目的在于打通整個結構,了解整個運行方式,以及如果我需要對源代碼進行簡單的調整和修改我需要從哪里入手,拋磚引玉。本文選擇了elasticsearch2.3.5的版本來進行分析調試,適用于2.x的版本。
環境準備
這次分析調試過程會將elasticsearch的源碼check到本地后通過IDEA來分析,具體步驟可以通過搜索引擎獲得,我默認了你做好了我以下提及的準備。
- IDEA 用來啟動和調試es工程
- JDK1.8 這個版本的es可以用1.7或1.8編譯
-
elasticsearch2.3.5源代碼
完成環境準備后的效果如下圖所示
分析思路
我們分析的目的并不是無意義的閑讀,或者我認為沒有明確的需求和產出的閱讀只是在浪費我的時間。雖然我的時間也不值錢。
下面這張圖是我在閱讀之前的思考,雖然我并不是在閱讀之際就直接將它畫出來,但這個邏輯指導我做這一次的閱讀。
這些邏輯是可以在還沒有開始閱讀工作之前就需要想到的,在長期對es做二次開發工作后,我們可以很簡單的猜測出它會具有圖中所提到的功能點,那么接下來的工作就是帶著預先設定的猜測去尋找對應的實現。那么最容易做到也是最簡單的事情就是,發出一次最簡單的請求,然后去跟蹤這個請求所流轉的過程。es既然本身提供restful接口,從大層面來看,它也是一個web程序,這就方便了我們尋找切入口。
分析過程
首先在IDEA中把ES以DEBUG啟動模式啟動,本篇重點關注請求流轉的過程,即使我知道有在啟動準備中有很多工作。不過我還是希望先滿足自己的好奇心,我希望知道我提交給es一個請求后它是怎么走過它的生命周期最終落地形成結果返回給我的。
ES的API可以大致分為以下幾類
- 文檔API(Document APIs): 提供對文檔的增刪改查操作
- 搜索API(Search APIs): 提供對文檔進行某個字段的查詢
- 索引API(Indices APIs): 提供對索引進行操作
- 查看API(cat APIs): 按照更直觀的形式返回數據,更適用于控制臺請求展示
- 集群API(Cluster APIs): 對集群進行查看和操作的API
我選擇使用cat APIs來做本次DEBUG,因為我不需要其他額外的參數,而是可以簡單的使用curl或者在瀏覽器輸入url來發起我所需要的請求。
我首先在瀏覽器輸入了http://localhost:9200/_cat
瀏覽器給我以下回顯
=^.^=
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}
/_cat/nodeattrs
/_cat/repositories
/_cat/snapshots/{repository}
以上你可以看到所有cat的API都羅列展示出來。那么我們的目的就是跟蹤這一個請求,它在哪里被轉發最后被執行,都是我們所關心的。
首先我們需要尋找到一個可以下斷點的突破點。這一點比較需要經驗,首先ES是一個優秀的開源程序,那么我有足夠的理由相信,它會在命名規范上做的很出色,所以我的第一個想法肯定是通過觀察工程目錄結構來尋找我第一個BreakPoint的位置。
其實仔細觀察目錄結構,通過rest包名可以猜到這里大概會放有和restful服務相關的邏輯,點開包目錄很清楚的可以看到我們所熟悉的各類API的位置,其中也包括catAPI。再點開cat包目錄,好的,我們已經找到相關的類了。
其實我覺得源代碼閱讀這種事情切入點都是靠猜出來的 ——魯迅
可是還是很懵,到底是哪個呢?
仔細觀察可以發現,cat包下的類,和cat命令的功能是一一對應的。
比如/_cat/indices
其實很明顯的可以看得出來是RestIndicesAction,點進去我們也可以看到在構造函數中注冊的路徑
@Inject
public RestIndicesAction(Settings settings, RestController controller, Client client, IndexNameExpressionResolver indexNameExpressionResolver) {
super(settings, controller, client);
this.indexNameExpressionResolver = indexNameExpressionResolver;
controller.registerHandler(GET, "/_cat/indices", this);
controller.registerHandler(GET, "/_cat/indices/{index}", this);
}
同理你完全可以試試多點幾個類,就可以驚喜的看到這個路徑就這么直白的寫死在代碼中。同理,我很容易猜到,我剛才訪問的API對應執行的邏輯應該是會放在RestCatAction中。如以下
public class RestCatAction extends BaseRestHandler {
private static final String CAT = "=^.^=";
private static final String CAT_NL = CAT + "\n";
private final String HELP;
@Inject
public RestCatAction(Settings settings, RestController controller, Set<AbstractCatAction> catActions, Client client) {
super(settings, controller, client);
controller.registerHandler(GET, "/_cat", this);
StringBuilder sb = new StringBuilder();
sb.append(CAT_NL);
for (AbstractCatAction catAction : catActions) {
catAction.documentation(sb);
}
HELP = sb.toString();
}
@Override
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
channel.sendResponse(new BytesRestResponse(RestStatus.OK, HELP));
}
}
你甚至還能看到作者賣萌的表情。
有一個小tips是@Inject注解,這是谷歌的一個輕量級IOC框架提供的注解,我不了解,平時也是使用spring,但是這里其實無需深究它,可以大概的理解成在執行這個構造函數的時候會將所需要的變量注入到參數當中。
那我們把斷點下在了這個構造函數當中,然后我刷新了瀏覽器頁面,也就是重新發送了一遍請求。
很遺憾斷點沒有截獲這一次請求。
再思考了一下,既然是構造函數,那么是不是在程序啟動的過程中就已經執行過了?很好,那么我REDEBUG了ES。這次沒有辜負我的期望,斷點準確的截住了代碼。
也就是說這個功能API列表其實在程序啟動的時候就已經加載完畢,存儲在了HELP變量中。這其實可以理解,當程序啟動的時候就已經可以知道所有API的路徑,也可以看到構造函數中的循環是在循環請求所有API路徑并拼接到HELP變量中。
但是還是沒解決我們的問題,到底是在哪里執行請求的呢。
眼光瞄向了handleRequest方法??梢钥吹絟andleRequest上有@Override注解。說明這個方法是實現或者復寫了父類或者接口的方法。到這里我已經腦補出了結構,這是一個類似處理器一樣的邏輯,需要實現的功能類實現了接口中規定好的方法,然后將功能類注冊到一個處理器管理的區域由中心決定什么時候來調用處理器處理請求。
我們可以看一下這個類的UML圖。
非常明顯的邏輯,RestHandler規定了處理器必須要實現的方法,AbstractComponent抽取了關于日志處理的邏輯。在這一點上是值得我學習的,將所有日志處理抽出來而不是散落在程序當中。
這次我們斷點下在handleRequest中然后再次發送請求。bingo
我終于捕獲了我自己發出的請求。
那么將剛才所做的事情都套用到同包的其他類中就可以找到所有API的具體邏輯,這就在如果有修改API需求的時候可以發揮用處了。在這個最簡單的API中實現邏輯就是簡單的返回預先加載好的字符串,但無論在哪個API類中都會在handleRequest中實現它的邏輯。
小結
本次分析我抱著如果我需要修改執行邏輯的時候我應該去哪里改的目的來尋找,解決了一部分問題也發現了很多新問題,主要的難點有以下一些問題:
- ES幾乎沒有借助多少外部依賴,它僅用netty就實現了一個內部的web框架來管理請求的轉發,給一開始的尋找切入口帶來了困難。
- 我在尋找我自己的請求到底去哪的時候其實沒有文章中描述的這么輕松。。
- ES本身內部的注釋也不多,有時候很難理解作者的意思
基本的需求滿足后我現在希望關注在ES做為一個優秀的開源軟件,它的結構是如何設計的,如何解耦以及如何讓代碼優雅。下一篇我希望繼續來分析ES源碼中的一些“概念”。