Elasticsearch是一个基于Lucene的开源分布式搜索引擎,具有分布式多用户能力,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是第二流行的企业搜索引擎。设计用于云计算中,能够达到实时搜索、高性能计算;同时Elasticsearch的横向扩展能力非常强,不需要重启服务,基本上达到了零配置。但是目前来说相关资料很少,同时版本更新很快,bug存在,API繁多并且变化。
初识:
索引是一种数据结构,它允许对存储在其中的单词进行快速随机访问。
当需要从大量文本中快速检索文本目标时,必须首先将文本内容转换成能够进行快速搜索的格式,以建立针对文本的索引数据结构,此即为索引过程。
它通常由逻辑上互不相关的几个步骤组成。
索引处理就是从索引中查找单词,从而找到包含该单词的文档的过程。搜索质量主要由查准率(Precision)和查全率(Recall)两个指标进行衡量。查准率用来衡量搜索系列过滤非相关文档的能力,而查全率用来衡量搜索系统查找相关文档的能力。
另外,除了快速搜索大量文本和搜索速度之后,搜索过程还涉及到了许多其它问题,例如单项查询、多项查询、短语查询、通配符查询、结果ranking和排序,以及友好的查询输入方式等。这些问题的解决,通常需要多个组件协作完成。
ElasticSearch却也不仅只是一个全文本搜索引擎,它还是一个分布式实时文档存储,其中每个field均是被索引的数据且可被搜索;也是一个带实时分析功能的分布式搜索引擎,并且能够扩展至数以百计的服务器存储及处理PB级的数据。
如前所述,ElasticSearch在底层利用Lucene完成其索引功能,因此其许多基本概念源于Lucene。
ElasticSearch概念和设计
索引
索引(index)是Elasticsearch存放数据的地方。索引是具有类似特性的文档的集合。类比传统的关系型数据库领域来说,索引相当于SQL中的一个数据库,或者一个数据存储方案(schema)。但与关系型数据库相比,Elasticsearch可以快速、搞笑地对索引中的数据进行全文检索,并且不需要存储原始数据。如果你熟悉MongoDB,就可以将Elasticsearch的索引理解为MongoDB中的集合。如果你熟悉CouchDB,就可以将索引理解为CouchDB中的数据库。
索引由其名称(必须为全小写字符)进行标识,并通过引用此名称完成文档的创建、搜索、更新及删除操作。一个ES集群中可以按需创建任意数目的索引。
类型(Type)
类型是索引内部的逻辑分区(category/partition),然而其意义完全取决于用户需求。因此,一个索引内部可定义一个或多个类型(type)。一般来说,类型就是为那些拥有相同的域的文档做的预定义。
例如,在索引中,可以定义一个用于存储用户数据的类型,一个存储日志数据的类型,以及一个存储评论数据的类型。文档类型可以帮助我们轻松地区分这些对象。值得注意的是,每个文档可以有不同的结构。在实际操作中,将该文档划分为不同类型对数据操作有明显的帮助。划分时需要牢记一些限制条件,其中一个限制条件就是不同的文档类型对同一字段不能设置为不同的字段类型。类比传统的关系型数据库领域来说,类型相当于“表”。
文档
文档(document)是Elasticsearch中存储的主要实体。文档由字段(行数据的列 “key/value” 的 “key”)组成,Elasticsearch允许一个字段出现多次,拥有一个名字及一个或多个值,该类字段被称为多值字段(multivalued)。每个字段对应一种类型(字符串型、数值型、日期型等)。字段类型可以是复合的,字段可以包含其他子文档或数组。字段类型在Elasticsearch中非常重要,它使得搜索引擎知道应如何执行不同的操作,如比较、排序等。幸运的是Elasticsearch可以自动确定字段类型。与关系型数据库不同,Elasticsearch的文档不需要有固定结构,不同文档可以具有不同的字段集合,而且在程序开发时不需要知道文档的字段。当然,用户也可以通过模式映射(schema mapping)定义文档结构。
集群(Cluster)
ES集群是一个或多个节点的集合,它们共同存储了整个数据集,并提供了联合索引以及可跨所有节点的搜索能力。多节点组成的集群拥有处理大型数据集并实现容错功能,它可以在一个或几个节点出现故障时保证服务的整体可用性。集群靠其独有的名称进行标识,默认名称为“elasticsearch”。节点靠其集群名称来决定加入哪个ES集群,一个节点只能属一个集群。果不考虑冗余能力等特性,仅有一个节点的ES集群一样可以实现所有的存储及搜索功能。
集群里的每个服务器则被称为一个节点(node)。可以通过索引分片(分割成更小的个体此处不懂可以看下边的分片和副本的概念)将海量数据进行分割并分布到不同节点。通过副本(索引部分的拷贝)可以实现更强的可用性和更高的性能。这些服务器被统称为一个集群(cluster),
节点(Node)
运行了单个实例的ES主机称为节点,它是集群的一个成员,可以存储数据、参与集群索引及搜索操作。类似于集群,节点靠其名称进行标识,默认为启动时自动生成的随机Marvel字符名称。用户可以按需要自定义任何希望使用的名称,但出于管理的目的,此名称应该尽可能有较好的识别性。
节点通过为其配置的ES集群名称确定其所要加入的集群。
分片与副本
ES的“分片(shard)”机制可将一个索引内部的数据分布地存储于多个节点,它通过将一个索引切分为多个底层物理的Lucene索引完成索引数据的分割存储功能,这每一个物理的Lucene索引称为一个分片(shard)。也就是单个主机上的Lucene 索引,被命名为分片(shard)。
每个分片其内部都是一个全功能且独立的索引,因此可由集群中的任何主机存储。创建索引时,用户可指定其分片的数量,默认数量为5个。Shard有两种类型:primary和replica,即主shard及副本shard。
Primary shard用于文档存储,每个新的索引会自动创建5个Primary shard,当然此数量可在索引创建之前通过配置自行定义,不过,一旦创建完成,其Primary shard的数量将不可更改。Replica shard是Primary Shard的副本,用于冗余数据及提高搜索性能。每个Primary shard默认配置了一个Replica shard,但也可以配置多个,且其数量可动态更改。ES会根据需要自动增加或减少这些Replica shard的数量。ES集群可由多个节点组成,各Shard分布式地存储于这些节点上。ES可自动在节点间按需要移动shard,例如增加节点或节点故障时。简而言之,分片实现了集群的分布式存储,而副本实现了其分布式处理及冗余功能。
映射(Mapping)
ES中,所有的文档在存储之前都要首先进行分析。用户可根据需要定义如何将文本分割成token、哪些token应该被过滤掉,以及哪些文本需要进行额外处理等等。
另外,ES还提供了额外功能,例如将域中的内容按需排序。事实上,ES也能自动根据其值确定域的类型。
数据重新分布recovery
代表数据恢复或叫数据重新分布,es在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。
数据源river
代表es的一个数据源,也是其它存储方式(如:数据库)同步数据到es的一个方法。它是以插件方式存在的一个es服务,通过读取river中的数据并把它索引到es中,官方的river有couchDB的,RabbitMQ的,Twitter的,Wikipedia的。
索引快照gateway
代表es索引快照的存储方式,es默认是先把索引存放到内存中,当内存满了时再持久化到本地硬盘。gateway对索引快照进行存储,当这个es集群关闭再重新启动时就会从gateway中读取索引备份数据。es支持多种类型的gateway,有本地文件系统(默认),分布式文件系统,Hadoop的HDFS和amazon的s3云存储服务。
discovery.zen
代表es的自动发现节点机制,es是一个基于p2p的系统,它先通过广播寻找存在的节点,再通过多播协议来进行节点之间的通信,同时也支持点对点的交互。
Transport
代表es内部节点或集群与客户端的交互方式,默认内部是使用tcp协议进行交互,同时它支持http协议(json格式)、thrift、servlet、memcached、zeroMQ等的传输协议(通过插件方式集成)。
2 ES的核心Luence
Lucene 是当今最先进,最高效的全功能开源搜索引擎框架。但只Lucene是一个软件类库,如果要发挥Lucene的功能,还需要开发一个调用Lucene类库的应用程序。Elasticsearch 作为一个应用程序使用 Lucene 作为内部引擎进行以下工作:
- (1)分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
- (2)实时分析的分布式搜索引擎。
- (3)可扩展上百台服务器,处理PB级别的结构化或非结构化数据。
在ES 中文档是Lucene索引和搜索的原子单位,它是包含了一个或多个域的容器,而域的值则是真正被搜索的内容。每个域都有其标识名称,通常为一个文本值或二进制值。将文档加入索引中时,需要首先将数据转换成Lucene能识别的文档和域,域值是被搜索的对象。
默认情况下,所有文档都没有加权值,或者说其加权因子都为1.0。通过改变文档的加权因子,可以指示Lucene在计算相关性时或多或少地考虑到该文档针对索引中其它文档的重要程度,从而能够将有着较大加权因子的文档排列在较前的位置。
2.2.3 ES的实时性
ES通过开新文件的方式使数据更快的被检索使用提高实时性。Lucene 在倒排索引的基础上通过将新收到的数据写到新的索引文件里做到实时更新条件下数据的可用和可靠。Lucene 把每次生成的倒排索引,叫做一个段(segment)。然后另外使用一个commit文件,记录索引内所有的segment。而生成segment 的数据来源,则是内存中的 buffer。也就是说,将新接收的数据存在buffer中,内存buffer数据转移到磁盘时生成一个新的segment,同时更新commit 文件来记录这个新生成的segment。
由于磁盘速度太慢,所以为了达到实时性效果ES的做法是在内存的buffer生成一个新的segment时,把这个segment 刷到文件系统缓存中。此时lucene可以检索这个新生成的segment。文件系统缓存写入到磁盘期间可能会产生数据丢失情况,例如发生主机错误,硬件故障等异常。
为了保证数据的安全性,ES在把数据写到内存buffer的时候同时还记录了一个translog日志。当异常发生时translog日志文件依然保持原样。ES会从commit位置开始,恢复整个translog文件中的记录保持数据一致性。当文件系统的缓存真正同步到磁盘上时,commit文件才更新。Translog才清空。为了保证translog的一致性,ES默认每5秒或者每次请求操作之前都强制将translog的内容同步到磁盘上。这样降低了一点点ES的性能,但是提高了数据的安全性。ES提供了单独的/_refresh接口,默认将新生成的segment刷到文件系统缓存的时间间隔是1秒,对于大部分应用来说1秒的时间间隔都算是实时搜索了。用户如果对1秒间隔还不满意的,可以主动调用该接口来保证搜索可见。ES也提供了/_flush接口,默认每30分钟(或者translog文件大小大于512MB,老版本是200MB)主动刷新一次translog。这两个行为,可以分别通过参数修改。
不过对于ELK的日志场景来说,我们并不需要如此高的实时性,而是需要更快的写入性能。所以,一般来说,我们反而会通过/_settings接口或者定制 template的方式,加大refresh_interval 参数:
# curl -XPOST http://127.0.0.1:9200/logstash-2015.06.21/_settings -d'
{ "refresh_interval": "10s" }’
如果是导入历史数据的场合,那甚至可以先完全关闭掉:
# curl -XPUT http://127.0.0.1:9200/logstash-2015.05.01 -d'
{"settings" : {"refresh_interval": "-1"}}'
2.2.4 ES的segment合并
ES通过新建segment提高实时性,会产生大量零碎的segment。给服务器负载带来压力,消耗cup内存等资源。所以ES后台会通过一个独立的线程在不影响新的segment产生的情况下,不断的主动将零碎的segment做数据归并,尽量让索引内只保留有少量的,每个都比较大的segment文件。归并过程中,尚未完成归并的较大的segment是被排除在检索可见范围之外的。归并完成后,较大的segment写入到磁盘,commit 文件做出相应的变更,删除之前较小的segment,改成新的大的segment。当检索请求都从小的segment转移到大的segment时,删除那些小的segment。从而降低segment的数量。
归并线程是按照一定的运行策略来挑选segment 进行归并的。主要有以下几条:
- index.merge.policy.floor_segment默认2MB,小于这个大小的segment,优先被归并。
- index.merge.policy.max_merge_at_once默认一次最多归并10 个segment。
- index.merge.policy.max_merge_at_once_explicit默认optimize 时一次最多归并30 个segment。
由于默认的最大segment大小是5GB。一个比较庞大的数据索引,会有不少的segment永远存在,这对文件句柄,内存等资源都是极大的浪费。由于归并任务太消耗资源,所以选择在负载较低的时间段,通过optimize接口,强制归并segment。
2.2.5 ES路由计算
在一个没有额外依赖的简单的分布式方案中,ES对任一数据计算起存储对应的分片方式如下:shard=hash(routing)%number_of_primary_shards每条数据都有自己的routing参数,默认情况下,将其_id值进行计算哈希后,对索引中的主分片数取余,就是数据实际应该存储到的分片ID。由于这个计算方式使用索引中的主分片数作为分母,所以整个索引中的主分片数不能更改,一但更改就会导致所有数据的存储位置计算结果发生变化,索引数据就不可读了。
基于这个路由规则,数据在ES 集群中的存储流程如图2-2。当客户端发起请求给node1节点,node1拿到请求数据通过数据的_id计算出数据应该存储在shard0上,通过cluster state 信息发现shard0在node3上,所以转发请求到node3。Node3完成请求数据的索引过程,将数据存入主分片0然后并行转发数据给分配有shard0的副本的node1和node2节点。当收到任一节点汇报副本分片数据写入成功,node3才返回给初始接收客户请求的节点node1,宣布数据写入成功。node1返回成功响应给客户端。
2.2.6 ES的shard分配机制
Shard分配在那个节点上是由ES自动决定的,只有新索引生成、索引删除、新增副本分片和节点增减引发的数据均衡情况会触发shard分配操作。
ES为了保护节点数据安全,采用磁盘限额策略。默认每隔30 秒会检查各个节点的数据目录磁盘使用情况,默认达到85%时,新索引就不会再分配到这个节点上。默认达到90%时就会触发该节点现存分片的数据均衡,把数据挪到其他的节点去。
ES集群的数据均衡策略是以各个节点的分片总数为基准的。这是均衡搜索压力提高性能的好方法。但对于ELK场景,一般压力集中在新索引的数据写入方面。当集群扩容是,新加入集群的节点,分片数远远低于其他节点。这个时候如果有新的索引创建,ES会默认将新索引的所有主分片几乎全分配到这台节点上。整个集群的写入压力,压在这个节点上,结果是直接把这个节点压死。集群出现异常。故对于ELK场景应该预先计算好索引的分片数,配置好单节点的分片限额。
为了解决大规模查询,大量读IO操作及聚合计算导致机器load增高cpu使用率升高,影响阻塞到新数据的写入,ES采用了读写分离方案。可以设置N台机器做热数据的存储,上面只放当天的数据。只需在这N台节点的elasticsearc.yml 中配置node.tag: hot之前的M台机器作为冷数据节点,只需在这M台节点配置node.tag:stale。每天计划任务更新索引的配置把N台节点的配置更改为stale,索引会自动迁移到M台冷数据节点。
关于安装请看下篇博客