热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

《Elasticsearch源码解析与优化实战》第18章:写入速度优化

ES写入速度

  • 文章目录

  • 一、 简介

  • 二、优化项

  • 2.1、Translog flush

  • 2.2、Refresh_interval

  • 2.3、Segment merge

  • 2.4、Indexing Buffer

  • 2.5、Bulk 线程池和队列大小

  • 2.6、磁盘间的任务均衡

  • 2.7、节点间的任务均衡

  • 2.8、索引过程调整和优化

    • 2.8.1、自动生成 doc ID

    • 2.8.2、调整字段 Mappings

      • 2.8.2.1、字段的 index 属性设置为:not_analyzed 或者 no

      • 2.8.2.2、使用不同的分析器:analyzer

    • 2.8.3、调整 _source 字段

    • 2.8.4、禁用 _all 字段

    • 2.8.5、对于 Analyzed 的字段禁用 Norms

    • 2.8.6、index_options 设置

  • 三、参考配置

  • 四、思考与总结


前言:由于工作原因,最近集中项目是酒店的搜索推荐, 搜索数据存储在ES,基于不同场景,对ES的深入探索, 开始接触ES的源码,本文是 《Elasticsearch 源码解析与优化实战》 的一个探索笔记。有不正确地方,请指正。 

一、 简介

基于版本: 2.x – 5.x,后期会更新其他面版本。

在 Es 的默认设置,是综合考虑数据可靠性搜索实时性写入速度等因素的,当你离开默认设置,追求极致的写入速度时,很多是以牺牲可靠性和搜索实时性为代价的。有时候,业务上对两者要求并不高,反而对写入速度要求很高。

但实际在我们的搜索业务中,因为是面向C端,所以更加追求搜索的速度,当然不通场景需求不一样,此处只是介绍写入速度的优化

二、优化项

2.1、Translog flush

从 es 2.x 开始,默认设置下,translog 的持久化策略为:每个请求都flush
,这是影响 es写入速度的最大因素,但是只有这样,写操作才有可能是可靠的。

对应配置项为:index.translog.durability: request

如果系统可以接受一定几率的数据丢失,调整translog
持久化策略为周期性和一定大小的时候 flush

index.translog.durability: async
index.translog.sync_interval: 120s
index.translog.flush_threshold_size: 1024mb
index.translog.flush_threshold_period: 120m

2.2、Refresh_interval

默认情况下索引的refresh_interval
为1秒,这意味着数据写1秒后就可以被搜索到,每次索引的 refresh
会产生一个新的 lucene
段,这会导致频繁的 segment merge
行为,如果你不需要这么高的搜索实时性,应该降低索引refresh
周期
。如:

index.refresh_interval: 120s

2.3、Segment merge

segment merge
操作对系统 CPU
IO
占用都比较高,从 es 2.0 开始,merge
行为不再由 ES
控制,而是转由 lucene
控制,因此以下配置已被删除:

indices.store.throttle.type
indices.store.throttle.max_bytes_per_sec
index.store.throttle.type
index.store.throttle.max_bytes_per_sec

改为以下调整开关:
index.merge.scheduler.max_thread_count
index.merge.policy.*

最大线程数的默认值为:

Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() 2))

是一个比较理想的值,如果你只有一块硬盘并且非 SSD,应该把他设置为1,因为在旋转存储介质上并发写,由于寻址的原因,不会提升,只会降低写入速度。

merge 策略有三种:

  • tiered
  • log_byete_size
  • log_doc

默认情况下:index.merge.polcy.type: tiered

索引创建时合并策略就已确定,不能更改,但是可以动态更新策略参数,一般情况下,不需要调整。如果堆栈经常有很多merge,可以尝试调整以下配置:

  • index.merge.policy.floor_segment
    : 该属性用于阻止 segment  的频繁 flush,小于此值将考虑优先合并,默认为2M,可考虑适当降低此值。

  • index.merge.policy.segments_per_tier
    :该属性指定了每层分段的数量,取值越小最终 segment
    越少,因此需要 merge
    的操作更多,可以考虑适当增加此值。默认为10,他应该大于等于index.merge.policy.max_merge_at_once

  • index.merge.policy.max_merged_segment
    : 指定了单个segment
    的最大容量,默认为5GB,可以考虑适当降低此值。

2.4、Indexing Buffer

indexing buffer
在为 doc
建立索引时使用,当缓冲满时会刷入磁盘,生成一个新的 segment
, 这是除refresh_interval
外另外一个刷新索引,生成新 segment
的机会。每个 shard 有自己的 indexing buffer
,下面的关于这个 buffer
大小的配置需要除以这个节点上所有的 shard
数量。

