ES原理剖析

特性

es重度使用了文件系统缓存,即:pacache.

es名词解释

  1. Cluster:ES集群,由多个Node组成.

  2. Node:节点,一个es实例就是一个node.

  3. index:索引,一系类Documents的集合.对于存入的数据,都会有一个索引对应.

  4. shard:分片.每个索引都会被分为多个分片,默认是5个.存储在不同的节点上.

  5. replica:副本,容灾.默认会有5个副本.副本和分片不会出现在同一个node上.

新增数据集群调度

对于每条数据,都会给其分配_id,而数据分配到哪个节点就是根据这个参数计算.

根据id定位分片:

//节点0,1,2,3        主分片数
shard = hash(_id) % number_of_primary_shards

默认情况下,每个索引会建立5个主分片.

在客户端请求写入数据时,流程如上图.

  1. 请求由master节点处理.

  2. mater是node1.

  3. master经过_id计算shard,发现算出来是shard1,则根据集群中的信息寻找shard1存在了node2节点上,将请求转发到node2.

  4. node2数据处理完后,告诉node3进行副本数据同步.

  5. node3数据备份完成后,告知node2.

  6. node2返回master节点node1,从而通知客户端数据写入成功

对于副本,在同步大文件时,可以先取消副本,然后在同步完成后开启,从而减少segment归并时产生的消耗.

# curl -XPUT http://127.0.0.1:9200/索引_2020-04-01/_settings -d '{
    "index": { "number_of_replicas" : 0 }
}'

新增数据,节点策略

主要有3个阶段

内存阶段

新增数据到内存,同时生成translog事物日志.此时不可被搜索.

refresh阶段

每隔1秒执行一次refresh操作.将内存中的数据生成一个segmanet,存储到文件系统缓存中.此时,数据就可以搜索了.

es提供了主动刷新数据的接口:/_refresh.

如果感觉1s时间刷新太长,可以在更新完数据后,调用此接口进行数据刷新,使其可被搜索.如果在同步大量数据时,在这期间不关心是否可被搜索,提高同步效率,可以使用

# curl -XPOST http://127.0.0.1:9200/索引_2020-04-01/_settings -d'
{ "refresh_interval": "10s" }
'

根据情况降低刷新次数.

或者可以在同步历史数据时,将refresh关闭.

# curl -XPUT http://127.0.0.1:9200/索引_2020-04-01 -d'
{
  "settings" : {
    "refresh_interval": "-1"
  }
}'

等同步完成后,在手动执行refresh,使其可被搜索.

# curl -XPOST http://127.0.0.1:9200/索引_2020-04-01/_refresh

flush阶段

默认每隔30分钟,或者translog文件达到了512MB,就执行一次flush操作.把文件系统缓存中的segment文件,持久化到磁盘.完成后,同时清空translog文件.

对于时间间隔和大小,可以进行设置:

//flush时间间隔
index.translog.flush_threshold_period
//触发flush文件大小
index.translog.flush_threshold_size

数据持久化完成.

数据的维护

数据存到系统中了,就需要对数据进行维护,来解决在录入数据时产生的负作用,比如,refresh会每秒生成一个segment文件,如果要对数据检索,就要挨个检查这些文件,显然是及其浪费资源的.为了解决这个问题,使用文件归并策略.

Segment归并

在文件系统缓存中,会根据一些策略对其中的segment文件开启一些线程进行归并.归并完成后刷新到磁盘,将请求从小segment切换到归并后的segment后,删除旧的segment.

相关参数设置:

  1. index.merge.policy.floor_segment

    默认2MB,小于这个大小的segment会优先被归并.

  2. index.merge.policy.max_merge_at_once

    默认10个.一次默认归并多少个segment.

  3. index.merge.policy.max_merge_at_once_explicit

    默认30个.在forcemerge时,一次会归并多少个segment.

  4. index.merge.policy.max_merged_segment

    默认5GB.大于这个的segment,不会参与归并.forcemerge除外.

由于归并非常消耗IO和CPU,因此,默认会使用 Math.min(3,Runtime.getRuntime().availableProcessors() / 2)个线程来进行归并.CPU核数大于6时,会启动3个归并线程.

//最大归并线程数量
index.merge.scheduler.max_thread_count

数据均衡

为了保护安全,每隔30s会检查下各节点磁盘使用量.如果超过了85%,新索引分配就不会在分配到这个节点.超过了90%,就会触发该节点上现存分片的数据均衡,把数据挪到其他节点上去.

# curl -XPUT localhost:9200/_cluster/settings -d '{
    "transient" : {
        "cluster.routing.allocation.disk.watermark.low" : "85%",
        "cluster.routing.allocation.disk.watermark.high" : "10gb",
        "cluster.info.update.interval" : "1m"
    }
}'

数据的查询

query阶段

1.新请求到达任一节点,作为协调节点.

2.协调节点将请求分发到该索引的所有主分片或者分片副本(负载均衡).

3.每个分片在本地进行查询,得到id列表和排序值.

4.协调节点收到列表和排序值,合并到自己的优先队列中,进行全局分页排序.

fetch阶段

协调节点根据合并后的id列表,向相关节点发送get请求获取详细数据.

shard分配策略

索引的shard分配到哪个节点上,通常由es自动决定.以下会触发分配动作.

  1. 新索引的生成.

  2. 索引的删除.

  3. 新增副本分片.

  4. 节点增减引发的数据均衡.

ES运维

分片数据移动

因为负载过高,磁盘利用率过高,服务器下线,更换磁盘等原因,可以会需要从节点上移走部分分片数据:

curl -XPOST 127.0.0.1:9200/_cluster/reroute -d '{
  "commands" : [ {
        "move" :
            {
              "index" : "索引_2020-04-01", "shard" : 0, "from_node" : "10.19.0.81", "to_node" : "10.19.0.104"
            }
        }
  ]
}

节点下线

Elasticsearch 集群就会自动把这个 IP 上的所有分片,都自动转移到其他节点上。等到转移完成,这个空节点就可以毫无影响的下线了。

curl -XPUT 127.0.0.1:9200/_cluster/settings -d '{
  "transient" :{
      "cluster.routing.allocation.exclude._ip" : "10.0.0.1"
   }
}'

数据冷热分离方案

Elasticsearch 集群一个比较突出的问题是: 用户做一次大的查询的时候, 非常大量的读 IO 以及聚合计算导致机器 Load 升高, CPU 使用率上升, 会影响阻塞到新数据的写入, 这个过程甚至会持续几分钟。所以,可能需要仿照 MySQL 集群一样,做读写分离。

实施方案

1.N 台机器做热数据的存储, 上面只放当天的数据。这 N 台热数据节点上面的 elasticsearc.yml 中配置 node.attr.tag: hot

2.之前的数据放在另外的 M 台机器上。这 M 台冷数据节点中配置 node.attr.tag: stale

3.模板中控制对新建索引添加 hot 标签:

{
    "order" : 0,
    "template" : "*",
    "settings" : {
      "index.routing.allocation.include.tag" : "hot"
    }
}

4.每天计划任务更新旧索引的配置, 将 tag 更改为 stale, 索引会自动迁移到 M 台冷数据节点.

curl -XPUT http://127.0.0.1:9200/indexname/_settings -d'
{
   "index": {
      "routing": {
         "allocation": {
            "include": {
               "tag": "stale"
            }
         }
     }
   }
}'

这样,写操作集中在 N 台热数据节点上,大范围的读操作集中在 M 台冷数据节点上。避免了堵塞影响。

Last updated

Was this helpful?