这段时间在维护产品的搜索功能,每次在管理台看到 elasticsearch
这么高效的查询效率我都很好奇他是如何作到的。算法
这甚至比在我本地使用 MySQL
经过主键的查询速度还快。数据库
为此我搜索了相关资料:json
这类问题网上不少答案,大概意思呢以下:数组
Lucene
的全文检索引擎,它会对数据进行分词后保存索引,擅长管理大量的索引数据,相对于 MySQL
来讲不擅长常常更新数据及关联查询。说的不是很透彻,没有解析相关的原理;不过既然反复提到了索引,那咱们就从索引的角度来对比下二者的差别。markdown
先从 MySQL
提及,索引这个词想必你们也是烂熟于心,一般存在于一些查询的场景,是典型的空间换时间的案例。数据结构
如下内容以 Innodb 引擎为例。
复制代码
假设由咱们本身来设计 MySQL
的索引,大概会有哪些选择呢?elasticsearch
首先咱们应当想到的是散列表,这是一个很是常见且高效的查询、写入的数据结构,对应到 Java
中就是 HashMap
工具
这个数据结构应该不须要过多介绍了,它的写入效率很高O(1)
,好比咱们要查询 id=3
的数据时,须要将 3 进行哈希运算,而后再这个数组中找到对应的位置便可。oop
但若是咱们想查询 1≤id≤6
这样的区间数据时,散列表就不能很好的知足了,因为它是无序的,因此得将全部数据遍历一遍才能知道哪些数据属于这个区间。性能
有序数组的查询效率也很高,当咱们要查询 id=4
的数据时,只须要经过二分查找也能高效定位到数据O(logn)
。
同时因为数据也是有序的,因此天然也能支持区间查询;这么看来有序数组适合用作索引咯?
天然是不行,它有另外一个重大问题;假设咱们插入了 id=2.5
的数据,就得同时将后续的全部数据都移动一位,这个写入效率就会变得很是低。
既然有序数组的写入效率不高,那咱们就来看看写入效率高的,很容易就能想到二叉树;这里咱们以平衡二叉树为例:
因为平衡二叉树的特性:
左节点小于父节点、右节点大于父节点。
因此假设咱们要查询 id=11
的数据,只须要查询 10—>12—>11
便能最终找到数据,时间复杂度为O(logn)
,同理写入数据时也为O(logn)
。
但依然不能很好的支持区间范围查找,假设咱们要查询5≤id≤20
的数据时,须要先查询10节点的左子树再查询10节点的右子树最终才能查询到全部数据。
致使这样的查询效率并不高。
跳表可能不像上边提到的散列表、有序数组、二叉树那样平常见的比较多,但其实 Redis
中的 sort set
就采用了跳表实现。
这里咱们简单介绍下跳表实现的数据结构有何优点。
咱们都知道即使是对一个有序链表进行查询效率也不高,因为它不能使用数组下标进行二分查找,因此时间复杂度是o(n)
但咱们也能够巧妙的优化链表来变相的实现二分查找,以下图:
咱们能够为最底层的数据提取出一级索引、二级索引,根据数据量的不一样,咱们能够提取出 N 级索引。
当咱们查询时即可以利用这里的索引变相的实现了二分查找。
假设如今要查询 id=13
的数据,只须要遍历 1—>7—>10—>13
四个节点即可以查询到数据,当数越多时,效率提高会更明显。
同时区间查询也是支持,和刚才的查询单个节点相似,只须要查询到起始节点,而后依次日后遍历(链表有序)到目标节点便能将整个范围的数据查询出来。
同时因为咱们在索引上不会存储真正的数据,只是存放一个指针,相对于最底层存放数据的链表来讲占用的空间即可以忽略不计了。
但其实 MySQL
中的 Innodb
并无采用跳表,而是使用的一个叫作 B+
树的数据结构。
这个数据结构不像是二叉树那样大学老师当作基础数据结构常常讲到,因为这类数据结构都是在实际工程中根据需求场景在基础数据结构中演化而来。
好比这里的 B+
树就能够认为是由平衡二叉树演化而来。
刚才咱们提到二叉树的区间查询效率不高,针对这一点即可进行优化:
在原有二叉树的基础上优化后:全部的非叶子都不存放数据,只是做为叶子节点的索引,数据所有都存放在叶子节点。
这样全部叶子节点的数据都是有序存放的,便能很好的支持区间查询。
只须要先经过查询到起始节点的位置,而后在叶子节点中依次日后遍历便可。
当数据量巨大时,很明显索引文件是不能存放于内存中,虽然速度很快但消耗的资源也不小;因此 MySQL
会将索引文件直接存放于磁盘中。
这点和后文提到 elasticsearch 的索引略有不一样。
因为索引存放于磁盘中,因此咱们要尽量的减小与磁盘的 IO(磁盘 IO 的效率与内存不在一个数量级)
经过上图能够看出,咱们要查询一条数据至少得进行 4 次IO,很明显这个 IO 次数是与树的高度密切相关的,树的高度越低 IO 次数就会越少,同时性能也会越好。
那怎样才能下降树的高度呢?
咱们能够尝试把二叉树变为三叉树,这样树的高度就会降低不少,这样查询数据时的 IO 次数天然也会下降,同时查询效率也会提升许多。
这其实就是 B+ 树的由来。
其实经过上图对 B+树
的理解,也能优化平常工做的一些小细节;好比为何须要最好是有序递增的?
假设咱们写入的主键数据是无序的,那么有可能后写入数据的 id 小于以前写入的,这样在维护 B+树
索引时便有可能须要移动已经写好数据。
若是是按照递增写入数据时则不会有这个考虑,每次只须要依次写入便可。
因此咱们才会要求数据库主键尽可能是趋势递增的,不考虑分表的状况时最合理的就是自增主键。
总体来看思路和跳表相似,只是针对使用场景作了相关的调整(好比数据所有存储于叶子节点)。
MySQL
聊完了,如今来看看 Elasticsearch
是如何来使用索引的。
在 ES 中采用的是一种名叫倒排索引
的数据结构;在正式讲倒排索引以前先来聊聊和他相反的正排索引
。
以上图为例,咱们能够经过 doc_id
查询到具体对象的方式称为使用正排索引
,其实也能理解为一种散列表。
本质是经过 key 来查找 value。
好比经过 doc_id=4
便能很快查询到 name=jetty wang,age=20
这条数据。
那若是反过来我想查询 name
中包含了 li
的数据有哪些?这样如何高效查询呢?
仅仅经过上文提到的正排索引显然起不到什么做用,只能依次将全部数据遍历后判断名称中是否包含 li
;这样效率十分低下。
但若是咱们从新构建一个索引结构:
当要查询 name
中包含 li
的数据时,只须要经过这个索引结构查询到 Posting List
中所包含的数据,再经过映射的方式查询到最终的数据。
这个索引结构其实就是倒排索引
。
但如何高效的在这个索引结构中查询到 li
呢,结合咱们以前的经验,只要咱们将 Term
有序排列,即可以使用二叉树搜索树的数据结构在o(logn)
下查询到数据。
将一个文本拆分红一个一个独立Term
的过程其实就是咱们常说的分词。
而将全部 Term
合并在一块儿就是一个 Term Dictionary
,也能够叫作单词词典。
当咱们的文本量巨大时,分词后的 Term
也会不少,这样一个倒排索引的数据结构若是存放于内存那确定是不够存的,但若是像 MySQL
那样存放于磁盘,效率也没那么高。
因此咱们能够选择一个折中的方法,既然没法将整个 Term Dictionary
放入内存中,那咱们能够为Term Dictionary
建立一个索引而后放入内存中。
这样即可以高效的查询Term Dictionary
,最后再经过Term Dictionary
查询到 Posting List
。
相对于 MySQL
中的 B+树
来讲也会减小了几回磁盘IO
。
这个 Term Index
咱们可使用这样的 Trie树
也就是咱们常说的字典树
来存放。
更多关于字典树的内容请查看这里。
若是咱们是以 j
开头的 Term
进行搜索,首先第一步就是经过在内存中的 Term Index
查询出以 j
打头的 Term
在 Term Dictionary
字典文件中的哪一个位置(这个位置能够是一个文件指针,多是一个区间范围)。
紧接着在将这个位置区间中的全部 Term
取出,因为已经排好序,即可经过二分查找快速定位到具体位置;这样即可查询出 Posting List
。
最终经过 Posting List
中的位置信息即可在原始文件中将目标数据检索出来。
固然 ElasticSearch
还作了许多针对性的优化,当咱们对两个字段进行检索时,就能够利用 bitmap
进行优化。
好比如今须要查询 name=li and age=18
的数据,这时咱们须要经过这两个字段将各自的结果 Posting List
取出。
最简单的方法是分别遍历两个集合,取出重复的数据,但这个明显效率低下。
这时咱们即可使用 bitmap
的方式进行存储(还节省存储空间),同时利用先天的位与
计算即可得出结果。
[1, 3, 5]
⇒ 10101
[1, 2, 4, 5]
⇒ 11011
这样两个二进制数组求与即可得出结果:
10001
⇒ [1, 5]
最终反解出 Posting List
为[1, 5]
,这样的效率天然是要高上许多。
一样的查询需求在 MySQL
中并无特殊优化,只是先将数据量小的数据筛选出来以后再筛选第二个字段,效率天然也就没有 ES
高。
固然在最新版的 ES
中也会对 Posting List
进行压缩,具体压缩规则能够查看官方文档,这里就不具体介绍了。
最后咱们来总结一下:
经过以上内容能够看出再复杂的产品最终都是基础数据结构组成,只是会对不一样应用场景针对性的优化,因此打好数据结构与算法的基础后再看某个新的技术或中间件时才能快速上手,甚至本身就能知道优化方向。
最后画个饼,后续我会尝试按照 ES
倒排索引的思路作一个单机版的搜索引擎,只有本身写一遍才能加深理解。
更好的阅读体验请访问此处:www.notion.so/ElasticSear…
你的点赞与分享是对我最大的支持