小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
基本概念
Cluster(集群)
ES 可以作为一个独立的单个搜索服务器,不过,为了处理大型数据集,实现容错和高可用性,ES 可以运行在许多互相合作的服务器上,这些服务器称为集群。
Node(节点)
形成集群的每个服务器称为节点。
Shard(分片)
当有大量的文档时,由于内存的限制、磁盘处理能力不足,无法足够快的响应客户端的请求等,一个节点可能不够。这种情况下,数据可以分为较少的分片。每个分片放到不同的服务器上。
当你查询的索引分布在多个分片上时,ES 会把查询发送到每个相关的分片,并将结果组合在一起,而应用程序并不知道分片的存在。即:这个过程对用户来说是透明的。
Replia(副本)
为提高查询吞吐量或实现高可用性,可以使用分片副本。
副本是一个分片的精确复制,每个分片可以有零个或多个副本,ES 中可以有许多相同的分片,其中之一被选择更改索引操作,这种特殊的分片称为主分片。
当主分片丢失时,如:该分片所在的数据不可用时,集群将副本提升为新的主分片。
基本知识
1. Elasticsearch 的倒排索引是什么?
传统的我们的检索是通过文章,逐个遍历找到对应的位置。
而倒排索引,是通过分词策略,形成了词和文章的关系映射表,这种字典 + 映射表即为倒排索引。
有了倒排索引,就能实现 O(1) 复杂度检索文章,这就是倒排索引。
2. 详细描述一下 Elasticsearch 搜索的过程?
ES 搜索是一个二阶段过程,我们称之为 Query Then Fetch;
-
查询阶段:
- 客户端发送一个请求到某一个节点 Node,Node 会创建一个大小为 from + size 的空队列;
- Node 将查询请求转发到每个主分片或副分片中,每个分片在本地执行搜索并构建一个匹配文档的大小 from + size 的优先队列;
- 每个分片返回自己的优先队列中的文档 ID 和排序值给协调节点 Node,Node 合并这些值到自己的优先队列中,产生一个全局排列的结果列表。
-
取回阶段:
- 协调节点 Node 辨别出哪些文档需要被取回并向相关分片提交多个 GET 请求,将文档返回给协调节点。一旦所有文档都被取回了,协调节点返回结果给客户端。
3. 是否了解字典树?
常用的字段数据结构如下:
| 数据结构 | 优缺点 |
|---|---|
| 排序列表 Array/List | 使用二分法查找,不平衡。 |
| HashMap/TreeMap | 性能高,内存消耗大,几乎是原始数据的三倍。 |
| SkipList | 跳跃表,可快速查找词语,在 Lucene、Redis、HBase 等均有实现。相对于 TreeMap 等结构,特别使用高并发场景。 |
| Trie | 适合英文字典,如果系统中存在大量字符串并且这些字符串基本没有公共前缀,则相应 Trie 树会非常耗内存。 |
| Double Array Trie | 适合做中文字典,内存占用小,很多分词工具均采用此种算法。 |
Trie 的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。它有 3 个基本性质:
- 根节点不包含字符,除根节点的每一个节点都只包含一个字符;
- 从根节点到某一节点,路径上经过的字符串连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
4. 为什么 ES/Lucene 比 MySQL 快?
- MySQL 只有 Term Dictionary 这一层,是以 B+ 树的排序方式存储到磁盘上的。检索一个 Term 需要若干次的随机访问磁盘的操作。而 Lucene 在 Term Dictionary 的基础上增加了 Term Index 来加速索引。Term Index 是以树的形式缓存在内存中的,从 Term Index 查到对应的 Term Dictionary 上的 block 位置之后,再去磁盘上找 Term,大大减少了磁盘的随机访问次数。
另外需要注意的是,Term Index 在内存中是以 FST(Finite State Transducers)的形式保存的,其特点是非常节省内存。Term Dictionary 在磁盘上是以分 block 的方式保存的,一个 block 内部利用公共前缀压缩,比如都是 AB 开头的单词就可以把 AB 省去,这样 Term Dictionary 比 B+ 树更加节省空间。
5. 联合索引如何查询?
本部分内容参考 blog.csdn.net/baichoufei9…
假设我们想同时查询 age=18 和 gender=女 的人,同时存在这两个条件,在 Lucene 中应该如何查询呢?
- 使用 skiplist,同时遍历 age=18 和 gender=女 两个
posting list,互相 skip。 - 使用 bitset 数据结构,对 age 和 gender 两个 filter 分别求出 bitset,然后执行 and 操作。
使用 skiplist 举例说明一下:
比如我们现在有三个 posting list,分别为:
[1,13,24,35,56]
[2,3,5,14,24,45]
[1,4,7,16,24,68]
当我们过滤到第一行的 13 的时候,以为 13 比下一行的 2,3,5 都大,所以我们就可以把这些跳过了,不用比较了。直到 24,我们找到我们想要寻找的值。
使用 bitset 举例说明一下
bitset 是一种很直观的数据结构,对应 posting list 如:
[1,3,4,7,10]
对应的 bitset 就是:
[1,0,1,1,0,0,1,0,0,1]
每个文档按照文档 ID 排序对应其中的一个 bit。bitset 自身就有压缩的特点,其用一个 byte 就可以代表 8 个文档。所以 100 万个文档只需要 12.5 万个 byte。但是考虑到文档可能有数十亿之多,在内存里保存 bitset 仍然是很奢侈的事情。而且对于每一个 filter 都要消耗一个 bitset,比如 age=18 缓存起来的话是一个 bitset,18<=age<25 是另外一个 filter,缓存起来也要一个 bitset。
所以秘诀就在于需要有一个数据结构:
可以很压缩的保存上亿个 bit 代表对应的文档是否匹配 filter; 这个压缩的 bitset 仍然可以很快的进行 and 和 or 的逻辑操作;
Lucene 使用的这个数据结构叫做 Roaring Bitmap。
其压缩的思路其实很简单。与其保存 100 个 0,占用 100 个 bit。还不如保存 0 一次,然后声明这个 0 重复了100 遍。
这两种合并使用索引的方式都有其用途。Elasticsearch 对其性能有详细的对比(www.elastic.co/blog/frame-… Frame of Reference 编码是如此 高效,对于简单的相等条件的过滤缓存成纯内存的 bitset 还不如需要访问磁盘的 skip list 的方式要快。
6. Lucene 索引是如何实现的?
参考文档:Lucene底层实现原理,它的索引结构
Lucene 经过多年演进优化,现在的一个索引结构包含如下几部分:词典、倒排表、正向文件、列式存储 DocValues。
Lucene 的检索步骤如下:
- 内存加载词典索引(tip)文件,通过 FST 匹配前缀找到后缀词块的位置;
- 根据词块位置,读取磁盘中 tim 文件中后缀块并找到后缀和相应的倒排表位置信息;
- 根据倒排表位置去 doc 文件中加载倒排表;