美文网首页
Elasticsearch 基本概念和原理

Elasticsearch 基本概念和原理

作者: 代码的搬运工 | 来源:发表于2020-01-09 22:44 被阅读0次

    Elasticsearch是实时的分布式搜索分析引擎,内部使用Lucene做索引和搜索。

    何谓实时?新增到ES中的数据在1秒后就能被检索到,这种新增数据对搜索的可见性称为“准实时搜索”。分布式意味着可以动态调整集群规模,弹性扩容,而这一切操作起来都非常方便,用户甚至不必了解集群原理就可以实现。按官方的描述,集群规模支持“上百”个节点,相比HDFS等上千台的集群,这个规模“小了点”。因此,目前我们认为ES适合中等数据量的业务,不适合存储海量数据。

    Lucene是Java语言编写的全文搜索框架,用于处理纯文本的数据,但它只是一个库,提供建立索引、执行搜索等接口,但不包含分布式服务,这些正式ES做的。什么是全文?对全部的文本内容进行分析,建立索引,使之可以被搜索,因此称为全文。

    基于ES,你可以很容易地搭建自己的搜索引擎,用于分析日志,或者配合开源爬虫建立某个垂直领域的搜索引擎。ES易用的产品设计使得它很容易上手。除了搜索,ES还提供了大量的聚合功能,所以它不单单是一个搜索引擎,还可以进行数据分析、统计、生产指标数据。

    1、索引结构

    ES是面向文档的。各种文本内容以文档的形式存储到ES中,文档可以是一封邮件、一条日志,或者一个网页的内容。一般使用JSON作为文档的序列化格式,文档可以有很多字段,在创建索引的时候,我们需要描述文档中每个字段的数据类型,并且可能需要指定不同的分析器,就像在关系型数据中“CREATE TABLE”一样。

    在存储结构上,由_index、_type和_id唯一标识一个文档。

    _index指向一个或多个物理分片的逻辑命名空间,_type类型用于区分同一个集合中的不同细分,在不同的细分中,数据的整体模式是相同或相似的,不适合完全不同类型的数据。多个_type可以在相同的索引中存在,只要它们的字段不冲突即可。_id文档标记符由系统自动生产或使用者提供。

    2、分片

    在分布式系统中,单机无法存储规模巨大的数据,要依靠大规模集群处理和存储这些数据,一般通过增加机器数量来提高系统水平扩展能力。因此,需要将数据分成若干小块分配到各个机器上。然后通过某种路由策略找到某个数据库所在的位置。

    除了将数据分片以提高水平扩展能力,分布式存储中还会把数据复制成多个副本,放置到不同的机器中,这样一来可以增加系统可用性,同时数据副本还可以使读操作并发执行,分担集群压力。但是多数据副本也带来了一致性的问题:部分副本写成功,部分副本写失败。

    对了应对并发更新问题,ES将数据副本分为主从两部分,即主分片和副分片。主数据作为权威数据,写过程中先写主分片,成功后再写副分片,恢复阶段以主分片为准。

    数据分片和数据副本的关系如下图所示:

    分片是底层的基本读写单元,分片的目的是分割巨大索引,让读写可以并行操作,由多台机器共同完成。读写请求最终落到某个分片上,分片可以独立执行读写工作。ES利用分片将数据分发到集群内各处。分片是数据的容器,文档保存在分片内,不能跨分片存储。分片又被分配到集群内的各个节点里。当集群规模扩大或缩小时,ES会自动在各节点中迁移分片,使数据仍然均匀分布在集群里。

    索引与分片的关系如下图所示:

    一个ES索引包含很多分片,一个分片是一个Lucene的索引,它本身就是一个完整的搜索引擎,可以独立执行建立索引和搜索任务。Lucene索引又由很多分段组成,每个分段都是一个倒排索引。ES每次“refresh”都会生产一个新的分段,其中包含若干文档的数据。在每个分段内部,文档的不同字段被单独建立索引。每个字段的值由若干词组成,词是原文本内容经过分词器处理和语言处理后的最终结果。

    索引建立的时候就需要确定好主分片数,在较老的版本中(5.x版本之前),主分片数量不可以修改,副分片数可以随时修改。现在(5.x版本之后),ES已经支持在一定条件的限制下,对某个索引的主分片进行拆分(split)和缩小(shrink)。但是,我们仍然需要在一开始就尽量规划好主分片数量:先依据硬件情况定好单个分片容量,然后依据业务场景预估数据量和增长量,再除以单个分片容量。

    分片数不够时,可以考虑新建索引,搜索1个有着50个分片的索引与搜索50个每个都有1非分片的索引完全等价,或者使用_split API来拆分索引(6.x版本开始支持)。

    在实际应用中,我们不应该向单个索引持续写数据,直到它的分片巨大无比。巨大的索引会在数据老化后难以删除,以_id为单位删除文档不会立即释放空间,删除的文档只在Lucene分段合并时才会真正从磁盘删除。即使手工触发分段合并,仍然会引起较高的I/O压力,并且可能因为分段巨大导致在合并过程中磁盘空间不足。因此,建议周期性地创建新索引。例如,每天创建一个。假如有一个索引website,可以将它命名为website_20180319。然后创建一个名为website的索引别名来关联这个索引。这样,对于业务方来说,读取时使用的名称不变,当需要删除数据的时候,可以直接删除整个索引。

    索引别名就像一个快捷方式或软链接,不同的是它可以指向一个或多个索引。可以用于索引分组,或者索引间的无缝切换。

    现在我们已经确定好了主分片数量,并且保证单个索引的数据量不会太大,周期性创建新索引带来的一个新问题是集群整体分片数量较多,集群管理的总分片数越多压力就越大。在每天生成一个新索引的场景中,可能某天产生的数据量很小,实际上不需要这么多分片,甚至一个就够。这时,可以使用_shrink API来缩减主分片数量,降低集群负载。

    3、动态更新索引

    为文档建立索引,使其每个字段都可以被搜索,通过关键词检索文档内容,会使用倒排索引的数据结构。倒排索引一旦被写入文档后就具有不变性,不变性具有许多好处:对文件的访问不需要加锁,读取索引时可以被文件系统缓存等。

    那么索引如何更新,让新添加的文档可以被搜索到?答案是使用更多的索引,新增内容并写到一个新的倒排索引中,查询时,每个倒排索引都被轮流查询,查询完再对结果进行合并。

    每次内存缓冲的数据被写入文件时,会产生一个新的Lucene段,每个段都是一个倒排索引。在一个记录元信息的文件中描述了当前Lucene索引都含有哪些分段。

    由于分段的不变性,更新、删除等操作实际上是将数据标记为删除,记录到单独的位置,这种方式称为标记删除。因此删除部分数据不会释放磁盘空间。

    4、近实时搜索

    在写操作中,一般会先在内存中缓冲一段数据,再将这些数据写入硬盘,每次写入硬盘的这些数据称为一个分段,如同任何写操作一样。一般情况下(direct方式除外),通过操作系统write接口写到磁盘的数据先到达系统缓存(内存),write函数返回成功时,数据未必被刷到磁盘。通过手工调用flush,或者操作系统通过一定策略将系统缓存刷到磁盘。这种策略大幅提升了写入效率。从write函数返回成功开始,无论数据有没有被刷到磁盘,该数据已经对读取可见。

    ES正式利用这种特性实现了近实时搜索。每秒产生一个新分段,新段先写入文件系统缓存,但稍后再执行flush刷盘操作,写操作很快会执行完,一旦写成功,就可以像其他文件一样被打开和读取了。

    由于系统先缓冲一段数据才写,且新段不会立即刷入磁盘,这两个过程中如果出现某些意外情况(如主机断电),则会存在丢失数据的风险。通用的做法是记录事务日志,每次对ES进行操作时均记录事务日志,当ES启动的时候,重放translog中所有在最后一次提交后发生的变更操作。

    5、段合并

    在ES中,每秒清空一次写缓冲,将这些数据写入文件,这个过程称为refresh,每次refresh会创建一个新的Lucene段。但是分段数量太多会带来较大的麻烦,每个段都会消耗文件句柄、内存。每个搜索请求都需要轮流检查每个段,查询完再对结果进行合并;所以段越多,搜索也就越慢。因此需要通过一定的策略将这些较小的段合并为大的段,常用的方案是选择大小相似的分段进行合并。在合并过程中,标记为删除的数据不会写入新分段,当合并过程结束,旧的分段数据被删除,标记删除的数据才从磁盘删除。

    HBase、Cassandre等系统都有类似的分段机制,写过程中先在内存缓冲一批数据,不时地将这些数据写入文件作为一个分段,分段具有不变性,在通过一些策略合并分段。分段合并过程中,新段的产生需要一定的磁盘空间,我们要保证系统有足够的剩余可用空间。Cassandra系统在段合并过程中的一个问题就是,当持续地向一个表中写入数据,如果段文件大小没有上限,当巨大的段达到磁盘空间的一半时,剩余空间不足以进行新的段合并过程。如果段文件设置一定上限不再合并,则对表中部分数据无法实现真正的物理删除。ES存在同样的问题。

    相关文章

      网友评论

          本文标题:Elasticsearch 基本概念和原理

          本文链接:https://www.haomeiwen.com/subject/ljvyactx.html