Elasticsearch初学终极教程 - 第三章: Elastic Search基本服务架构

卡拉先生
发布于 2020年06月09日 | 上次编辑:2020年07月19日

本系列其它文章:

本文目录

1. 本章引言:如何理解一个复杂系统

要理解一个复杂系统,我认为最快的做法是远-近-远,即

  1. 远 - 先大致理解这个复杂系统的输入输出,如果有可能的话,把系统在本地跑起来
  2. 近 - 开始逐个理解这个系统里你感兴趣的部件,理解它是怎样被组织在一起的,为什么这样组织
  3. 远 - 再从概念出发,理解步骤一里的输入输出,在这个系统里是怎样经过每一个部件的

理解 Elastic Search 也是一样,千万不要一上来就钻到它的引擎(Lucene,本文后文会讲到)里去,这样会绕着绕着把兴致给绕没。相反,像我们在上一章做的一样,先把 Elastic Search 在本地跑起来,在这一章里,你可以通过观察我们给它的输入、输出来大致理解这个系统。

在本章里,我们从10000米的高空开始向下俯冲,每下降一点,我们就离 Elastic Search 的细节更近一些。

等到快接近地面的时候,所有的概念应该从上自下已经相对清晰,然后我们回过头来来一点一点分析 Elastic Search 概念上是怎样被组织起来的,它的基本服务架构是怎么样的。

理解了这些,这样可以帮助你在以后的学习中加速前进。同时碰到问题,不光可以知其然,也可以知其所以然。深入理解整个 Elastic Search 的概念还可以帮助你理解,在哪些场景下使用 Elastic Search 会更合适,而哪些场景 Elastic Search 并不理想【比如单纯当做数据库来使用】。

如果需要参考如何把 Elastic Search 启动起来,参考教程里上一章以及社区里的文章:Elastic Search 配置中文教程

2. 从集群讲起:10000 米高空

集群是 Elastic Search 里 10000 米的高空。你可能以为,一台运行 Elastic Search 的服务器是 ES 里最宏大的概念,其实不是。

一个集群是一些运行 Elastic Search 的节点服务器(node)的总称。虽然从技术上讲,一台服务器就可以是一个集群(可以跑起来多个node节点)。那么,为什么需要有集群呢?

说得天花乱坠一点,集群这个概念的出现主要是解决几个问题

2.1. 数据分片

想象现在你手里有一个任务,需要能把全球所有的网站存起来,然后能用 Elastic Search 进行搜索。

这听起来有点天方夜谭,因为目前 google 索引的数据大致是 100000T。也就是说,就算你有一台服务器,什么都没干,也需要10万台这样的服务器,才能把这些网页装到硬盘中。当然,这不妨碍我们用这个例子进行思考。

再假设,你的用户要搜索 熊猫 这个关键词,而这 10 万台服务器中的网页你提前也不知道是不是包含熊猫,因此你需要向它们每一个都发送一个请求,询问在每台服务器上的 Elastic Search : 你包含 熊猫 吗?

最后,你需要有一个地方把这 10 万台服务器上的结果汇总起来,呈现给用户。当然事实上 Google 做了很多额外的事情,但整个简化的谷歌搜索的流程大致就是这样的。

而把这 10 万 T 的数据,分成一个 T 一个 T,放在 10 万台服务器上的过程,就是分布式数据库中常见的名词:分片(英文叫 sharding)。

当数据量太大,单机无法处理,这时在分布式服务中常见的一个手段就是分片,把数据拆成一个个独立的单元,放到不同服务器上。再在程序里把这些数据组织起来。

那么 Elastic Search 怎么知道自己身体里有多少节点,能向哪些节点做请求?这些就被抽象成了 集群 (英文叫 cluster)的概念。一个集群中,抽象地会有 N 个节点,这些节点,可以简单地想象成服务器的概念。在下图中,我们有一个集群,分别有三个,节点 A,B 和 C,它们代表在一个 Cluster 中,有三台服务器。

elasticsearch 的集群概念
elasticsearch 的集群概念

当你的数据量越来越多时,因为一个节点装不下你的数据,你会开始需要做分片,做所谓的 横向扩展。但请注意,据我了解,市面上真正需要做横向扩展的公司和业务屈指可数。

