Elasticsearch 核心原理系列:10 张图理解 Elasticsearch 核心概念
Elasticsearch 是什么?它能干什么?
Elasticsearch(以下称之为 ES)是一款基于 Lucene 的分布式全文搜索引擎,擅长海量数据存储、数据分析以及全文检索查询,它是一款非常优秀的数据存储与数据分析中间件,广泛应用于日志分析以及全文检索等领域,目前很多大厂都基于 Elasticsearch 开发了自己的存储中间件以及数据分析平台。
从核心概念开始
Lucence
Lucene 是 Apache 下的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,它是 ES 实现全文检索的核心基础,索引文档以及搜索索引的的核心流程都是在 Lucene 中完成的。
核心数据结构
Document
我们都说 ES 是面向 document 的,这句话什么意思呢?实际就是表示 ES 是基于 document 进行数据操作的,操作主要包括数据搜索以及索引(这里的索引时数据写入的意思)。因此可以说 document 是 ES 的基础数据结构,它会被序列化之后保存到 ES 中。那么这个 document 到底是个什么东东呢?相信大家都对 Mysql 还是比较熟悉的,因此我们用 Mysql 中的数据库与表的概念与 ES 的 index 进行对比,可能并不是十分的恰当和吻合,但是可以有助于大家对于这些概念的理解。另外 type 也在 ES6.x 版本之后逐渐取消了。
Index
在 ES 之前的版本中,是有 type 这个概念的,类比数据库中的表,那上文中所说的 document 就会放在 type 中。但是在 ES 后面的版本中为了提高数据存储的效率逐渐取消了 type,因此 index 实际上在现在的 ES 中既有库的概念也有表的概念。简单理解就是 index 就是文档的容器,它是一类文档的集合,但是这里需要注意的是 index 是逻辑空间的分类,实际数据是存在物理空间的分片上的。
另外需要说明的是,在 ES 中索引是有不同上下文含义的,它既可以是名词也可以是动词。索引为名词是就是上文中提到的它是 document 的集合,索引为动词的时候表示将 document 数据保存到 ES 中,也就是数据写入。
在 ES 中,为了屏蔽语言的交互差异,ES 直接对外的交互都是通过 Rest API 进行的。
倒排索引
我们都知道索引存在的意义就是为了加速数据的查询。在关系型数据库中如果没有索引的话,为了查找数据我们需要每条数据去进行比对,运气不好的话可能需要扫描全表才能查找到想要的数据。以 Mysql 为例,它使用了 B+树作为索引来加速数据的查询。假设有这样的一种场景,周末在路上逛的时候突然听到一首非常好听的歌曲,你记住了其中两句歌词,想着赶快拿手机到 QQ 音乐中查一下是什么歌。如果你是 QQ 音乐的程序猿,你该怎么实现根据歌词查询歌曲的功能呢?
用 B+树作为索引行不行呢?全文索引就是需要支持对大文本进行索引的,从空间上来说 B+ 树不适合作为全文索引,同时 B+ 树因为每次搜索都是从根节点开始往下搜索,所以会遵循最左匹配原则,而我们使用全文搜索时,往往不会遵循最左匹配原则,所以可能会导致索引失效。这时候倒排索引就派上用场了。
所谓正排索引就像书中的目录一样,根据页码查询内容,但是倒排索引确实相反的,它是通过对内容的分词,建立内容到文档 ID 的关联关系。这样在进行全文检索的时候,根据词典的内容便可以精确以及模糊查询,非常符合全文检索的要求。
倒排索引的结构主要包括了两大部分一个是 Term Dictionary(单词词典),另一个是 Posting List(倒排列表)。Term Dictionary(单词词典)记录了所用文档的单词以及单词和倒排列表的关系。Posting List(倒排列表)则是记录了 term 在文档中的位置以及其他信息,主要包括文档 ID,词频(term 在文档中出现的次数,用来计算相关性评分),位置以及偏移(实现搜索高亮)。
FST
如上文所述,在进行全文检索的时候,通过倒排索引中 term 与 docId 的关联关系获取到原始数据。但是这里有一个问题,ES 底层依赖 Lucene 实现倒排索引的,因此在进行数据写入的时候,Lucene 会为原始数据中的每个 term 生成对应的倒排索引,因此造成的结果就是倒排索引的数据量就会很大。而倒排索引对应的倒排表文件是存储在硬盘上的。如果每次查询都直接去磁盘中读取倒排索引数据,在通过获取的 docId 再去查询原始数据的话,肯定会造成多次的磁盘 IO,严重影响全文检索的效率。因此我们需要一种方式可以快速定位到倒排索引中的 term。大家想想使用什么方式比较好呢?可以考虑 HashMap, TRIE, Binary Search Tree 或者 Tenary Search Tree 等数据结构,实际上 Lucene 实际是使用了 FST(Finite State Transducer)有限状态传感器来实现二级索引的设计,它其实就是一种有限状态机。
我们先来看下 trie 树的结构,在 Lucene 中是这样做的,将倒排索引中具有公共前缀的 term 组成一个 block,如下图所示的 cool 以及 copy,它们拥有 co 的公共前缀,按照类似前缀树的逻辑来构成 trie 树,对应节点中携带 block 的首地址。我们来分析下 trie 树相比 hashmap 有什么优点?hashmap 实现的是精准查找,但是 trie 树不仅可以实现精准查找,另外由于其公共前缀的特性还可以实现模糊查找。那我们再看 trie 树有什么地方可以再进行优化的地方?
如上如所示,term 中的 school 以及 cool 的后面字符是一致的,因此我们可以通过将原先的 trie 树中的后缀字符进行合并来进一步的压缩空间。优化后的 trie 树就是 FST。
因此通过建立 FST 这个二级索引,可以实现倒排索引的快速定位,不需要经过多次的磁盘 IO,搜索效率大大提高了。不过需要注意的是 FST 是存储在堆内存中的,而且是常驻内存,大概占用 50%-70%的堆内存,因此这里也是我们在生产中可以进行堆内存优化的地方。
集群相关概念
为了增强 ES 的数据存储可靠性以及高可用,ES 支持进行集群部署,集群后的 ES 即便是某些节点出现故障,也不会导致真个 ES 集群不可用,同时通过水平扩容增强了 ES 的数据存储能力。
节点
所谓的节点实际就是 ES 的实例,我们通常在一台服务器部署一个 ES 实例,其实就是一个 Java 进程。虽然都是 ES 实例,但是实际上的 ES 集群,不同节点承担着不同的能力角色,有的是 data node,主要负责保存分片的数据的,承担着数据横向扩展的重要作用,有的是 coordinating node 负责将用户请求进行转发以及将查询的结果进行合并返回。当然还有 master 节点,负责对真个集群状态进行管理和维护。
分片
单个 ES 节点的数据存储毕竟有限,没法实现海量数据的存储要求。那么怎么才能满足海量数据的存储要求呢?一个核心思想就是拆分,比如总共 10 亿条数据,如果都放在一个节点中不仅查询以及数据写入的速度回很慢,页存在单点问题。在传统关系型数据库中,采用分库分表的方式,用更多的数据库实例来承接大量的数据存储。那么在 ES 中,也是采取类似的设计思想,既然一个 ES 的实例存在数据存储的上线,那么就用多个实例来进行存储。在每个实例中存在的数据集合就是分片。如下图所示,index 被切分成三个分片,三个分片分别存储在三个 ES 实例中,同时为了提升数据的高可用性,每个主分片都有两个副本分片,这些副本分片是主分片的数据拷贝。
这里需要注意的是,分片不是随意进行设定的,而是需要根据实际的生产环境提前进行数据存储的容量规划,否则分片设置的过大或者过小都会影响 ES 集群的整体性能。如果分片设置的过小,那么单个分片的数据量可能会很大,影响数据检索效率,也会影响数据的横向扩展。如果分片设置的过大就会影响搜索结果的数据相关性评分,影响数据检索的准确性。
总结
本文对 ES 的核心概念进行了全面的梳理与阐述,相信大家对于 ES 有了初步的了解,下篇文章中再带大家好好理解下 ES 的核心业务流程的原理以及优秀的设计思想,只有理解了 ES 的核心概念以及核心流程,那么在生产中遇到一些搜索优化、节点 JVM 优化等才会有对应的排查方向,另外 ES 中的一些优秀的设计思想,也是非常值得我们学习的,当我们在设计软件平台的时候有时可以借鉴这些优秀的设计思想。
大家好,我是慕枫,感谢各位小伙伴点赞、收藏和评论,文章持续更新,我们下期再见!微信搜索:慕枫技术笔记,优质文章持续更新,我们有学习打卡的群可以拉你进,一起努力冲击大厂,另外有很多学习以及面试的材料提供给大家。
版权声明: 本文为 InfoQ 作者【慕枫技术笔记】的原创文章。
原文链接:【http://xie.infoq.cn/article/73c7bc776a8ab2a0d7a173472】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论