原理分析
es中有個隱藏的字段_source,這個字段中存了其他字段的內容,我們直接查詢es返回的結果中展示的各個字段的值其實就是從_source字段中讀取的。如果想要對一個字段只建索引,不做存儲。就是不把這個字段的值存在_source字段中,這樣查詢結果中就不會顯示該字段的內容。如下圖所示:
從test3的mapping信息中可以看出 name,count字段是不存儲字段內容的。
因此,我們查詢test3中的返回結果中只有age字段的值,沒有name,count字段的值。
那麼如何恢復索引中不存儲字段name,count的值呢?
其實es未每個字段都默認開啓了docValues。docValue中存儲了字段的正排索引,即DocId -> 字段內容的映射。在每條數據插入索引的同時會創建docValues,docValues的內容存在es的數據目錄中的dvd和dvm文件中。由於docValues中存儲着正排索引,因此理論上我們也可以從中獲取字段的內容,包括不存儲字段的內容。
es是基於Lucene開發的,es的數據文件就是lucene文件,es一個分片就是lucene的一個索引。因此我們可以利用lucene提供的API去解析es的分片數據,從中DocValues中恢復不存儲字段的內容。
實現
本文是基於es2.3.5版本的,它對應的lucene版本是5.5.0。因此實現代碼中使用的lucene版本是5.5.0。
1、創建索引
首先我們創建一個索引test3
mapping信息已經在上面貼出來了,爲了方便分析這個索引只有1個分片,0個副本。有3個字段:name(string not_analyzed),age(integer),count(integer)。其中name,count是不存儲的。我們的目標是恢復name,count的數據。
2、將分片文件夾拷貝出來
分片文件夾的位置:/mnt/disk1/data/es1/escluster/nodes/0/indices/test3/0
j將這個目錄拷貝到本機中:F:\workspace\HKBIgData\tmp\0
3、pom文件的依賴
<dependencies>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>5.5.0</version>
</dependency>
<!--一般分詞器,適用於英文分詞-->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>5.5.0</version>
</dependency>
<!--中文分詞器-->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>5.5.0</version>
</dependency>
<!--對分詞索引查詢解析-->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>5.5.0</version>
</dependency>
<!--檢索關鍵字高亮顯示-->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>5.5.0</version>
</dependency>
</dependencies>
4、實現代碼
import org.apache.lucene.index.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.nio.file.Paths;
public class Test {
public static void main(String[] args) throws Exception {
String toWrite;
// 創建輸出文件
File file = new File("F:\\workspace\\HKBIgData\\tmp\\result.txt");
if (!file.exists()) {
file.createNewFile();
}
FileWriter fileWriter = new FileWriter(file, true);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
Directory directory = FSDirectory.open(Paths.get("F:\\workspace\\HKBIgData\\tmp\\0\\index"));
//讀取索引文件
DirectoryReader reader= DirectoryReader.open(directory);
// 獲取segment數量
int size = reader.leaves().size();
// 遍歷每個segment,讀取docValue中的數據
for (int i = 0; i < size; i++) {
// 獲取字段的DocValue
SortedNumericDocValues db = DocValues.getSortedNumeric(reader.leaves().get(i).reader(), "count");
SortedNumericDocValues db2 = DocValues.getSortedNumeric(reader.leaves().get(i).reader(), "age");
RandomAccessOrds str = (RandomAccessOrds)DocValues.getSortedSet(reader.leaves().get(i).reader(), "name");
System.out.println("segment-------------" + i);
// 逐條從docValue中解析出數據並打印出來,string 類型和數字類型的處理不一樣
for (int j=0; j< str.getValueCount();j++) {
db.setDocument(j);
db2.setDocument(j);
str.setDocument(j);
toWrite = "age: "+db2.valueAt(j) + "|" + "count: "+db.valueAt(j) + "|" + "name: "+str.lookupOrd(str.ordAt(j)).utf8ToString();
// 打印結果
System.out.println(toWrite);
// 將結果寫入輸出文件中
bufferedWriter.write(toWrite+"\n");
bufferedWriter.flush();
}
}
bufferedWriter.close();
reader.close();
System.out.println("ending!!!");
}
}
5、校驗結果
執行上面的程序後,生成的result.txt文件就是恢復後的數據了,打開文件:
可以看到生成數據的總數量爲860003,這和索引中的數據量是一致的。我們在看看每行數據的各個字段是否對應正確。
從結果中取出最後一條數據 name: Tn5fuNK1ICgNiX1ga15lL0Q2lN1gD4 去es中查詢
對比後可以看到,各個字段的數據是對應的。
總結
從docValues中恢復數據的實現並不難,寥寥幾行代碼就能完成。難點主要在於不知道如何正確使用lucene API去文件中恢復數據。由於之前分析過es聚合過程的源碼,裏面就有用lucene API讀取docvalues的代碼。因此才從中知道如何正確使用lucene API的。 另外有幾點注意事項:
1、本文是基於es2.3.5版本的,不同的es版本對於的lucene API版本也不同,API的使用方法或許會有所變化。
2、對於string analyzed 類型的字段,由於入索引的時候經過分詞,因此是不能從docValue中恢復原始數據的。