目录javascript
Lucene将index数据分为segment(段)进行存储和管理.html
Lucene中, 倒排索引一旦被建立就不可改变, 要添加或修改文档, 就须要重建整个倒排索引, 这就对一个index所能包含的数据量, 或index能够被更新的频率形成了很大的限制.java
为了在保留不变性的前提下实现倒排索引的更新, Lucene引入了一个新思路: 使用更多的索引, 也就是经过增长新的补充索引来反映最新的修改, 而不是直接重写整个倒排索引.json
—— 这样就能确保, 从最先的版本开始, 每个倒排索引都会被查询到, 查询完以后再对结果进行合并.缓存
① 将数据写入buffer(内存缓冲区);安全
② 执行commit操做: buffer空间被占满, 其中的数据将做为新的 index segment 被commit到文件系统的cache(缓存)中;bash
③ cache中的index segment经过fsync
强制flush到系统的磁盘上;服务器
④ 写入磁盘的全部segment将被记录到commit point(提交点)中, 并写入磁盘;curl
④ 新的index segment被打开, 以备外部检索使用;异步
⑤ 清空当前buffer缓冲区, 等待接收新的文档.
说明:
(a)
fsync
是一个Unix系统调用函数, 用来将内存缓冲区buffer中的数据存储到文件系统. 这里做了优化, 是指将文件缓存cache中的全部segment刷新到磁盘的操做.(b) 每一个Shard都有一个提交点(commit point), 其中保存了当前Shard成功写入磁盘的全部segment.
① 提交删除操做, 先查询要删除的文档所属的segment;
② commit point中包含一个.del
文件, 记录哪些segment中的哪些document被标记为deleted
了;
③ 当.del
文件中存储的文档足够多时, ES将执行物理删除操做, 完全清除这些文档.
在删除过程当中进行搜索操做:
依次查询全部的segment, 取得结果后, 再根据.del
文件, 过滤掉标记为deleted
的文档, 而后返回搜索结果. —— 也就是被标记为delete的文档, 依然能够被查询到.
在删除过程当中进行更新操做:
将旧文档标记为deleted
, 而后将新的文档写入新的index segment中. 执行查询请求时, 可能会匹配到旧版本的文档, 但因为.del
文件的存在, 不恰当的文档将被过滤掉.
(1) 现有流程的问题:
插入的新文档必须等待fsync
操做将segment强制写入磁盘后, 才能够提供搜索.而 fsync
操做的代价很大, 使得搜索不够实时.
(2) 改进写入流程:
① 将数据写入buffer(内存缓冲区);
② 不等buffer空间被占满, 而是每隔必定时间(默认1s), 其中的数据就做为新的index segment被commit到文件系统的cache(缓存)中;
③ index segment 一旦被写入cache(缓存), 就当即打开该segment供搜索使用;
④ 清空当前buffer缓冲区, 等待接收新的文档.
—— 这里移除了fsync
操做, 便于后续流程的优化.
优化的地方: 过程②和过程③:
segment进入操做系统的缓存中就能够提供搜索, 这个写入和打开新segment的轻量过程被称为
refresh
.
Elasticsearch中, 每一个Shard每秒都会自动refresh一次, 因此ES是近实时的, 数据插入到能够被搜索的间隔默认是1秒.
(1) 手动refresh —— 测试时使用, 正式生产中请减小使用:
# 刷新全部索引: POST _refresh # 刷新某一个索引: POST employee/_refresh
(2) 手动设置refresh间隔 —— 若要优化索引速度, 而不注重实时性, 能够下降刷新频率:
# 建立索引时设置, 间隔1分钟: PUT employee { "settings": { "refresh_interval": "1m" } } # 在已有索引中设置, 间隔10秒: PUT employee/_settings { "refresh_interval": "10s" }
(3) 当你在生产环境中创建一个大的新索引时, 能够先关闭自动刷新, 要开始使用该索引时再改回来:
# 关闭自动刷新: PUT employee/_settings { "refresh_interval": -1 } # 开启每秒刷新: PUT employee/_settings { "refresh_interval": "1s" }
Elasticsearch经过事务日志(translog
)来防止数据的丢失 —— durability持久化.
① 索引数据在写入内存buffer(缓冲区)的同时, 也写入到translog日志文件中;
② 每隔refresh_interval
的时间就执行一次refresh:
(a) 将buffer中的数据做为新的 index segment, 刷到文件系统的cache(缓存)中;
(b) index segment一旦被写入文件cache(缓存), 就当即打开该segment供搜索使用;
③ 清空当前内存buffer(缓冲区), 等待接收新的文档;
④ 重复①~③, translog文件中的数据不断增长;
⑤ 每隔必定时间(默认30分钟), 或者当translog文件达到必定大小时, 发生flush操做, 并执行一次全量提交:
(a) 将此时内存buffer(缓冲区)中的全部数据写入一个新的segment, 并commit到文件系统的cache中;
(b) 打开这个新的segment, 供搜索使用;
(c) 清空当前的内存buffer(缓冲区);
(d) 将translog文件中的全部segment经过
fsync
强制刷到磁盘上;(e) 将这次写入磁盘的全部segment记录到commit point中, 并写入磁盘;
(f) 删除当前translog, 建立新的translog接收下一波建立请求.
扩展: translog也能够被用来提供实时CRUD.
当经过id查询、更新、删除一个文档时, 从segment中检索以前, 先检查translog中的最新变化 —— ES老是可以实时地获取到文档的最新版本.
共计:3599 个字
(1) 关于translog的配置:
flush操做 = 将translog中的记录刷到磁盘上 + 更新commit point信息 + 清空translog文件.
Elasticsearch默认: 每隔30分钟就flush一次;
或者: 当translog文件的大小达到上限(默认为512MB)时主动触发flush.
相关配置为:
# 发生多少次操做(累计多少条数据)后进行一次flush, 默认是unlimited: index.translog.flush_threshold_ops # 当translog的大小达到此预设值时, 执行一次flush操做, 默认是512MB: index.translog.flush_threshold_size # 每隔多长时间执行一次flush操做, 默认是30min: index.translog.flush_threshold_period # 检查translog、并执行一次flush操做的间隔. 默认是5s: ES会在5-10s之间进行一次操做: index.translog.interval
(2) 数据的故障恢复:
① 增删改操做成功的标志: segment被成功刷新到Primary Shard和其对应的Replica Shard的磁盘上, 对应的操做才算成功.
② translog文件中存储了上一次flush(即上一个commit point)到当前时间的全部数据的变动记录. —— 即translog中存储的是尚未被刷到磁盘的全部最新变动记录.
③ ES发生故障, 或重启ES时, 将根据磁盘中的commit point去加载已经写入磁盘的segment, 并重作translog文件中的全部操做, 从而保证数据的一致性.
(3) 异步刷新translog:
为了保证不丢失数据, 就要保护translog文件的安全:
Elasticsearch 2.0以后, 每次写请求(如index、delete、update、bulk等)完成时, 都会触发
fsync
将translog中的segment刷到磁盘, 而后才会返回200 OK
的响应;或者: 默认每隔5s就将translog中的数据经过
fsync
强制刷新到磁盘.
—— 提升数据安全性的同时, 下降了一点性能.
==> 频繁地执行fsync
操做, 可能会产生阻塞致使部分操做耗时较久. 若是容许部分数据丢失, 可设置异步刷新translog来提升效率.
PUT employee/_settings { "index.translog.durability": "async", "index.translog.sync_interval": "5s" }
由上述近实时性搜索的描述, 可知ES默认每秒都会产生一个新的segment文件, 而每次搜索时都要遍历全部的segment, 这很是影响搜索性能.
为解决这一问题, ES会对这些零散的segment进行merge(归并)操做, 尽可能让索引中只保有少许的、体积较大的segment文件.
这个过程由独立的merge线程负责, 不会影响新segment的产生.
同时, 在merge段文件(segment)的过程当中, 被标记为deleted的document也会被完全物理删除.
① 选择一些有类似大小的segment, merge成一个大的segment;
② 将新的segment刷新到磁盘上;
③ 更新commit文件: 写一个新的commit point, 包括了新的segment, 并删除旧的segment;
④ 打开新的segment, 完成搜索请求的转移;
⑤ 删除旧的小segment.
segment的归并是一个很是消耗系统CPU和磁盘IO资源的任务, 因此ES对归并线程提供了限速机制, 确保这个任务不会过度影响到其余任务.
(1) 归并线程的速度限制:
限速配置 indices.store.throttle.max_bytes_per_sec
的默认值是20MB, 这对写入量较大、磁盘转速较高的服务器来讲明显太低.
对ELK Stack应用, 建议将其调大到100MB或更高. 能够经过API设置, 也能够写在配置文件中:
PUT _cluster/settings { "persistent" : { "indices.store.throttle.max_bytes_per_sec" : "100mb" } } // 响应结果以下: { "acknowledged": true, "persistent": { "indices": { "store": { "throttle": { "max_bytes_per_sec": "100mb" } } } }, "transient": {} }
(2) 归并线程的数目:
推荐设置为CPU核心数的一半, 若是磁盘性能较差, 能够适当下降配置, 避免发生磁盘IO堵塞:
PUT employee/_settings { "index.merge.scheduler.max_thread_count" : 8 }
(3) 其余策略:
# 优先归并小于此值的segment, 默认是2MB: index.merge.policy.floor_segment # 一次最多归并多少个segment, 默认是10个: index.merge.policy.max_merge_at_once # 一次直接归并多少个segment, 默认是30个 index.merge.policy.max_merge_at_once_explicit # 大于此值的segment不参与归并, 默认是5GB. optimize操做不受影响 index.merge.policy.max_merged_segment
segment的默认大小是5GB, 在很是庞大的索引中, 仍然会存在不少segment, 这对文件句柄、内存等资源都是很大的浪费.
但因为归并任务很是消耗资源, 因此通常不会选择加大 index.merge.policy.max_merged_segment
配置, 而是在负载较低的时间段, 经过optimize
接口强制归并segment:
# 强制将segment归并为1个大的segment: POST employee/_optimize?max_num_segments=1 # 在终端中的操做方法: curl -XPOST http://ip:5601/employee/_optimize?max_num_segments=1
optimize线程不会受到任何资源上的限制, 因此不建议对还在写入数据的热索引(动态索引)执行这个操做.
实战建议: 对一些不多发生变化的老索引, 如日志信息, 能够将每一个Shard下的segment合并为一个单独的segment, 节约资源, 还能提升搜索效率.
参考资料
https://www.elastic.co/guide/cn/elasticsearch/guide/current/making-text-searchable.html
https://www.elastic.co/guide/cn/elasticsearch/guide/current/near-real-time.html
https://www.elastic.co/guide/cn/elasticsearch/guide/current/translog.html
https://www.elastic.co/guide/cn/elasticsearch/guide/current/merge-process.html
版权声明
做者: 马瘦风
出处: 博客园 马瘦风的博客
感谢阅读, 若是文章有帮助或启发到你, 点个[好文要顶👆] 或 [推荐👍] 吧😜
本文版权归博主全部, 欢迎转载, 但[必须在文章页面明显位置给出原文连接], 不然博主保留追究相关人员法律责任的权利.