indices.memory.index_buffer_size 默认为整个堆空间的10%
indices.memory.min_index_buffer_size 默认48mb
indices.memory.max_index_buffer_size 默认无限制

在大量的索引操作时,indices.memory.index_buffer_size
默认设置可能不够,这和可用堆内存,单节点上的 shard
数量相关,可以考虑适当增大。

2.5、Bulk 线程池和队列大小

建立索引的过程偏计算密集型任务,应该使用固定大小的线程池配置,来不及处理的放入队列,线程数量配置为 CPU 核心数+1,避免过多的上下文切换。队列大小可以适当增加。

2.6、磁盘间的任务均衡

如果你的部署方案是为 path.data 配置多个路径来使用多块磁盘,es 在分配 shard 时,落到各磁盘上的 shard 可能并不均匀,这种不均匀可能会导致某些磁盘繁忙,利用率达到100%,这种不均匀达到一定程度可能会对写入性能产生负面影响。

Es 在处理多路径时,优先将 shard 分配到可用空间百分比最多的磁盘,因此短时间内创建的 shard 可能被集中分配到这个磁盘,即使可用空间是99%和98%的差别。后来 Es 在2.x 版本中开始解决这个问题的方式是:预估一下这个 shard 会使用的空间,从磁盘可用空间中减去这部分,直到现在6.x beta 版也是这种处理方式。但是实现也存在一些问题:这种机制只存在于一次索引创建的过程中,下一次的索引创建,磁盘可用空间并不是上次做完减法以后的结果,这也可以理解,毕竟预估是不准的,一直减下去很快就减没了。

但是最终的效果是,这种机制并没有从根本上解决问题,即使没有完美的解决方案,这种机制的效果也不够好。如果单一的机制不能解决所有的场景,至少应该为不同场景准备多种选择。为此,我们为 es 增加了两种策略:

  • 简单轮询: 系统初始阶段,简单轮询的效果是最均匀的
  • 基于可用空间的动态加权轮询: 以可用空间作为权重,在磁盘之间加权轮询

2.7、节点间的任务均衡

为了在节点间任务尽量均衡,数据写入客户端应该把 bulk 请求轮询发送到各个节点。

当使用 java api
或者 rest api
bulk
接口发送数据时,客户端将会轮询的发送到集群节点,节点列表取决于:当client.transport.sniff
为 true(默认为 false),列表为所有数据节点。否则,列表为初始化客户端对象时添加进去的节点。

java api
TransportClient
rest api
RestClient
都是线程安全的,当写入程序自己创建线程池控制并发,应该使用同一个 Client
对象。在此建议使用 rest api
,兼容性好,只有吞吐量非常大才值得考虑序列化的开销,显然搜索并不是高吞吐量的业务。

观察bulk
请求在不同节点上的处理情况,通过cat
接口观察 bulk
线程池和队列情况,是否存在不均:

GET _cat/thread_pool

2.8、索引过程调整和优化

2.8.1、自动生成 doc ID

分析 ES 写入流程可以看到,写入 doc 时如果是外部指定了 id,ES 会先尝试读取原来doc的版本号, 判断是否需要更新,使用自动生成 doc id
可以避免这个环节。

2.8.2、调整字段 Mappings

2.8.2.1、字段的 index 属性设置为:not_analyzed 或者 no

对字段不分词或者不索引,可以节省很多运算,降低 CPU 占用。 尤其是 binary 类型,默认情况下占用 CPU 非常高,而这种类型根本不需要进行分词做索引。

单个 doc 在建立索引时的运算复杂度,最大的因素不在于 doc 的字节数或者说某个字段 value 的长度,而是字段的数量。 例如在满负载的写入压力测试中,mapping 相同的情况下,一个有10个字段,200字节的 doc, 通过增加某些字段 value 的长度到500字节,写入 ES 时速度下降很少,而如果字段数增加到20,即使整个 doc 字节数没增加多少,写入速度也会降低一倍。

2.8.2.2、使用不同的分析器:analyzer

不同的分析器在索引过程中运算复杂度也有较大的差异

2.8.3、调整 _source 字段

_source
字段用于存储 doc 原始数据,对于部分不需要存储的字段,可以通过 includes、excludes
来过滤,或者将_source
禁用,一般用于索引和数据分离。

这样可以降低 io 的压力,不过实际场景大多数情况不会禁用 _source
,而即使过滤掉某些字段,对于写入速度的提示效果也不大,满负荷写入情况下,基本是 CPU 先跑满了,瓶颈在于 CPU。

2.8.4、禁用 _all  字段

_all
字段默认是开启的,其中包含所有字段分词后的关键词,作用是可以在搜索的时候不指定特定字段,从所有字段中检索。如果你不需要这个特性,可以禁用 _all
,可以小幅的降低CPU 压力,对速度影响并不明显。

