文章目录
1. 简介
Elasticsearch 是一个分布式、可扩展、实时的 搜索与数据分析引擎 。Elasticsearch 不仅仅只是全文搜索,还支持结构化搜索、数据分析、复杂的人类语言处理、地理位置和对象间关联关系等,天生具有良好的水平伸缩性。
Elasticsearch是一个 近实时 的搜索平台。这意味着,从索引一个文档直到这 个文档能够被搜索到有一个轻微的延迟(通常是1秒)
不同于关系型数据库的行和列存储,Elasticsearch 是 面向文档 的,不仅存储文档,而且 索引 每个文档的内容,使之可以被检索。
2. 集群原理
ElastiSearch天生就是 分布式的,主旨是随时可用和按需扩容。 扩容可以通过购买性能更强大( 垂直扩容 ) 或者数量更多( 水平扩容 )的服务器来实现。但是垂直扩容是有极限的。 真正的扩容能力是来自于水平扩容,将负载压力分散到这些节点中。
2.1 角色
主节点
主节点负责负责 管理集群 范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。 主节点并 不涉及到文档级别的变更和搜索等操作 ,所以即使流量的增加它也不会成为瓶颈。
任何节点都可以成为主节点,主节点从候选主节点中选举产生。生产环境建议分离数据节点与主节点,即设置专用的候选主节点,避免因数据节点负载重导致主节点不响应。
数据节点
数据节点 保存数据并执行与数据相关的操作 ,例如CRUD,搜索和聚合。数据节点的负载较重,对CPU、内存、硬盘要求较高。
协调节点
协调节点 接收客户端请求 的节点。搜索、批量请求可能涉及到不同节点的数据,协调节点将请求分发到对应的数据节点(scatter阶段),将数据节点返回的结果集聚合处理后返回给客户端(gather阶段)。此外,协调节点还起到 负载均衡 的作用。
每个节点都是隐式的协调节点,由于gather阶段需要消耗较多的CPU和内存,建议指定专用协调节点。
2.2 分片
Elasticsearch 每个索引中的数据都被分发到多个分片之中,每个分片都保留全部数据的一部分。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。 当集群规模扩大或者缩小时, Elasticsearch 会自动的在各节点中迁移分片,使得数据仍然均匀分布在集群里。每个分片都是一个 Lucene 的实例,本身就是一个完整的搜索引擎。
主分片
主分片响应索引请求,主分片的数量在创建索引时指定,决定着索引能够保存的最大数据量。因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。
当节点故障导致在索引丢失部分主分片时,Elasticsearch 会将副本分片提升为主分片。
副本分片
副本分片只是一个主分片的拷贝。副本分片作为冗余备份提供故障转移,并为搜索和返回文档等读操作提供服务。提高系统的可用性和搜索性能。
集群健康
green
所有的主分片和副本分片都正常运行。yellow
所有的主分片都正常运行,但不是所有的副本分片都正常运行。red
有主分片没能正常运行。
3. 分布式文档存取
3.1 路由
当索引一个文档的时候,文档会被存储到一个主分片中。Elasticsearch 通过路由规则决定文档应当存储在哪个分片中。
shard = hash(routing) % number_of_primary_shards
默认使用文档_id
的哈希码对主分片数取余来路由文档。
3.2 索引、新建、删除文档
索引、新建、删除文档都是写操作,会现在主分片上完成之后再复制到相关的副本分片上。
具体流程是:
- 客户端向协调节点发送写请求;
- 协调节点将请求路由到文档所属主分片的节点上;
- 目标节点在主分片上执行请求,并将请求并行转发到副本分片所处的节点上;
- 一旦所有副本分片都执行成功,主分片节点将向协调节点报告成功,协调节点向客户端报告成功。
所以当客户端收到成功响应时,文档变更在主副分片上均已执行成功。默认情况下,ES在半数以上的副本分片正常时,才会允许写操作,避免网络分区时产生不一致性。
3.3 获取文档
可以从主分片或者任意副本分片上获取文档。
具体流程:
- 客户端向协调节点发送请求;
- 协调节点根据路由规则确定文档所属主分片,通过轮训算法将请求转发到主分片或者副本分片所处节点;
- 目标节点执行请求,通过协调节点将请求返回给客户端。
3.4 更新文档
ES中的文档都是不可变的,更新操作会首先读取原有文档,应用更新后,创建一个新的文档,并删除之前的文档。主分片更新完成后,会将完整的新文档并行复制到副本分片上。之所以采用基于文档的复制,而不是转发更新请求,是因为无法保证多个更新操作有序复制到副本分片。
ES使用_version
字段进行乐观的并发版本控制。UPDATE API在GET时会出发一次刷新,而UPDATE BY QUERY则直接从searcher里检索文档。
3.5 搜索文档
搜索分为两个阶段:query then fetch,
查询阶段:
- 协调节点接受客户端请求,并构建一个大小为from + size的优先级队列用于聚合搜索结果;随后协调节点会将查询请求广播到索引中每一个分片拷贝(主分片或者副本分片);
- 每个分片在本地执行搜索并将结果放入一个大小为from + size的优先队列。随后返回一个轻量级的结果集列表给协调节点,即优先级队列中所有文档的ID和排序值;
- 协调节点将所有分片的结果合并到自己的优先级队列中,获得全局的搜索排序结果。
取回阶段:
- 协调节点确定要获取的文档并向执行查询的分片发送mutil-get请求;
- 每个分片将文档所需字段返回;
- 协调节点收集到所有文档后,将结果返回给客户端。
在全文搜索的下,Query then Fetch 会出现打分偏离的情形,Elasticsearch提供DFS Query then Fetch的搜索方式,使用预查询来计算整体文档的frequency等信息。
深度分页
协调节点需要根据 number_of_shards * (from + size)
排序文档,来找到被包含在 size
里的文档。from值若很大,将会给协调节点带来很高的负载,排序过程将会变得沉重。
ES的深度分页默认只支持到前1万条记录。
词项(词条)搜索
每个字段被分词后的单词称之为词项(term),当倒排索引中的词项数量很大时,顺序遍历的方式检索词项将会变得不可行。
Elasticsearch将词项排序后放入Term Dictionary中,利用跳表和二分查找来快速定位词项,其时间复杂度为O(lgN)。当词项很多时,将Term Dictionary全部放入内存有些不太现实,于是将Term Dictionary分块并利用Term Index和有限状态机(Trie前缀树)来定位词项所属的分块。
在多词项的联合查询中,Elasticsearch使用跳表或者位图(Roaring Bitmaps)完成多个搜索结果文档ID的快速与运算,实现联合查询。
4. 分片的内部原理
4.1 分段存储
Elasticsearch的全文搜索是基于倒排索引的:对字段分词后,建立词项到文档的索引,并根据相关性排序搜索结果。
索引数据是按段(Segment),每个段都是倒排索引的最小单位,分段存储的优点在于:
- 数据增加、变更时,无需全量更新倒排索引;
- 加上段的不变性,可以避免使用锁;
段具有不变性,一旦生成就不能被修改,不变性的有点在于:
- 不需要锁,没有对段进行修改也就不需要做并发控制;
- 缓存,可以直接利用文件系统缓存;
- 其它缓存,比如Filter缓存在索引的生命周期内始终有效,不需要在数据改变时重建,因为段数据不会变化;
- 段可以被压缩存入磁盘;
由于段具有不变性,删除数据并不会将文档从段中移除,而是在.del文件中记录被删除的文档信息。文档仍然可以被搜索匹配到,只是会从结果集中删除。类似的,更新操作变成了:删除+新增两个操作的结合。
不变性的缺点主要在于:存储空间占用大,旧数据只有在段合并(merge)期间才会被删除。倘若频繁更新数据,存储空间将会浪费更多。
4.2 延迟刷新
Elasticsearch的近实时搜索特性来源于延迟刷新策略。
当有新的文档写入时,先将其写入到JVM的内存中,当达到一定的时间或者缓存的数据达到一定量时,会触发一次刷新(refresh):将缓存的文档生成一个新的段,异步刷盘,首先写到文件系统的缓存上,稍后将刷新到磁盘上并生成一个提交点(commit point)。
只有文档以段的形式存在时才能被搜索到,也就是文档生成段并刷新到文件系统缓存,才能供搜索使用,但不需要等段被刷新到磁盘。
默认情况下,每个分片会每秒刷新一次。这Elasticsearch文档近实时搜索的原因,文档变化并不是立即对搜素可见,但是会在1秒之后变为可见。
段被写入磁盘后,会有一个提交点,一旦拥有提交点,段不可修改。
4.3 事务日志
延迟刷新和异步刷盘在提升了Elasticsearch写入能力的情况下,增大了数据丢失的风险。Elasticsearch引入了事务日志(Translog)来记录还没有持久化到磁盘的数据,是一种WAL(Write Ahead Log)策略,提供Crash-Safe能力。
引入事务日志后,文档的写入流程为:
- 文档首先被添加到缓存中,同时会在事务日志中追加一条记录;
- 当达到默认刷新时间或者缓存数据达到一定量时,触发刷新操作,生成新的段并写到文件系统缓存;
- 当日志文件达到一定量时或者超过一定时间时,触发一次Flush操作——将文件系统缓存中的数据落盘,生成一个commit point,然后清空日志文件。
4.4 段合并
Elasticsearch默认情况下,每秒就会生成一个段。段的数量太多时会带来较大的资源损耗:文件句柄、内存和CPU消耗等等;在搜索阶段,搜索请求需要检查每一个段,然后合并查询结果,段越多搜索速度越慢。
为此,Elasticsearch引入了段合并(Segment Merge)。段合并在后台定期进行,将多个大小相似的小段合并成一个大段,同时删除旧段,并将新段刷新到磁盘。
在段合并期间,Elasticsearch会将已被删除的旧文档作物理删除——删除文档不会被拷贝到新的大段中。
段合并的计算量较大,对磁盘IO消耗也较高,会影响正常的数据写入速率。
参考
《Elasticsearch实战与原理解析》