vlambda博客
学习文章列表

大数据搜索引擎 Elasticsearch 数字类型(numeric)数据的处理与搜索原理。

关注新技术,学习新知识!

Elasticsearch专为字符串搜索而生,在建立索引的时候针对字符串进行了非常多的优化,在对字符串进行准确匹配或者前缀匹配等匹配的时候效率是很高的。ES底层把所有的数据都会当成字符串,其中就包括数字——所有的数字在ES底层都是会以字符串的形式保存。


数字的索引是什么样子

查询term数量太多的问题,解决方案就是用空间换时间,通过前缀聚合部分的term来达到。

这里的聚合的实现方式是采用trie的数据结构,比如445、446和448这个三个term,可以聚合到44这个term的下面,节点44包含的文档的id列表应该是所有子节点的并集,这样原先需要的11个term就可以减少2个。同理对于其他的term也进行合并,合并之后[423,642]查询就只需要6个term,效率提高了一倍!

然而聚合也是要讲道理的,把445、446和448聚合到44以及把44聚合到4相当于是把数字除以10,精度就是10。但是并不是一直都希望这个精度是10,也可以设置为100(精度相对应的降低,节约索引空间)等等。ES提供了precisionStep来定制化这个精度,不过不是针对十进制,而是二进制的位数。比如precisionStep设置为4,那么在二进制位里面每隔4位(相当于十进制的16)就建立一个前缀聚合索引。

比如对于二进制数字0100 0011 0001 1010,当precisionStep为4的时候,会建立4个索引——0100 0011 0001 10100100 0011 00010100 0011以及0100(最高的4位),这四个索引相当于从trie的子节点一直到根节点

精度越高(precisionStep越小)索引就越大,查询速度越快;精度越低,索引越小,相对查询速度比较慢。

比如对于long类型的数据,precisionStep是4的时候,最多需要同时搜索465个term;precisionStep是2的时候,最多只要189个term。不过并不能绝对的说精度越高越好,因为查找这些term需要的时间也会相应增加。实际上最佳的precisionStep还是要根据业务情况测试得出。

上面根据precisionStep建立索引的过程中有一个特殊的分词器来帮助拆分,比如把423拆成42342以及4。不过分词器会同样的把4拆分成4,那怎么区分423444呢?

那就需要额外的空间来区分这两个4,ES给出的解决方案是在这两个数字前面加上一个前缀shift表示偏移量。比如4234shift242342shift1423shift0);而44shift0,所以前者的4比后者的要大。分词之后的term在每次比较之前都会先比较shiftshift越大,相应的term也越大,避免的重复的问题。

总结上面建立索引的过程:当一个文档进来的时候,有一个数字423需要建立索引,于是先把这个int数据转化成字符串,再用一个特殊的分词器根据精度把423分成对应的三个term423424,并且附上对应的前缀shift,接下来在trie中找到这几个term,把稳定的id添加到这几个term的文档id列表里面(如果不存在就创建这个term)。


查询原理

清楚了数字类型的数据的索引机制之后,范围查询的原理就比较简单了。

比如有一个范围[423, 642],要找到字段大于等于423并且小于等于642的文档。

  1. 先在索引的trie里面找到这两个term以及范围内的兄弟节点,分别是trie的两个叶节点423、641和642

  2. 从叶节点向上缩小范围,对两个数字分别除以10加一和减一之后查找范围为[43,63],此时的shift是1,得到这一层级的“叶子节点”以及范围内的兄弟节点是44和63

  3. 再从这一层向上,两个数字除以10,分别加一减一,得到范围[5,5]shift为2,这就是最后的节点了,term是5

  4. 上面三个步骤得到最后需要的term是423、44、5、63、641和642

上面是用十进制举得一个例子,在二进制里面也是同样的道理,这里就不啰嗦了。实际上在ES实现里面用了很多位操作,效率相比于使用十进制要高很多,感兴趣的同学可以去看源代码,在LegacyNumericUtils类的splitRange方法里面。


总结

总的来说,Elasticsearch对于数字类型数据的索引和搜索不同于传统的MySQL或者Java等编程语言,采用了独特的字符串存储以及Trie数据结构保存索引的方式。

ES先将输入的数字进行预处理,把float和double分别映射成int和long,原来是int和long类型的则保持不变

然后把输入的整型数字切分成许多组由最长7位二进制数字组成的二进制串,每组二进制数字都是一个Unicode字符,整体连起来变成一个Unicode字符串

接下来根据precisionStep把这个字符串数字分词成很多term,并附上前缀shift

根据这些term建立索引词典,词典的结构类似于一个trie

范围查询的时候根据trie把所谓的范围区间划分成离散的term字符串,这些term指向的文档的并集就是范围查询的结果。

--

知识分享,时代前行!

~~ 时代Java

还有更多好文章……

请查看历史和官网。

点击“在看”给作者个赞↓↓↓