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。
当新文档写入时,默认定时每一秒就会生成一个Segment,查询的时候会同时查询所有的Segments,并对结果进行汇总。
Lucene当中有一个用来记录所有Segments信息的文件,Commit Point。
当有删除请求的时候,不会直接去删除数据,而是记录在.del文件中。
文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。 可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。
Refresh
将Index Buffer写入Segment的过程就叫做Refresh,默认1秒发生一次,只有Refresh后,数据才能被搜索到,这也是ElasticSearch为什么被称为近实时搜索。
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不会清空。
flush
默认30分钟会触发一次,或者当Transaction Log满的时候(默认512M)触发
- 调用Refresh,Index Buffer清空
- 调用fsync,将缓存中的Segments写入磁盘
- 删除Transaction Log
flush api
- 刷新(flush) blogs 索引
POST /blogs/_flush
- 刷新(flush)所有的索引并且并且等待所有刷新在返回前完成。
POST /_flush?wait_for_ongoing
Merge
由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。
Elasticsearch通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。
两个提交了的段和一个未提交的段正在被合并到一个更大的段,如图
一旦合并结束,老的段被删除,如图
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。