熔断机制
1.问题
- ES 内存占用过高且持续不会缓解
- 节点频繁 GC 且耗时较长
- 查询响应时间过长甚至直接失败
- 修改相关功能或性能配置直接导致集群出现 OOM 等异常情况
2.JVM
由于ES运行在JVM容器中,需要合理配置JVM参数以达到更好的内存使用
- 内存大小
Xms 和 Xmx 设置为相同的值,且不要超过系统内存的50%。一般建议配置在 JVM 指针压缩技术最大值范围之内,可以通过日志中的compressed ordinary object pointers [true]来判断是否开启了指针压缩;如果有特殊需求(例如查询需要占用大量内存的情况),可以将内存调大,但是需要配置 40GB 乃至 50GB 以上的内存才能追平指针压缩技术带来的性能优化,基本不建议这样使用(对 CPU 和 GC 等都有性能损耗)。
- 垃圾回收器
使用 G1 相比 CMS 更适合大内存使用场景,一般默认参数即可:
G1ReservePercent:老年代预留给新生代对象晋升的分配担保比例。如果新生代经常晋升失败而导致 Full GC,可以适当调高此阈值,意味着降低了老年代的实际可用空间,使用 ES 默认配置即可。
InitiatingHeapOccupancyPercent:触发老年代全局并发标记的比例,使用 ES 默认配置即可。
MaxGCPauseMillis:GC 最大的停顿毫秒数。如果业务对 GC 比较敏感,可以适当调小,但是会增加 CPU 的开销,建议 50-200 之间。
2.2 优化手段
以下为常见 数据写入的调优手段,写入调优均以提升写入吞吐量和并发能力为目标,而非提升写入实时性。
2.2.1 增加 flush 时间间隔,
目的是减小数据写入磁盘的频率,减小磁盘IO频率。
2.2.2 增加refresh_interval
的参数值
目的是减少segment文件的创建,减少segment的merge次数,merge是发生在jvm中的,有可能导致full GC,增加refresh会降低搜索的实时性。
ES的 refresh 行为非常昂贵,并且在正在进行的索引活动时经常调用,会降低索引速度,这一点在索引写入原理中介绍过,了解索引的写入原理,可以关注我的博客Elastic开源社区。
默认情况下,Elasticsearch 每秒定期刷新索引,但仅在最近 30 秒内收到一个或多个搜索请求的索引上。
如果没有搜索流量或搜索流量很少(例如每 5 分钟不到一个搜索请求)并且想要优化索引速度,这是最佳配置。此行为旨在在不执行搜索的默认情况下自动优化批量索引。建议显式配置此配置项,如 30秒。
2.2.3 增加Buffer大小,
本质也是减小refresh的时间间隔,因为导致segment文件创建的原因不仅有时间阈值,还有buffer空间大小,写满了也会创建。 默认最小值 48MB< 默认值 JVM 空间的10% < 默认最大无限制
2.2.4 关闭副本
当需要单次写入大量数据的时候,建议关闭副本,暂停搜索服务,或选择在检索请求量谷值区间时间段来完成。
- 第一是减小读写之间的资源抢占,读写分离
- 第二,当检索请求数量很少的时候,可以减少甚至完全删除副本分片,关闭segment的自动创建以达到高效利用内存的目的,因为副本的存在会导致主从之间频繁的进行数据同步,大大增加服务器的资源占用。
具体可通过则设置index.number_of_replicas 为0以加快索引速度。没有副本意味着丢失单个节点可能会导致数据丢失,因此数据保存在其他地方很重要,以便在出现问题时可以重试初始加载。初始加载完成后,可以设置index.number_of_replicas改回其原始值。
2.2.5 禁用swap
大多数操作系统尝试将尽可能多的内存用于文件系统缓存,并急切地换掉未使用的应用程序内存。这可能导致部分 JVM 堆甚至其可执行页面被换出到磁盘。
交换对性能和节点稳定性非常不利,应该不惜一切代价避免。它可能导致垃圾收集持续几分钟而不是几毫秒,并且可能导致节点响应缓慢甚至与集群断开连接。在Elastic分布式系统中,让操作系统杀死节点更有效。
2.2.6 使用多个工作线程
发送批量请求的单个线程不太可能最大化 Elasticsearch 集群的索引容量。为了使用集群的所有资源,应该从多个线程或进程发送数据。除了更好地利用集群的资源外,还有助于降低每个 fsync 的成本。
确保注意TOO_MANY_REQUESTS (429)响应代码(EsRejectedExecutionException使用 Java 客户端),这是 Elasticsearch 告诉我们它无法跟上当前索引速度的方式。发生这种情况时,应该在重试之前暂停索引,最好使用随机指数退避。
与调整批量请求的大小类似,只有测试才能确定最佳工作线程数量是多少。这可以通过逐渐增加线程数量来测试,直到集群上的 I/O 或 CPU 饱和。
2.2.7 避免使用稀疏数据
2.2.8 max_result_window
参数
max_result_window是分页返回的最大数值,默认值为10000。max_result_window本身是对JVM的一种保护机制,通过设定一个合理的阈值,避免初学者分页查询时由于单页数据过大而导致OOM。
在很多业务场景中经常需要查询10000条以后的数据,当遇到不能查询10000条以后的数据的问题之后,网上的很多答案会告诉你可以通过放开这个参数的限制,将其配置为100万,甚至1000万就行。但是如果仅仅放开这个参数就行,那么这个参数限制的意义有何在呢?如果你不知道这个参数的意义,很可能导致的后果就是频繁的发生OOM而且很难找到原因,设置一个合理的大小是需要通过你的各项指标参数来衡量确定的,比如你用户量、数据量、物理内存的大小、分片的数量等等。通过监控数据和分析各项指标从而确定一个最佳值,并非越大越好
3、查询调优
3.1 读写性能不可兼得
首先要明确一点:鱼和熊掌不可兼得。读写性能调优在很多场景下是只能二选一的。牺牲 A 换 B 的行为非常常见。索引本质上也是通过空间换取时间。写生写入实时性就是为了提高检索的性能。
当你在二手平台或者某垂直信息网站发布信息之后,是允许有信息写入的延时性的。但是检索不行,甚至 1 秒的等待时间对用户来说都是无法接受的。满足用户的要求甚至必须做到10 ms以内。
3.2 优化手段
3.2.1 避免单次召回大量数据
搜索引擎最擅长的事情是从海量数据中查询少量相关文档,而非单次检索大量文档。非常不建议动辄查询上万数据。如果有这样的需求,建议使用滚动查询
3.2.2 避免单个文档过大
鉴于默认http.max_content_length设置为 100MB,Elasticsearch 将拒绝索引任何大于该值的文档。您可能决定增加该特定设置,但 Lucene 仍然有大约 2GB 的限制。
即使不考虑硬性限制,大型文档通常也不实用。大型文档对网络、内存使用和磁盘造成了更大的压力,即使对于不请求的搜索请求也是如此,_source因为 Elasticsearch_id在所有情况下都需要获取文档的文件系统缓存有效。对该文档进行索引可能会占用文档原始大小的倍数的内存量。Proximity Search(例如短语查询)和高亮查询也变得更加昂贵,因为它们的成本直接取决于原始文档的大小。
有时重新考虑信息单元应该是什么是有用的。例如,您想让书籍可搜索的事实并不一定意味着文档应该包含整本书。使用章节甚至段落作为文档可能是一个更好的主意,然后在这些文档中拥有一个属性来标识它们属于哪本书。这不仅避免了大文档的问题,还使搜索体验更好。例如,如果用户搜索两个单词fooand bar,则不同章节之间的匹配可能很差,而同一段落中的匹配可能很好。
3.2.3 单次查询10条文档 好于 10次查询每次一条
批量请求将产生比单文档索引请求更好的性能。但是每次查询多少文档最佳,不同的集群最佳值可能不同,为了获得批量请求的最佳阈值,建议在具有单个分片的单个节点上运行基准测试。首先尝试一次索引 100 个文档,然后是 200 个,然后是 400 个等。在每次基准测试运行中,批量请求中的文档数量翻倍。当索引速度开始趋于平稳时,就可以获得已达到数据批量请求的最佳大小。在相同性能的情况下,当大量请求同时发送时,太大的批量请求可能会使集群承受内存压力,因此建议避免每个请求超过几十兆字节。
3.2.4 数据建模
很多人会忽略对 Elasticsearch 数据建模的重要性。
nested属于object类型的一种,是Elasticsearch中用于复杂类型对象数组的索引操作。Elasticsearch没有内部对象的概念,因此,ES在存储复杂类型的时候会把对象的复杂层次结果扁平化为一个键值对列表。
特别是,应避免连接。Nested 可以使查询慢几倍,Join 会使查询慢数百倍。两种类型的使用场景应该是:Nested针对字段值为非基本数据类型的时候,而Join则用于 当子文档数量级非常大的时候。
关于数据建模,在我的博客中有详细的讲解,此处不再赘述
3.2.5 给系统留足够的内存
Lucene的数据的fsync是发生在OS cache的,要给OS cache预留足够的内从大小,详见JVM调优。
3.2.6 预索引
利用查询中的模式来优化数据的索引方式。例如,如果所有文档都有一个price
字段,并且大多数查询 range 在固定的范围列表上运行聚合,可以通过将范围预先索引到索引中并使用聚合来加快聚合速度。
3.2.7 使用filter代替query
query和filter的主要区别在: filter是结果导向的而query是过程导向。query倾向于“当前文档和查询的语句的相关度”而filter倾向于“当前文档和查询的条件是不是相符”。即在查询过程中,query是要对查询的每个结果计算相关性得分的,而filter不会。另外filter有相应的缓存机制,可以提高查询效率。
3.2.8 避免深度分页
避免单页数据过大,可以参考百度或者淘宝的做法。es提供两种解决方案 scroll search 和 search after。关于深度分页的详细原理,推荐阅读:详解Elasticsearch深度分页问题
3.2.9 使用 Keyword 类型
并非所有数值数据都应映射为数值字段数据类型。Elasticsearch为 查询优化数字字段,例如integer
or long。如果不需要范围查找,对于 term查询而言,keyword 比 integer 性能更好。
3.2.10 避免使用脚本
Scripting是Elasticsearch支持的一种专门用于复杂场景下支持自定义编程的强大的脚本功能。相对于 DSL 而言,脚本的性能更差,DSL能解决 80% 以上的查询需求,如非必须,尽量避免使用 Script
5、硬件优化
5.1 简述
es的默认配置是一个非常合理的默认配置,绝大多数情况下是不需要修改的,如果不理解某项配置的含义,没有经过验证就贸然修改默认配置,可能造成严重的后果。比如max_result_window这个设置,默认值是1W,这个设置是分页数据每页最大返回的数据量,冒然修改为较大值会导致OOM。ES没有银弹,不可能通过修改某个配置从而大幅提升ES的性能,通常出厂配置里大部分设置已经是最优配置,只有少数和具体的业务相关的设置,事先无法给出最好的默认配置,这些可能是需要我们手动去设置的。关于配置文件,如果你做不到彻底明白配置的含义,不要随意修改。
jvm heap分配:7.x 版本默认1GB,这个值太小,很容易导致OOM。Jvm heap大小不要超过物理内存的50%,最大也不要超过32GB(compressed oop),它可用于其内部缓存的内存就越多,但可供操作系统用于文件系统缓存的内存就越少,heap过大会导致GC时间过长
5.2 节点
- 相同角色的节点,避免使用差异较大的服务器配置,
- 避免使用“超大杯”服务器(SS:Super Server),比如128核CPU,1 T的内存,2T的固态硬盘。这样可能会产生较大的资源浪费。
- 等量的配置,使用较少的物理机好于使用较多的虚拟机。比如一个一个五台4核16G的物理机,好于10甚至11台2核8G的虚拟机,这里不仅仅是虚拟机本身可能也会消耗一部分性能的问题,也涉及数据安全的问题。
- 避免在同一台服务器上部署多个节点,会增加集群管理的难度。
5.3 分片
5.3.1 分片创建策略
分片产生的目的是为了实现分布式,而分布式的好处之一就是实现“高可用性”(还包括高性能如提高吞吐量等会在后面内容展开讲),分片的分配策略极大程度上都是围绕如何提高可用性而来的,如分片分配感知、强制感知等。
互联网开发没有“银弹”,分片的数量分配也没有适用于所有场景的最佳值,创建分片策略的最佳方法是使用您在生产中看到的相同查询和索引负载在生产硬件上对生产数据进行基准测试。分片的分配策略主要从两个指标来衡量:即数量和单个分片的大小。
5.3.2 分片分配策略
- ES使用数据分片(shard)来提高服务的可用性,将数据分散保存在不同的节点上以降低当单个节点发生故障时对数据完整性的影响,同时使用副本(repiica)来保证数据的完整性。关于分片的默认分配策略,在7.x之前,默认5个primary shard,每个primary shard默认分配一个replica,即5主1副,而7.x之后,默认1主1副
- ES在分配单个索引的分片时会将每个分片尽可能分配到更多的节点上。但是,实际情况取决于集群拥有的分片和索引的数量以及它们的大小,不一定总是能均匀地分布。
- Paimary只能在索引创建时配置数量,而replica可以在任何时间分配,并且primary支持读和写操作,而replica只支持客户端的读取操作,数据由es自动管理,从primary同步。
- ES不允许Primary和它的Replica放在同一个节点中,并且同一个节点不接受完全相同的两个Replica
- 同一个节点允许多个索引的分片同时存在
5.3.3 分片的数量
- 避免分片过多:大多数搜索会命中多个分片。每个分片在单个 CPU 线程上运行搜索。虽然分片可以运行多个并发搜索,但跨大量分片的搜索会耗尽节点的搜索线程池。这会导致低吞吐量和缓慢的搜索速度。
- 分片越少越好:每个分片都使用内存和 CPU 资源。在大多数情况下,一小组大分片比许多小分片使用更少的资源。
5.3.4 分片的大小决策
- 分片的合理容量:10GB-50GB。虽然不是硬性限制,但 10GB 到 50GB 之间的分片往往效果很好。根据网络和用例,也许可以使用更大的分片。在索引的生命周期管理中,一般设置50GB为单个索引的最大阈值。
- 堆内存容量和分片数量的关联:小于20分片/每GB堆内存,一个节点可以容纳的分片数量与节点的堆内存成正比。例如,一个拥有 30GB 堆内存的节点最多应该有 600 个分片。如果节点超过每 GB 20 个分片,考虑添加另一个节点。
5.4 内存
根据业务量不同,内存的需求也不同,一般生产建议不要少于16G。ES是比较依赖内存的,并且对内存的消耗也很大,内存对ES的重要性甚至是高于CPU的,所以即使是数据量不大的业务,为了保证服务的稳定性,在满足业务需求的前提下,我们仍需考虑留有不少于20%的冗余性能。一般来说,按照百万级、千万级、亿级数据的索引,我们为每个节点分配的内存为16G/32G/64G就足够了,太大的内存,性价比就不是那么高了。
5.5 磁盘
对于ES来说,磁盘可能是最重要的了,因为数据都是存储在磁盘上的,当然这里说的磁盘指的是磁盘的性能。磁盘性能往往是硬件性能的瓶颈,木桶效应中的最短板。ES应用可能要面临不间断的大量的数据读取和写入。生产环境可以考虑把节点冷热分离,“热节点”使用SSD做存储,可以大幅提高系统性能;冷数据存储在机械硬盘中,降低成本。另外,关于磁盘阵列,可以使用raid 0。
5.6 CPU
CPU对计算机而言可谓是最重要的硬件,但对于ES来说,可能不是他最依赖的配置,因为提升CPU配置可能不会像提升磁盘或者内存配置带来的性能收益更直接、显著。当然也不是说CPU的性能就不重要,只不过是说,在硬件成本预算一定的前提下,应该把更多的预算花在磁盘以及内存上面。通常来说单节点cpu 4核起步,不同角色的节点对CPU的要求也不同。服务器的CPU不需要太高的单核性能,更多的核心数和线程数意味着更高的并发处理能力。现在PC的配置8核都已经普及了,更不用说服务器了。
5.7 网络
ES是天生自带分布式属性的,并且ES的分布式系统是基于对等网络的,节点与节点之间的通信十分的频繁,延迟对于ES的用户体验是致命的,所以对于ES来说,低延迟的网络是非常有必要的。因此,使用扩地域的多个数据中心的方案是非常不可取的,ES可以容忍集群跨多个机房,可以有多个内网环境,支持跨AZ部署,但是不能接受多个机房跨地域构建集群,一旦发生了网络故障,集群可能直接GG,即使能够保证服务正常运行,维护这样(跨地域单个集群)的集群带来的额外成本可能远小于它带来的额外收益。
5.8 总结
- 集群需要多少种配置(内存型/IO型/运算型),每种配置需要多少数量,通常需要和产品运营和运维测试商定,视业务量和服务器的承载能力而定,并留有一定的余量。
- 一个合理的ES集群配置应不少于5台服务器,避免脑裂时无法选举出新的Master节点的情况,另外可能还需要一些其他的单独的节点,比如ELK系统中的Kibana、Logstash等。
6、架构优化:
架构层面,非一言两语可详述,推荐阅读:从单机到百万节点:Elasticsearch高可用集群架构部署方案
- 合理的分配角色和每个节点的配置,在部署集群的时候,应该根据多方面的情况去评估集群需要多大规模去支撑业务。这个是需要根据在你当前的硬件环境下测试数据的写入和搜索性能,然后根据你目前的业务参数来动态评估的,比如:业务数据的总量、每天的增量、查询的并发以及QPS以及峰值的请求量。
- 节点并非越多越好,会增加主节点的压力
- 分片并非越多越好,从deep pageing 的角度来说,分片越多,JVM开销越大,负载均衡(协调)节点的转发压力也越大,查询速度也越慢。单个分片也并非越大越好,一般来说单个分片大小控制在30-50GB。
7、Mpping结构和索引字段优化:
doc_values:正排索引,对于不需要聚合的字段,关闭正排索引可节省资源,提高查询速度
fielddata:可以理解为“runtime_doc_values”,doc_value 为 index time 正排索引。fielddata会消耗JVM空间,如果执行大量数据的聚合使用 fielddata,会造成 OOM
尽量不要使用 dynamic mapping
ignore_above:字段保留的长度,越小越好
调整_source字段,通过include和exclude过滤
store:开辟另一块存储空间,可以节省带宽
注意:_sourse设置为false,则不存储源数据,可以节省磁盘,并且不影响搜索。但是禁用_source必须三思而后行,禁用后将导致以下后果:
update,update_by_query和reindex不可用。
高亮失效
reindex失效,原本可以修改的mapping部分参数将无法修改,并且无法升级索引
无法查看元数据和聚合搜索
影响索引的容灾能力
禁用_all字段:_all字段的包含所有字段分词后的Term,作用是可以在搜索时不指定特定字段,从所有字段中检索,ES 6.0之前需要手动关闭
关闭 Norms 字段:计算评分用的,如果你确定当前字段将来不需要计算评分,设置false可以节省大量的磁盘空间,有助于提升性能。常见的比如filter和agg字段,都可以设为关闭。
关闭 index_options(谨慎使用,高端操作):此设置用于在index time过程中哪些内容会被添加到倒排索引的文件中,例如TF,docCount、postion、offsets等,减少option的选项可以减少在创建索引时的CPU占用率,不过在实际场景中很难确定业务是否会用到这些信息,除非是在一开始就非常确定用不到,否则不建议删除
enabled:是否创建倒排索引,对于不需要查询的字段,关闭正排索引可节省资源,提高查询速度。