大数据搜索引擎 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 1010
、0100 0011 0001
、0100 0011
以及0100
(最高的4位),这四个索引相当于从trie的子节点一直到根节点
精度越高(precisionStep越小)索引就越大,查询速度越快;精度越低,索引越小,相对查询速度比较慢。
比如对于long类型的数据,precisionStep是4的时候,最多需要同时搜索465个term;precisionStep是2的时候,最多只要189个term。不过并不能绝对的说精度越高越好,因为查找这些term需要的时间也会相应增加。实际上最佳的precisionStep还是要根据业务情况测试得出。
上面根据precisionStep建立索引的过程中有一个特殊的分词器来帮助拆分,比如把423
拆成423
、42
以及4
。不过分词器会同样的把4
拆分成4
,那怎么区分423
的4
和4
的4
呢?
那就需要额外的空间来区分这两个4
,ES给出的解决方案是在这两个数字前面加上一个前缀shift
表示偏移量。比如423
的4
,shift
是2
(423
的42
的shift
是1
,423
的shift
是0
);而4
的4
,shift
是0
,所以前者的4
比后者的要大。分词之后的term在每次比较之前都会先比较shift
,shift
越大,相应的term也越大,避免的重复的问题。
总结上面建立索引的过程:当一个文档进来的时候,有一个数字423
需要建立索引,于是先把这个int数据转化成字符串,再用一个特殊的分词器根据精度把423
分成对应的三个term423
、42
和4
,并且附上对应的前缀shift
,接下来在trie中找到这几个term,把稳定的id添加到这几个term的文档id列表里面(如果不存在就创建这个term)。
查询原理
清楚了数字类型的数据的索引机制之后,范围查询的原理就比较简单了。
比如有一个范围[423, 642]
,要找到字段大于等于423并且小于等于642的文档。
先在索引的trie里面找到这两个term以及范围内的兄弟节点,分别是trie的两个叶节点423、641和642
从叶节点向上缩小范围,对两个数字分别除以10加一和减一之后查找范围为
[43,63]
,此时的shift
是1,得到这一层级的“叶子节点”以及范围内的兄弟节点是44和63再从这一层向上,两个数字除以10,分别加一减一,得到范围
[5,5]
,shift
为2,这就是最后的节点了,term是5上面三个步骤得到最后需要的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
还有更多好文章……
请查看历史和官网。
点击“在看”给作者个赞↓↓↓