比如说,我们用一个已经非常大的公司——小红书,做例子。Google 索引了小红书 1200 万的页面,我们再假设每个页面都是字数 1000 个左右的汉字,那么这一千多万个页面全部存起来的话,不过24G左右。即使是用相对复杂一点的索引方法(关于索引,后文会讲)也不会超过 500G。因此,对绝大多数公司来说,业务数据要达到开始分片的规模,其实是少见的。当然,数据产生(比如爬虫公司)或者专门的数据搜集公司除外。

google索引的小红书页面量
google索引的小红书页面量

因此对绝大多数初学者来说,一上来就开始深挖所谓的 大数据处理,其实是没有太多用武之地的。更加实际的路径可以是先把 Elastic 本身的原理吃透,把本教程读通,再开始。

2.2. 副本分片(replica)和系统健壮性(Robust)

如果你有运维服务器的经验,那么你应该有机会碰到,类似服务器硬盘坏了、内存坏了或者 CPU 坏了之类的情况。总之再健壮的服务器,也有倒下的时候。那么问题来了,如果正在运行 elastic search 的这台服务器 A,因为各种原因停止运行了,那么你的用户的查询就会受到影响。但是,如果你有一份保险:即同样的数据存在了另一台备份服务器 B上,那么用户的查询不会因为 A终止运行了而停下。

在 Elastic Search 中,我们把同样的数据放在多个服务器上这样的配置叫副本分片(replica),而一个集群同样需要知道各个副本分片的信息。这样,集群的另一个作用就是用来管理副本分片。

2.3. 查询集群健康状态

当然如上文所说,数据分片和副本分片是较为高级的内容。如果你需要开发的应用只是实现一个搜索,或者你还在初学阶段,可以大致了解一下概念即可。而真正地需要深入理解数据分片和副本分片,通常是在数据量相当大,或者对可靠性要求相当高的情况下。对于大多数初学者,可以循序渐进地来。

到这里我们讨论了集群的抽象概念。那么,怎么样具化这个概念呢?我们通过什么跟集群打交道?如果你学完了上一章的课程,应该有一些印象,而且现在应该有一个可用的 Elastic 和 Kibana 在本地跑着。那么我们可以先来看一下怎么样跟集群沟通。

首先再次进入 Kibana 的开发者工具界面,敲入GET命令

GET /_cluster/health

回顾一下,这条命令是在向 elastic search 的服务器请求其 集群的健康状况。它会返回一些关于集群的重要信息,比如说集群中有几个节点,当前集群的健康状态是怎样的。

顺便说一句,elastic search 的请求路径(end-point)通常是非常规范的,由 HTTP请求类型 + API + 命令 三部分组成。比如这里,HTTP请求类型是GET类型,API(以下划线开头)_cluster是集群操作,最后的命令是health。这可以帮助你记忆和内化繁多的命令。通常来讲,记住了这个请求的结构,再熟记一些常用的单词,在找命令的时候直接猜也能猜出个大概。我写了一篇文章介绍如何设计出优秀的 REST API,而 Elastic Search 的 API 也是比较符合规范的。因此如果你读一下之后,英文单词背一背的话,大概猜也能猜到要做一个操作应该调用什么 API。

同样,这个命令会返回我们一个JSON,如下图

kibana集群健康
kibana集群健康

这里,最重要的信息应该就是 statusnumber_of_nodes 了。第一个 status 会有三个状态

  • Green - 绿色,即健康状态
  • Yello - 黄色,亚健康状态,大意可理解为你的集群处于能用状态,但不一定稳健。即如果有服务器倒掉,你的搜索服务可能会不正常
  • Red - 不健康状态。需要检查你的服务器状态

number_of_nodes 则更明显,是你的 ES 集群中有多少节点正在运行。在这里,我们只有一个节点运行在本地。

3. Elastic Search中的节点——6000米高空

上文讲到(怎么像评书一样),Elastic Search 概念中最大的结构是一个集群。而一个集群中,可能包含一个或多个节点。这些节点受到集群的管理。你可以向一个集群中增加或者删除一些节点(扩容或者减容),也可以查询节点的信息(比如这个节点的 CPU 使用状况如何等等)。

3.1. 节点的种类

在 ES 中,节点有种类的概念。

