分布式特性

es支持集群模式,是一个分布式系统,主要有以下好处:

  • 增大系统容量,如内存、磁盘,使得es集群可以支持PB级的数据
  • 提高系统可用性,即使部分节点停止服务,整个集群依然可以提供正常服务

es集群由多个es实例组成:

  • 不同集群通过集群名称来区分,可通过cluster.name进行配置
  • 每个es实例本质上是一个JVM进程,通过node.name配置实例名称

Cluster State

es集群相关的数据称为cluster state,主要记录如下信息:

  • 节点信息,比如索引名称、连接地址等
  • 索引信息:比如索引名称、配置等
  • ……

Master Node

一个集群只能有一个Master,只有master可以修改cluster state

cluster state存储在每个节点上,master维护最新版本并同步给其他节点

master节点是通过集群中所有节点选举产生的,可以被选举的节点称为master-eligible,可以通过node.master配置哪些节点可以被选举

Coordinating Node

处理请求的节点称为coordinating节点,该节点为所有节点的默认角色,不能取消

  • 路由请求到正确的节点处理,比如创建索引的请求到master节点

Data Node

存储数据的节点称为data节点,默认所有节点都是data节点,可以通过配置node.data配置哪些节点可以存储数据

系统可用性

服务可用性:2个节点的情况下,允许其中1个节点停止服务

数据可用性:(1)引入副本(Replication)解决(2)每个节点上都有完备的数据

es提供集群使得服务实现可用性,引入副本,例如test_index在es-01实例的节点上,那么test_index有一个数据副本存储在另一台节点之上

增大系统容量

es引入分片(Shard)的概念来使数据分布在所有节点上,分片使得es能够支持PB级数据

  • 分片存储了部分数据,可以分布在任意节点
  • 分片数在索引创建时指定,并且后续不允许在修改
  • 分片有主分片和副本分片之分,副本分片也就是上面提到的副本(Replication),实现数据可用性

Cluster Health

可以通过下面API查看集群健康状态

curl -XGET http://localhost:9200/_cluster/health?pretty

集群的健康状态有3种:

  • green:健康状态,所有主副分片都正常分配
  • yellow:所有主分片都正常分配,但是有副本分片未正常分配
  • red:有主分片未分配

故障转移

集群中的各个节点都会互相的做心跳检测,当es-02和es-03发现es-01无法响应后,会发起master选举

假如es-02被选举成为新的master后,由于主分片P0下线,集群状态变为Red

master(es-02)认为P0未被分配,就将其副本R0提升为P0,此时,所有主分片都被分配,但仍有副本分片未分配,集群状态变为Yello

master(es-02)会为P0和P2生成新的副本,集群状态变为绿色

文档分布式存储

文档存储

es通过下面算法计算文档存储在哪个分片上

shard = hash(routing) % numbers_of_primary_shards
  • hash算法可以保证文档均匀的分散在所有分片上
  • routing是一个关键参数,默认是文档id,也可以自行指定
  • numbers_of_primary_shards为主分片数

文档创建与读取流程

文档创建流程

  • client发送创建文档的请求到es-03
  • es-03通过routing算法计算该文档应该存储在P2分片,查询cluster state后得知P2分片在es-02上,然后将创建文档的请求发给es-02
  • P2接收到请求并且执行创建文档的请求,然后将同样的请求发送给副本R2
  • R2接收并执行创建文档请求后,通知P2成功的结果
  • P2接收副本分片R2结果后,返回成功给es-02
  • es-02返回结果给client

文档读取流程

  • client向es-03发送获取文档的请求
  • es-03通过routing算法计算该文档存储在P2,查询cluster state得知主分片P2、副本分片R2列表,然后以轮询的机制获取一个分片,比如这里是R2,就将请求转发到es-01
  • R2接收并执行读取文档请求,将结果返回给es-03
  • es-03返回结果给client

文档批量创建流程

  • client向es-03发送批量创建文档的请求(bulk)
  • es-03通过routing计算所有文档的分片,然后按照主分片分配对应的执行操作,同时发送请求到涉及的主分片
  • 主分片接收请求并执行,将同样的请求同步到副本分片
  • 副本分片执行后将结果返回给主分片
  • 主分片获得副本分片执行结果,返回给es-03
  • es-03将结果返回给client

