1. 概念
- Document(文档)最小数据单元。
- Index(索引)包含一堆有相似结构的document数据,代表了一类相似或者相同的document。
- Type(分类)表示Index中的一个逻辑数据分类。每个Index可以有多个type,而每个type又可以存储多个document。
5.X版本一个index可创建多个type。
6.X版本一个index只能存在一个type。
7.0版本开始,移除Type概念。主要原因是在同一个索引中,存储仅有小部分字段相同或者全部字段都不相同的document,会导致数据稀疏,影响Lucene有效压缩数据的能力,目前在 7.x 中兼容Type。
- Field(字段)document里面有多个field,每个field就是一个数据字段。
- Shard(分片)单台机器无法存储大量数据,Elasticsearch可以将一个Index(索引)中的数据切分为多个Shard,分布在不同服务器节点上,这样就可以实现水平扩展,提升吞吐量和性能。这其实是一种数据分散集群的架构模式。
每个Shard都是一个Lucene index,可以看成是一个Lucene实例。
- Replica(副本)Shard中的数据就可能丢失,为每个Shard创建多个备份(Replica副本)。Replica可以在Shard故障时提供备用服务,保证数据不丢失,多Replica提升搜索的吞吐量和性能,主从架构模式。
每个索引的Primary Shard数量,需要在索引建立时就设置,一旦设置完不能修改,而Replica Shard可以随时修改数量。
| Elasticsearch | 关系型数据库 |
|---|---|
| Index | 数据库实例 |
| Type | 表 |
| Document | 行 |
| Field | 列 |
2. 状态
- green:每个索引的primary shard和replica shard为active状态。
- yellow:每个索引的primary shard为active状态,但部分replica shard非active状态(即不可用状态)。
- red:部分索引的primary shard非active状态,部分索引有数据丢失。
3. 负载均衡
3节点集群创建索引,共有3个primary shard,每个primary shard都有2个replica shard,那总共就是9个shard:
- P1 / P2 / P3:test_index索引的数据会被均匀分布到3个primary shard。
- R?-?:每个primary shard都有一个replica shard,replica shard其实就是个数据备份,类似于msater/slave模式中的slave节点。6个replica shard也会被负载均衡到各个ES进程节点上。
Elasticsearch有一个原则:primary shard和它对应的replica shard不能全部署在同一个节点上,否则节点挂了的话,数据和拷贝都会丢失。
4. 高可用
Elasticsearch保证集群高可用的方式采用 主从模式 + 选举的方式 。
节点1宕机了,对于P1这个primary shard为非active状态,此时集群状态转为red。但是节点2和节点3上仍然保存着完整的数据,P1的两个副本R1-1和R1-2开始一轮选举,胜出者为primary shard,假设R1-1胜出。
当宕机的节点1恢复后,shard又会重新加入到集群中,原来P1发现节点2上有新的primary shard,自身变成repilca shard,并进行数据同步。
5. 水平扩展
如上图为原始集群,空间占满后2倍/3倍扩容效果如下:
6. 并发控制
6.1. 版本号
Elasticsearch内部采用了版本号机制对document的并发修改进行控制。所谓版本号,本质是一种乐观锁。Elasticsearch写入document数据时,ES会自动为document生成一个版本号_version。首次创建document时,_version版本号为1,每次对这个document修改或删除时,_version版本号会自动加1。
执行流程
- 假设我们现在有两个线程A和B,同时读取到了商品信息,此时获取到的document的_version版本号是相同的,假设都是1。
- A线程在本地将库存减1后,发送PUT更新请求,请求里带上了版本号
PUT /product/storage/1?version=1。 - ES收到请求后,对数据进行更新,然后将版本号+1。
- B线程在本地将库存减1后,发送PUT更新请求,请求里也带上了版本号1,
PUT /product/storage/1?version=1。 - ES收到请求后,发现id为1的document的版本号已经是2了,而B线程的更新请求里带的版本号还是7,两者不一样,于是拒绝更新,返回报错。
6.2. 自定义版本号
基于自身维护的一个版本号来进行并发控制。
- 内部版本语法
?version=1 - 自定义版本号语法
?version=1&version_type=external
当version_type=external时,只有当提供的version比Elasticsearch中的_version大的时候,才能完成修改。
7. 路由原理
Elasticsearch采用 hash路由算法 对document记录的id标识进行计算,产生了一个shard序号,通过这个shard序号就可以立即确认写到哪个shard里面,路由算法shard = hash(routing_key) % number_of_primary_shards。也可以手动指定document的routing_key值,那么routing_key相同的document就会路由到同一个shard中。
7.1. 写入
例如:初始3个primary shard,每个primary shard都有一个副本。初始时,客户端(集成了Elasticsearch Client SDK)发起了一条document的写入请求,请求可能hit到任意某个ES节点上,hit到的这个节点也叫做coordinate node(协调节点)
每个ES节点其实都知道集群中的其它节点的信息,包括集群中一共有多少primary/replica shard,每个节点上分配着哪些primary/replica shard。
- 假设ES进程2节点(协调节点)接受到了请求,于是根据document的id进行hash计算,发现结果是3,也就是应该由P3这个primary shard处理这个请求,所以就会把请求转发给ES进程3节点上的P3。
- primary shard 3处理完请求后,会将数据同步到自己的replica shard(R3-1),同步完后响应ES进程2,ES进程2(协调节点)收到响应后,返回给ES client结果。
Elasticsearch对于写请求,最终都是转交给primary shard去处理的。
7.2. 读取
document数据查询的原理基本和写入类似,查询请求既可以由primary shard处理,也可以由replica shard处理,提升系统的吞吐量和性能。coordinate node(协调节点)在接受到查询请求后,会采用round-robin算法,在对应的primary shard及其所有replica中随机选择一个发送请求,以达到读请求负载均衡的目的。
例如:客户端发起查询某个document的请求,假设命中到ES进程2,ES进程2根据document Id计算出应该由primary shard 3来处理,primary shard 3有一个replica,所以协调节点会采用round-robin算法选取其中一个转发请求,比如选择了R3-1,然后将请求转发给它,R3-1查询得到结果后返回,最终ES进程2将结果返回给客户端。
8. 数据一致性
Elasticsearch其实提供了三种数据同步机制:one,all,quorum,在请求时带上consistency参数,默认是quorum。
PUT /index/type/id?consistency=quorum
- one:对于document的写操作(增删改),只要有一个primary shard是active活跃可用的,操作就可以执行。
- all:对于document的写操作(增删改),要求必须所有的primary shard和replica shard都是活跃的,才可以执行这个写操作。
- quorum:对于document的写操作(增删改),写之前必须确保大多数shard都可用,当不满足“大多数”这个条件时,请求就会默认等待1分钟,超过时间就会报timeout错误。我们可以在写操作的时候,加一个timeout参数。
PUT /index/type/id?timeout=30
Elasticsearch是通过一个公式去计算“大多数”shard。
(primary + number_of_replicas) / 2 + 1
9. 数据持久化
Segment File
在Elasticsearch中创建一个index索引时,需要指定shard分片,一个shard分片在底层其实是一个Lucene索引,它由若干个segment文件和对应的commit point(提交点文件)构成。
Segment文件可以理解成底层存储document数据的文件,ES进行检索时最终是从它里面检索出数据,而commit point文件可以理解成一个保存了若干segment信息的列表,它标识着这个commit point前所有旧的segment file文件。
例如:当Elasticsearch创建commit point文件时,会已有一些segment文件是已经存在的,那commit point就保存着这些旧的segment文件的信息。
写入流程
一条document数据被写入磁盘会经历以下几个过程。write、refresh、flush、merge。
Write
document会被写入in-memory buffer中,所谓in-memory buffer其实就是应用内存。同时,document数据会被写入translog日志文件。
Refresh
每隔1秒Elasticsearch会执行一次refresh操作,将buffer中的数据refresh到filesystem cache中的一个新segment file中。filesystem cache等价os cache。注意:此时的segment file仅仅是存在于os cache中的缓存数据,并不存在于磁盘上。
refresh操作完成后,buffer会被清空。如果buffer满了也会执行refresh。当数据进入filesystem cache后可以被检索到。
Elasticsearch是准实时(NRT,near real-time),默认每隔1秒refresh一次,写入的数据1秒之后才能被检索到。通过index的index.refresh_interval参数配置refresh的时间间隔。
Flush
segment file一直存在于os cache中,如果发生宕机cache数据会丢失,提供一种机制将缓存数据写入磁盘文件。在refresh的过程中,os cache中的segment file会越来越多(每次refresh都会创建一个新的segment file)。当translog到达阈值时触发flush操作。
- flush的第一步,将buffer中的现有数据refresh到os cache中,清空buffer。
- 在磁盘上写入一个commit point文件,里面标识着这个commit point前os cache中的所有旧segment file文件数据。
- 强行将os cache中的所有数据fsync到磁盘文件中去。
- 最后将translog清空,因为此时里面记录的数据此时都已经被fsync到磁盘了。
默认每隔30分钟会自动执行一次flush操作,如果translog过大会提前触发。
在执行flush操作之前,数据要么是停留在buffer中,要么是停留在os cache中,此时一旦这台机器宕机,数据就全丢了。所以,需要将数据写入到一个专门的日志文件中,此时即使机器宕机了,再次重启时,Elasticsearch会自动读取translog日志文件中的数据,恢复到内存buffer和os cache中去。translog中的数据,本身也是先写入os cache,然后默认每隔5秒刷一次到磁盘中。所以默认情况下,即使有translog保证可用性,也可能丢失5s的数据。此时数据仅仅停留在buffer或者translog文件的os cache中,如果此时机器挂了,会丢失这5秒钟的数据。
保证数据不丢失设置index的index.translog.durability参数,每次写入一条数据都写入buffer,同时fsync写入磁盘上translog文件,写性能、吞吐量严重下降。
Merge
每refresh一次会在os cache中产生一个新的segment file,随着segment file增多,搜索的性能会降低。Elasticsearch会定期执行merge操作,每次merge时,ES会选择一些相似大小的segment进行合并,同时会将那些标识为deleted的document物理删除掉。合并后新的segment file会被写入磁盘,同时会新建commit point文件,里面标识着所有合并后新的segment file。