这个概念其实很简单——你可以把节点的种类想象成工种。比如施工队里,有泥瓦工,有木匠,有设计师等等,而节点的种类也类似,大致就是如果一个节点是某种种类,也就主要做那个种类做的事情。节点的种类有:

  • 主节点(master-eligible node)负责各种沟通协作节点的工作,一个集群必须有至少一个
  • 数据节点(data node)主要负责搜索和数据存储
  • 写入节点(ingest node)主要负责文档写入

注意,这三种是节点最主要的类型,且一个节点可能有多个各类。比如如果你照上一章的教程,在本地启动了一个elastic search的服务,那么你本地的节点既是主节点,也是数据节点,还是写入节点。详细的节点和其对应的功能的关系,以及各种节点之间的关系请参考文档elastic search node的分类

从这里开始,你应该已经看出来 Elastic Search 本身看似简单,但其实是个极复杂的搜索库。如果你需要一个响应快速,不需要配置不需要学习的搜索服务的话,可以尝试我们的卡拉搜索服务

3.2. 查看集群中的节点信息

另一个经常用到的跟集群打交道的命令是

GET /_cat/nodes

这个命令会查询集群中节点的信息,如下图

kibana节点信息
kibana节点信息

仔细把图片点开放大你会发现,我在命令后加了一个?v。这个的意思是让 kibana 输出逻辑模式即verbose模式的首字母。这也是一个常见套路,可以记一下。

查询的结果是,这个集群里,有一台IP为127.0.0.1的节点正在运行,同时这个节点的cpu使用情况,负载之类的信息,也都会被打印出来。关于节点的信息非常之多,如果你是运维或者负责管理公司的Elastic Search集群的话,那么这些是需要经常打交道的内容。更详细的手册需要参考 elastic cat nodes API

同时,node.role 这一列比较有趣,这里的值是 dilmrt,这是节点种类的首字母拼在了一起。回想上一节,主要的节点种类有

  • 主节点 (master node)
  • 数据节点(data node)
  • 写入节点(ingest node)

而这里的d即 data,i即 ingest,m即 master,当然还有其它几个字母,不用深究,需要的时候在手册中查查就好了。

4. 索引和倒排索引的概念——3000米高空

前面我们聊到了 Elastic Search 中的集群和节点,现在我们开始讨论一下,真正在日常开发中会直接打交道的概念——索引。

请注意,索引这个概念并不是Elastic Search特有的,而是搜索引擎中的一个概念。这也是为什么本节的标题中没有了Elastic Search 两个字。不光是 Elastic Search,谷歌搜索,卡拉搜索,必应搜索和几乎所有搜索引擎的实现里,都有索引这个概念。如果需要深入理解索引,请翻阅斯坦福大学教授 Christopher Manning 教授的搜索引擎入门,这书是免费的。

索引的概念可以简单理解为,一系列文档的集合(下文我们继续深入讲什么是文档,这里你先理解,文档就是你往搜索引擎里存的每一行数据,比如如果要搜索商品的话,就是一件商品的信息)。

4.1. 索引和倒排索引的关系

搜索引擎之所以可以非常快速地找到包含你键入的关键词的文章,就是因为索引的巧妙存储结构。

简单地说,假设我们有一万篇文档,你有一个关键词是 熊猫,那么最笨的办法无非是把这一万篇文档全看一遍扫一遍,找出来哪些文档含有熊猫这个词。

然而有一种特殊的数据结构(值得单独开一篇详讲)叫作倒排索引(invereted-index),它的作用是提前先处理一个映射,可以让你快速地从一个词,找到这个词在你的这些文档里对应的所有文档编号。

倒排索引的例子
倒排索引的例子

比如在上图中,假设我们有200篇文章,编号从1到200。这200篇文章中有各种各样的词语,比如说 Peiking University, Stanford等等。举个例子,其中一篇文章编号3可能是

Peiking University is one of the best universities in China,
while Stanford is one of the best in the United States.

那么我们说,我们的索引就包含200篇文章。而对于我们的这个索引,其存为倒排索引的结构则为上图所示:

对于词语Stanford,它在这个索引对应的倒排索引中,含有的文档就有上述的文档3,8,10,13,16和20.

那么当你要搜索 Stanford University 这个短语的时候,查看一下倒排索引,可以瞬间找出含有 Stanford 的文档的列表。同样的道理,你再把含有 University 这个短语的列表,全部找出来。两者相交一下,即可找出有短语Stanford University 的所有文档。

