Elasticsearch

Elasticsearch 是一个基于 Lucene 构建的开源、分布式、RESTful接口的全文搜索引擎。

Elasticsearch的概述

Elasticsearch(ES)是一个基于Lucene构建的开源、分布式、RESTful接口的全文搜索引擎。Elasticsearch还是一个分布式文档数据库,其中每个字段均可被索引,而且每个字段的数据均可被搜索,ES能够横向扩展至数以百计的服务器存储以及处理PB级的数据。可以在极短的时间内存储、搜索和分析大量的数据。通常作为具有复杂搜索场景情况下的核心发动机。

ElasticSearch就是为高可用和可扩展而生的。可以通过购置性能更强的服务器或者升级硬件来完成系统扩展,称为垂直或向上扩展(Vertical Scale/Scaling Up)。另一方面,增加更多的服务器来完成系统扩展,称为水平扩展或者向外扩展(Horizontal Scale/Scaling Out)。尽管ES能够利用更强劲的硬件,垂直扩展毕竟还是有它的极限。真正的可扩展性来自于水平扩展,通过向集群中添加更多的节点来分担负载,增加可靠性。ES天生就是分布式的:它知道如何管理多个节点来完成扩展和实现高可用性。这也意味你的应用不需要做任何的改动。

基本概念

全文搜索(Full-text Search)

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。 在全文搜索的世界中,存在着几个庞大的帝国,也就是主流工具,主要有:

  • Apache Lucene
  • Elasticsearch
  • Solr
  • Ferret

倒排索引(Inverted Index)

该索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。Elasticsearch能够实现快速、高效的搜索功能,正是基于倒排索引原理。

节点 & 集群(Node & Cluster)

Elasticsearch 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个Elasticsearch实例。单个Elasticsearch实例称为一个节点(Node),一组节点构成一个集群(Cluster)。

Document (文档)

文档指的是用户提交给 ES 的一条数据。需要注意的是,这里的文档并非指的是一个纯字符串文本,在 ES 中文档指的是一条 JSON 数据。如果对 MongoDB 有了解的话,这里文档的含义和 MongoDB 中的基本类似。

JSON 数据中可以包含多个字段,这些字段可以类比为 MySQL 中每个表的字段。

例如:

{
  "message": "this is my blog",
  "author": "cyhone"
}

这样我们后期进行搜索和查询的时候,也可以分别针对 message 字段和 author 字段进行搜索。

Index (索引)

Index(索引) 可以理解为是文档的集合,同在一个索引中的文档共同建立倒排索引。

也有很多人会把索引类比于 MySQL 中 schema 的概念。但在 ES 中 Index 更加灵活,用起来也更加方便。

此外,提交给同一个索引中的文档,最好拥有相同的结构。这样对于 ES 来说,不管是存储还是查询,都更容易优化。

类型(Type)