文档批量读取流程

  • client向es-03发送批量读取文档的请求(mget)
  • es-03通过routing计算所有文档对应的分片,然后以轮询的机制获取参与的分片,按照分片构建mget请求,同时发送请求到涉及的分片
  • 涉及处理请求的分片将结果返回给es-03
  • es-03返回结果给client

文档搜索实时性

es会将文档写入倒排索引,倒排索引一旦生成,不能修改,其好处有:

  • 不用考虑并发写文件问题,杜绝锁机制带来的性能问题
  • 由于文件不在更改,可充分利用文件系统缓存
  • 利于对文件进行压缩存储

其坏处为需要写入新文档时,必须重新构建倒排索引文件,然后代替老文件,新文件才能被索引,导致文档实时性差。解决方案就是写入的新文档直接生成新的倒排索引文件,查询时同时将请求引导到所有的索引文件,然后做结果汇总计算。

Lucene采用这种方案,它构建的单个倒排索引称为segment,合在一起称为Index,ES中的一个shard对应Lucene Index,Lucene 会有一个专门的文件来记录所有的 segment 信息,称为 commit point。

refresh

segment写入磁盘的过程依然很耗时,可以借助文件系统缓存的特性,将segment在缓存中创建并开放查询来进一步提高文档实时性,该过程在ES里面称为refresh。

在refresh之前,文档会先存储在一个buffer中,refresh时将buffer中的所有文档清空并生成segment

es默认每1秒执行一次refresh,refresh发生时机主要有以下几种情况:

  • 间隔时间到达,通过index.setting.refresh.interval来设定
  • index buffer占满时,其大小通过indices.memory.index_buffer.size设置,默认为JVM heap的10%,所有分片共享
  • flush发生时也会触发refresh

translog

如果在内存中的segment还没有写入磁盘前宕机,那么其中的文档也就无法恢复,es为了解决这个问题,引入translog机制

  • 写入文档到buffer,同时将该操作写入translog
  • translog会及时写入到磁盘(fsync),相关配置 index.translog.*
  • es启动时检查translog文件,从中恢复数据

flush

flush负责的主要工作如下:

  • 将translog写入磁盘
  • 将index buffer清空,其中的文档生成一个新的segment,相当于refresh操作
  • 更新commit point并写入磁盘
  • 执行fsync操作,将内存中的segment写入磁盘
  • 删除旧的translog文件

flush发生的时机主要如下几种情况:

  • 间隔时间到达,通过index.translog.flush_threshold_period参数
  • translog占满时,其大小可以通过index.translog.flush_threshold_size控制,默认512MB,每个index都有自己的translog

删除与更新文档

segment一旦生成就不能修改,Lucene专门维护一个.del文件,记录所有已经删除的文档,.del 文档上记录的是文档在 Lucene 内部记录的 ID;在查询结果返回前会过滤掉 .del 中的所有文档

Segment merge

随着segment的增多,一次查询segment数量增多,查询变慢,es会定时的进行segment merge操作,减少segment的数量,也可以通过 force_merge API 可以手动强制做 segment merge 的操作。

脑裂

脑裂问题是分布式系统的经典问题,例如下图:

  • 集群的各个es实例都会互相做心跳检测,当es-01的心跳线出现问题时,无法与es-02和es-03进行通信
  • es-02和es-03会进行选举master,比如es-02称为新的master,此时会更新cluster state
  • es-01也会检测到无法与es-02和es-03通信,然后自己会组件集群,也会更新cluster state

这样子就会同时存在2个master和数据存在2个差异的cluster state,当心跳线恢复之后,无法正确选出master

解决方法:

在可选举的master-eligible节点大于等于quorum时才可以进行选举

quorum = master-eligible 节点数 / 2 + 1

就如上面所说,心跳线出现问题时,整个集群被一分为二

  • 左侧是1个节点数,master-eligible=1,quorum=1/2+1,即不满足master-eligible节点大于等于quorum条件,无法进行选举
  • 右侧2个节点,master-eligible=2,quorum=2/2+1,即满足master-eligible节点大于等于quorum条件,可以进行选举

es提供 discover.zen.minimum_master.nodes参数,设定其值为quorum即可避免脑裂问题