4.2. Lucene和Elastic的关系

虽然 Elastic Search 本身是个巨兽,但是其 20 余万行代码里,几乎全部是为搜索服务器服务的。而真正处理倒排索引、具体搜索算法的,则是一个叫 Lucene 的核心引擎。而不管你看到的搜索引擎多么复杂,甚至 Google 和百度那样的复杂,但其核心的思想里的倒排索引都是一样的。

当然,为了保证速度,索引本身的实现有非常多优化,上面举的例子显然是一个非常非常简化的版本。Lucene 本身的代码也有数十万行,因此与 Elastic Search 加在一起,总共有 50 万行左右代码,其复杂度可见一斑。

关于索引,我们整理一下

  1. 一系列文档形成一个索引(Index)
  2. 索引在搜索引擎里的具体存储数据结构叫倒排索引(Inverted Index),之所以叫倒排,是因为它是从词语到文档的关系。如果你看技术书的时候翻到最后几页,通常有一个索引列出来一些专有名词和其对应的页码,就跟倒排索引的存储方式很类似
  3. ES 用的搜索核心引擎叫 Lucene,是一个跟 Google 一样老的 Java 包,也是目前世界上最成功的开源项目之一,到目前为止还在非常积极地被开发。不光是 Elastic 公司本身在积极地贡献 Lucene 的代码,亚马逊、易贝、微软、推特、Airbnb 爱彼迎等等科技巨头都在使用 Lucene 作为其搜索引擎和向其贡献代码(而不是 Elastic Search)

5. 文档——1000 米低空

到此,我们熟悉了 Elastic Search 的几个基本操作,上一节中也了解了索引的概念,那么我们现在开始了解文档的概念。

与索引类似,文档 不是一个 Elastic Search 的概念,而是一个抽象的搜索引擎的概念。要理解什么是 文档(英文叫 document 或者 doc),最好的办法其实是举几个例子

5.0.1. 豆瓣网站中的文档是什么?

答:如果你到豆瓣的网站去看一下,有什么可以搜索的内容,那么可以看到有这么一些

豆瓣的搜索页面
豆瓣的搜索页面

  • 电影
  • 书籍
  • ...
  • 成员

对于电影,一部电影的信息就是文档。而一篇电影信息里通常包含电影名、导演、演员、剧情、发行年份、网友评分等。

对于书籍,显然有书名、作者、出版社、年份、摘要等。

而对于成员,则可能有用户名、注册时间、用户自我介绍等。

因此,结合上节的内容,我猜测豆瓣的搜索应该是有电影、书籍和成员等几个分开的索引,每个索引里分别存着对应的电影信息等。

5.0.2. 抖音里的文档是什么?

答:显然在抖音里,你可以搜索一个视频。而一个视频并不是文字,它为什么也可以被称为一个文档呢?

很简单,一个视频也可以转为文字。比如说,用语音识别把视频里讲话的文字提取出来。再比如说,用计算机视觉给视频打上标签,比如 搞笑视频 等。以及,可以用评论里的文字给视频打上标签,比如如果评论中有大量的 好吃,那也许这个视频是个做菜教程。总之,有很多办法把一个看起来不是文档的格式,转为搜索引擎可以理解的文档。

更重要的是,你需要把 文档 理解为 数据 ,而不是字面上的文档。

5.0.3. 滴滴/优步打车里的文档是什么?

答:这可能是最难以想象的,但是滴滴、优步的的找车问题,事实上也可以抽象为一个搜索问题。

比如说,你现在人在东直门,打了一个车。这时候,滴滴需要给你找到离你最近的司机。那么这时候司机的坐标信息,连同司机本身的信息,就是一个文档。而你的打车请求,就是一个搜索请求。

当然我并不是在说滴滴就是这么实现的,而是如果抽象出来的话,打车的问题可以如何被抽象为一个搜索的问题,以及这个看似和搜索完全不相关的事情,是怎样跟搜索引擎里的概念对应起来的。

5.1. Elastic Search 里的文档

Elastic Search 里的文档输入格式全为 JSON。当你把一个 JSON 文档发给 Elastic Search 的时候,它会先做一系列处理,然后往你指定的索引中插入这个文档。

