原理分析
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中恢复原始数据的。