从 ES 6.0 开始该字段被禁用

2.8.5、对于 Analyzed 的字段禁用 Norms

Norms 用于在搜索时计算 doc 的评分,如果不需要评分,可以禁用他:

"title": {"type": "string","norms": {"enabled": false}}

对于 text
类型的字段而言,默认开启了norms
,而 keyword
类型的字段则默认关闭了norms

开启norms之后,每篇文档的每个字段需要一个字节存储norms。对于 text 类型的字段而言是默认开启norms的,因此对于不需要评分的 text 类型的字段,可以禁用norms,这算是一个调优点吧。

2.8.6、index_options 设置

index_options
用于控制在建立倒排索引过程中,哪些内容会被添加到倒排,例如 doc数量、词频、positions、offsets等信息,优化这些设置可以一定程度降低索引过程中运算任务,节省 CPU 占用率。 不过实际场景中,通常很难确定业务将来会不会用到这些信息,除非一开始方案就明确这样设计的

index_options:索引选项控制添加到倒排索引(Inverted Index)的信息,这些信息用于搜索(Search)和高亮显示:

  • docs:只索引文档编号(Doc Number)
  • freqs:索引文档编号和词频率(term frequency)
  • positions:索引文档编号,词频率和词位置(序号)
  • offsets:索引文档编号,词频率,词偏移量(开始和结束位置)和词位置(序号)

默认情况下,被分析的字符串(analyzed string)字段使用positions,其他字段使用docs。

三、参考配置

下面是笔者的线上环境使用的全局模板和配置文件的部分内容,省略掉了节点名称、节点列表等基础配置字段,仅列出与本文相关内容。

从ES 5.x开始,索引级设置需要写在模板中,或者在创建索引时指定,我们把各个索引通用的配置写到了模板中,这个模板匹配全部的索引,并且具有最低的优先级,让用户定义的模板有更高的优先级,以覆盖这个模板中的配置。

{
"template": "*",
"order" : 0,
"settings": {
"index.merge.policy.max_merged_segment" : "2gb",
"index.merge.policy.segments per_tier" : "24",
"index.number_of_replicas" : "1",
"index.number_of_shards" : "24",
"index.optimize_auto_generated_id" : "true",
"index.refresh_interval" : "120s",
"index.translog.durability" : "async",
"index.translog.flush_threshold_size" : "1000mb",
"index.translog. sync_ interval" : "120s",
"index.unassigned.node_left.delayed_timeout" : "5d"
}
}

elasticsearch.yml中的配置:

indices.memory.index_buffer_size: 30%

四、思考与总结

(1) 方法比结论重要。一个系统性问题往往是多种因素造成的,在处理集群的写入性能问题上,先将问题分解,在单台上进行压测,观察哪种系统资源达到极限,例如,CPU或磁盘利用率、I/O block、线程切换、堆栈状态等。然后分析并调整参数,优化单台上的能力,先解决局部问题,在此基础上解决整体问题会容易得多。

(2) 可以使用更好的CPU,或者使用SSD,对写入性能提升明显。在我们的测试中,在相同条件下,E5 2650V4 比 E5 2430v2 的写入速度高60%左右。

(3) 在我们的压测环境中,写入速度稳定在平均单机每秒3万条以上,使用的测试数据:每个文档的字段数量为10个左右,文档大小约100字节,CPU使用 E5 2430 v2。




推荐阅读
  • 在开发中,有时候一个业务上要求的原子操作不仅仅包括数据库,还可能涉及外部接口或者消息队列。此时,传统的数据库事务无法满足需求。本文介绍了Java中如何利用java.lang.Runtime.addShutdownHook方法来保证业务线程的完整性。通过添加钩子,在程序退出时触发钩子,可以执行一些操作,如循环检查某个线程的状态,直到业务线程正常退出,再结束钩子程序。例子程序展示了如何利用钩子来保证业务线程的完整性。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • NotSupportedException无法将类型“System.DateTime”强制转换为类型“System.Object”
    本文介绍了在使用LINQ to Entities时出现的NotSupportedException异常,该异常是由于无法将类型“System.DateTime”强制转换为类型“System.Object”所导致的。同时还介绍了相关的错误信息和解决方法。 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • php缓存ri,浅析ThinkPHP缓存之快速缓存(F方法)和动态缓存(S方法)(日常整理)
    thinkPHP的F方法只能用于缓存简单数据类型,不支持有效期和缓存对象。S()缓存方法支持有效期,又称动态缓存方法。本文是小编日常整理有关thinkp ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
  • 开发笔记:MyBatis学习之逆向工程
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了MyBatis学习之逆向工程相关的知识,希望对你有一定的参考价值。转载:http://w ... [详细]
author-avatar
手机用户2602909197
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有