请注意,你的输入格式为 JSON,但并不代表在Elastic Search内,文档是以 JSON 形式存储的。具体来说,当你往 Elastic Search 里添加一篇文件的时候,发生了以下过程

  1. Elastic Search 把 JSON 文档拆开,分析里面的内容,并区分对待文字、字符串、日期等格式
  2. 处理完后 Elastic Search 会把你的文档拆成很多域(即 Field)。比如说,豆瓣电影的数据会有 电影名, 演员打分 这三个域
  3. 处理完域后,Elastic Search 会把这些域转为一个 Lucene 文档,然后交给 Lucene 真正地插入到 Lucene 的倒排引擎里
  4. 当你在搜索时,Elastic Search 会把你的查询,转换好,交给 Lucene。Lucene 返回的结果再包装一下,还给 Elastic Search,最后返回给用户

注意,简单起见,上述的过程简化了很多,略去了 Routing,考虑分片之类复杂的过程。几乎所有的开源闭源搜索服务系统的实现都与上述类似,具体的引擎和服务可能不同。比如说,虽然与 Elastic Search 的设计理念完全不同,卡拉搜索 使用 改装过的 Lucene 实现了 Elastic Search 快 10 倍的搜索体验。但说到底,索引、文档这些搜索引擎概念上的映射,与 Elastic Search 是完全一致的

6. 词语和编码——1 米低空

对 99.99% 的工程师来说,甚至 99% 的搜索工程师来说,搜索引擎内的词语(Token)和编码都是很少接触的。

计算机虽然是个发展只有几十年的学科,但是非常好地做到了层次抽象——底层的东西封装得很好,以便上一层可以细节不可知地直接使用。

比如说,即使你直接写 C 代码了,已经很底层了吧?但是再下一层的系统、硬件程序员已经将 CPU 的使用,内存的使用和设备资源管理等等用驱动封好了。即使你在写 C 你也不用管怎么操作 CPU 的缓存。

同样的,对于 Elastic Search 来说,除非真的是有特殊需求,很难想象你会去写一套 Lucene 的定制排序,同时也很难想象你需要到 Lucene 一层去控制索引文件的编码,这些都由 20 年来孜孜不倦的大神们给你搞定了。如果你实在感兴趣的话,可以考虑以下几个作者的博客,他们讨论了很多 Lucene 层的底层实现、编码、测试和实现细节等

7. 总结

写到这里,已经不知不觉洋洋洒洒写了将近一万字,看来热情不灭的笔者多年来对 Elastic Search 和 Lucene 的热爱不但没有随着时间被磨灭,反倒因为做卡拉搜索的经历越来越强烈。

Elastic Search 和 Lucene 都是开源界最成功的典范,特别是 Elastic Search 还是商业成功的一个典范。但就像蜘蛛侠里说的,“权利越大,责任越大”,在软件工程领域也是类似的 —— 功能越强大,越复杂。如果需要把 ES 用得出神入化,不光需要对 ES 本身有比较深的理解,还需要对 Lucene 和本文介绍的搜索基础知识有比较系统的研究。如果你对搜索感兴趣,欢迎找我们探讨。

而另一方面,卡拉搜索的设计哲学是 —— 把一切不必要的细节封装,满足 99% 的需求。因此,如果你,或你的公司的业务核心本身不是搜索,而是电商、游戏、媒体或者企业服务,大可不必在搜索上花太多功夫。卡拉搜索不光可以满足你们绝大部分需求,同时极快的响应速度也可以给你们的用户极好的使用体验。

(彩蛋:能阅读到这里的你真的是最棒的!如果你截一张这里的图,发给我们,我们将免费给你的卡拉搜索帐户充值 300 元代金券)

下一章我们会继续讨论 Elastic Search 的操作细节,结合这一章的知识,你应该可以对 ES 有更全面的掌握。如果觉得教程不错,请别忘了分享给朋友,或从你的博客链接过来。有你的肯定我们才有继续写下去的动力。

相关文章:

想要阅读更多技术文章和卡拉搜索的创业经历?
与 1893 位读者一起,订阅我们的邮件列表吧

相关文章

© 2020, 卡拉搜索, Built with ❤️ in San Francisco + Beijing

京ICP备15049164号-3