Document 可以分组,比如employee这个 Index 里面,可以按部门分组,也可以按职级分组。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document,类似关系型数据库中的数据表。 不同的 Type 应该有相似的结构(Schema),性质完全不同的数据(比如 products 和 logs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。

文档元数据(Document metadata)

文档元数据为_index, _type, _id, 这三者可以唯一表示一个文档,_index表示文档在哪存放,_type表示文档的对象类别,_id为文档的唯一标识。

字段(Fields)

每个Document都类似一个JSON结构,它包含了许多字段,每个字段都有其对应的值,多个字段组成了一个 Document,可以类比关系型数据库数据表中的字段。 在 Elasticsearch 中,文档(Document)归属于一种类型(Type),而这些类型存在于索引(Index)中,下图展示了Elasticsearch与传统关系型数据库的类比:

整体结构

下图是ES的主要结构图:

包含了这些模块:

  • Transport Client/Node Client/REST API:三种访问es集群的方式
  • Transport(Netty):通信模块,数据传输,底层采用netty框架
  • Index、Search…:支持搜索,索引等常用操作
  • Discovery:节点发现,集群之间通信的基石
  • Plugins:很多服务以插件形式提供,官方和社区支持的ik、head、river、discovery gce…
  • Script:提供脚本支持,内置painless,groovy等,默认painless
  • Store/Snapshot:文件存储与访问,快照创建和恢复
  • translog、cluster state、segments:es主要文件类型,其中translog、cluster state是es添加的数据,多个segments段组成一个完整的lucene索引
  • Monitor:监控模块,监控jvm,文件系统,操作系统等运行情况
  • File System:es支持可以在多种文件系统上运行,本地、共享型、HDFS、亚马逊云平台等

架构

集群

节点架构图

分片

每一个 shard 就是一个 Lucene Index,包含多个 segment 文件,和一个 commit point 文件。

在 es 配置好索引后,集群运行中是无法调整分片配置的。如果要调整分片数量,只能新建索引对数据进重新索引(reindex),该操作很耗时,但是不用停机。

分片时主要考虑数据集的增长趋势,不要做过度分片。

每个分片都有额外成本:

  • 每个分片本质上就是一个 Lucene 索引, 因此会消耗相应的文件句柄, 内存和 CPU 资源
  • 每个搜索请求会调度到索引的每个分片中. 如果分片分散在不同的节点倒是问题不太. 但当分片开始竞争相同的硬件资源时, 性能便会逐步下降
  • 每个搜索请求会遍历这个索引下的所有分片
  • ES 使用词频统计来计算相关性. 当然这些统计也会分配到各个分片上. 如果在大量分片上只维护了很少的数据, 则将导致最终的文档相关性较差
  • es 推荐的最大 JVM 堆空间时 30~32G,所以如果分片最大容量限制为 30G,假如数据量达到 200GB,那么最多分配 7 个分片就足够了。过早的优化是万恶之源,过早的分片也是。

流程

写入数据

  • 客户端选择一个 node 发送请求过去,这个 node 就是 coordinating node (协调节点)
  • coordinating node,对 document 进行路由,将请求转发给对应的 node
  • 实际上的 node 上的 primary shard 处理请求,然后将数据同步到 replica node
  • coordinating node,如果发现 primary node 和所有的 replica node 都搞定之后,就会返回请求到客户端
  • 其中步骤 3 中 primary 直接落盘 IO 效率低,所以参考操作系统的异步落盘机制:

  • ES 使用了一个内存缓冲区 Buffer,先把要写入的数据放进 buffer;同时将数据写入 translog 日志文件(其实是些 os cache)。
  • refresh:buffer 数据满/1s 定时器到期会将 buffer 写入操作系统 segment file 中,进入 cache 立马就能搜索到,所以说 es 是近实时(NRT,near real-time)的
  • flush:tanslog 超过指定大小/30min 定时器到期会触发 commit 操作将对应的 cache 刷到磁盘 file,commit point 写入磁盘,commit point 里面包含对应的所有的 segment file
  • translog 默认 5s 把 cache fsync 到磁盘,所以 es 宕机会有最大 5s 窗口的丢失数据

读取数据

  • 客户端发送任何一个请求到任意一个 node,成为 coordinate node
  • coordinate node 对 document 进行路由,将请求 rr 轮训转发到对应的 node,在 primary shard 以及所有的 replica 中随机选择一个,让读请求负载均衡,
  • 接受请求的 node,返回 document 给 coordinate note
  • coordinate node 返回给客户端

搜索过程

  • 客户端发送一个请求给 coordinate node
  • 协调节点将搜索的请求转发给所有的 shard 对应的 primary shard 或 replica shard
  • query phase:每一个 shard 将自己搜索的结果(其实也就是一些唯一标识),返回给协调节点,有协调节点进行数据的合并,排序,分页等操作,产出最后的结果
  • fetch phase ,接着由协调节点,根据唯一标识去各个节点进行拉去数据,最总返回给客户端