Elasticsearch最近一段時間非常火,以致於背后的公司都改名為Elastic了,因為Elasticsearch已經不僅限於搜索,反而更多的用在大數據分析場景,所以在公司品牌上開始“去Search化”。這得益於其強大的支持聚合分析的Query DSL,雖然這個DSL的語法有點復雜,但底層的技術確實牛B,分布式的快速分析引擎,Elasticsearch已經占有一席之地。
大家知道,搜索引擎的基本數據結構是反向索引,也就是為每個關鍵詞建立了到文檔的映射,然后所有的關鍵詞是一個有序列表。搜索的時候,只要先從有序列表中匹配到關鍵詞,就能搜索到包含該關鍵詞的所有文檔,反向索引的數據結構對於關鍵詞搜索的場景是非常高效的。
但聚合分析和搜索有很大的不同。典型的場景,比如計算某個文檔中每個關鍵詞的出現次數,反向索引就無能為力了,需要先掃描整個關鍵詞映射表,才能找到該文檔包含的所有關鍵詞,然后再進行聚合統計(這個例子其實不太准確,因為Lucene在反向索引中冗余了詞頻的信息,用於計算搜索相關度),也就是要對整個反向索引做全掃描,在數據量大的時候,性能當然好不到哪里去。
所以,Elasticsearch為聚合計算引入了名為fielddata的數據結構,其實就是根據反向索引再次反向出來的一個正向索引,也就是文檔到關鍵詞的映射。因為聚合計算也好,排序也好,通常是針對某些列的,實際上生成的是文檔到field的多個列式索引,所以叫做fielddata。這樣對文檔內的關鍵詞做聚合計算的時候,就只要從fielddata中根據文檔ID查找就好。而且,fielddata是保存在內存中的,好處是不占用存儲,壞處么,當然上內存不夠用啦。而且這個內存是從JVM的Heap上分配的,因為JVM對於大內存的垃圾收集的影響,不能不說對穩定性有很大的挑戰,數據量大的時候,時不時的OutOfMemory也不是鬧着玩的。因為內存是有限的,所以不可能預先為所有的字段都建立fielddata,只能是由具體的搜索需求來觸發。如果是未命中的搜索,還需要先在內存中建立fielddata,這會影響到響應時間。
fielddata的問題在於內存的有限性和JVM對於大內存的垃圾收集對系統帶來的穩定性挑戰。所以后來又引入了一個新的機制,就是DocValues,從數據結構上來說,它和fielddata是一樣的按列的正向索引,但是實現方式不同,DocValues是持久化存儲在文件中,並且是預先構建的,也就是數據進入到Elasticsearch時,就會同時生成反向索引和DocValues,這會消耗額外的存儲空間,但對於JVM的內存需求會大幅度減少,剩余的內存可以留給操作系統的文件緩存使用。加上DocValues是預先構建的,查詢時也免去了不命中時構建fielddata的時間,所以總體來看,DocValues只比內存fielddata慢大概10~25%,穩定性則有了大幅度提升。從Elasticsearch2.0開始,除了分詞過的字符串字段,其他字段已經默認生成DocValues了(可以在索引的Mapping中通過doc_values布爾值來設置)。
簡單的說,Elasticsearch通過反向索引做搜索,通過DocValues列式存儲做分析,將搜索和分析的場景統一到了通一個分布式系統中,還是很有搞頭的。不過分析不僅僅是聚合,這也是Elasticsearch還需要繼續努力的方向,目前通過Elasticsearch-Hadoop項目,可以將Elasticsearch的搜索結果做為Spark的RDD,利用Spark做更深度的分析。未來如果分布式計算這一層能夠和Spark這樣的計算框架再進一步做深度的融合,恐怕有可能成為大數據領域內的另外一個大殺器。
袋鼠雲正在基於Elasticsearch+Spark來做一些有意思的大數據產品,歡迎對Elasticsearch和Spark有深入研究的大牛們加入或者交流。