4

ElasticSearch分片定位与内部原理

路由文档到一个分片

文档会被存储到一个主分片中。Elasticsearch是如何知道一个文档应该存放到哪个分片中呢?

实际上,这个过程是根据下面这个公式决定的:

shard = hash(routing) % number_of_primary_shards

routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。

routing 通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到 余数 。这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻求的文档所在分片的位置。

这就解释了为什么我们要在创建索引的时候就确定好主分片的数量 并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。

自定义routing

所有的文档 API( get 、 index 、 delete 、 bulk 、 update 以及 mget )都接受一个叫做 routing 的路由参数 ,通过这个参数我们可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档——例如所有属于同一个用户的文档——都被存储到同一个分片中。

PUT posts/_doc/100?routing=bigdata
{
"content":"some content"
}

分片内部原理

Lucene Index

在Lucene中单个倒排索引文件被称为Segment。

多个Segment汇总起来称为Lucene Index,即ElasticSearch中的Shard。

clipboard.png

当新文档写入时,默认定时每一秒就会生成一个Segment,查询的时候会同时查询所有的Segments,并对结果进行汇总。

Lucene当中有一个用来记录所有Segments信息的文件,Commit Point。

当有删除请求的时候,不会直接去删除数据,而是记录在.del文件中。

文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。 可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。

Refresh

将Index Buffer写入Segment的过程就叫做Refresh,默认1秒发生一次,只有Refresh后,数据才能被搜索到,这也是ElasticSearch为什么被称为近实时搜索。

clipboard.png

Refresh api

刷新所有的索引与刷新特定索引

POST /_refresh 
POST /blogs/_refresh 

并不是所有的情况都需要每秒刷新。可能你正在使用 Elasticsearch 索引大量的日志文件, 你可能想优化索引速度而不是近实时搜索, 可以通过设置 refresh_interval , 降低每个索引的刷新频率:

可以通过设置 refresh_interval , 降低索引的刷新频率。

PUT /my_logs
{
  "settings": {
    "refresh_interval": "30s" 
  }
}

关闭自动更新

PUT /my_logs/_settings
{ "refresh_interval": -1 }

Transaction Log

Segment写入磁盘的过程相对耗时,借助文件系统缓存。Refresh时,先将Segment写入缓存以开放查询

为了保证数据不会丢失,同时会写Transaction Log。

高版本ElasticSearch,Transaction Log默认落盘,每个分片有一个transaction Log。

在ElasticSearch refresh动作时,Index Buffer会清空,但是Transaction logs不会清空。

clipboard.png

flush

默认30分钟会触发一次,或者当Transaction Log满的时候(默认512M)触发

  1. 调用Refresh,Index Buffer清空
  2. 调用fsync,将缓存中的Segments写入磁盘
  3. 删除Transaction Log

clipboard.png

flush api
  • 刷新(flush) blogs 索引
    POST /blogs/_flush
  • 刷新(flush)所有的索引并且并且等待所有刷新在返回前完成。
    POST /_flush?wait_for_ongoing

Merge

由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。

Elasticsearch通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。

两个提交了的段和一个未提交的段正在被合并到一个更大的段,如图

clipboard.png

一旦合并结束,老的段被删除,如图

clipboard.png

optimize Api

optimize API大可看做是 强制合并 API 。它会将一个分片强制合并到 max_num_segments 参数指定大小的段数目。 这样做的意图是减少段的数量(通常减少到一个),来提升搜索性能。

==optimize API 不应该 被用在一个活跃的索引,一个正积极更新的索引。后台合并流程已经可以很好地完成工作。 optimizing 会阻碍这个进程。不要干扰它!==

在特定情况下,使用 optimize API 颇有益处。例如在日志这种用例下,每天、每周、每月的日志被存储在一个索引中。 老的索引实质上是只读的;它们也并不太可能会发生变化。

在这种情况下,使用optimize优化老的索引,将每一个分片合并为一个单独的段就很有用了;这样既可以节省资源,也可以使搜索更加快速.

POST /logstash-2014-10/_optimize?max_num_segments=1

总结

看了这个内部原理,感觉跟HBase的写入流程还是蛮像的。

  • IndexBuffer --> MemStore
  • Segment --> HFile
  • translog --> WAL
  • merge --> compaction

clipboard.png

附录


小鸡
214 声望24 粉丝

1.01的365次方=37.8