想成为一名顶尖Java开发工程师?这些优化手段一定要掌握!(六)

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
传统型负载均衡 CLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
简介: 想成为一名顶尖Java开发工程师?这些优化手段一定要掌握!

🎉 更多查询优化经验

(1)针对query_string或multi_match的查询,可以采用将多个字段的值索引到一个新字段的方法。在mapping阶段设置copy_to属性,将多个字段的值索引到新字段,这样在进行multi_match查询时可以直接使用新字段进行查询,从而提高查询速度。

(2)对于日期字段的查询,特别是使用now进行查询时,由于不存在缓存,建议从业务需求出发,考虑是否必须使用now进行查询。事实上,利用querycache可以大大提高查询效率,因此也需要对查询缓存进行充分利用。

(3)在设置查询结果集大小时,应根据实际情况进行设置,不能设置过大的值,如将query.setSize设置为Integer.MAX_VALUE。因为Elasticsearch内部需要建立一个数据结构来存放指定大小的结果集数据,设置过大的值会耗费大量的内存资源。

(4)对于聚合查询,需要避免层级过深的aggregation,因为这会导致内存和CPU资源消耗较大。建议在服务层通过程序来组装业务,或者采用pipeline的方式来优化查询。

(5)对于预先聚合数据的方式,可以采用复用预索引数据的技巧来提高聚合性能。例如,如果要根据年龄进行分组,可以预先在索引阶段设置一个age_group字段,将数据进行分类,而不是通过rangeaggregations来按年龄分组。这样可以通过age_group字段来进行groupby操作,从而避免层级过深的aggregation查询,提高聚合性能。

(6)在编写代码时,有时需要在数据集中进行匹配查询。对于大型数据集,经常需要考虑性能问题。为了优化查询性能,一种常见的优化方式是使用filter代替match查询。filter的优点在于它可以缓存,因此可以提高查询的速度。

此外,由于match查询通常涉及模糊匹配和转换的过程(fuzzy_transpositions),使用filter可以避免这种无意义的查询操作,进一步提高查询效率。然而,需要注意的是,使用filter来代替match查询的优化是有限的。对于一些复杂的查询,仍需要使用match查询来实现。因此,在优化查询性能时,需要综合考虑使用不同的查询方式,以达到最优化的效果。

(7)对于数据类型的选择需要理解Elasticsearch底层数据结构,在Elasticsearch2.x时代,所有数字都是按照keyword类型进行处理,这意味着每个数字都会建立一个倒排索引。虽然这种处理方式可以提高查询速度,但是在执行范围查询时,例如type>1 and type<5,需要将查询转换为type in (1,2,3,4,5),这显著增加了范围查询的难度和耗时。随后,Elasticsearch进行了优化,在处理integer类型数据时采用了一种类似于B-tree的数据结构,即Block k-d tree,以加速范围查询。Block k-d tree被设计用于多维数值字段,并可用于高效过滤地理位置等数据。

此外,它还可用于单维度的数值类型。对于单维度的数据,Block k-d tree的实现与传统的B-tree有所不同。它对所有值进行排序,并反复从中心进行切分,生成具有类似B-tree结构的索引。该结构的叶子节点存储的不是单个值,而是一个值的集合,也就是所谓的一个Block。每个Block最多包含512至1024个值,以确保值在Block之间均匀分布。

这种数据结构大大提高了范围查询的性能,因为在传统的索引结构中,满足查询条件的文档集合并不是按照文档ID顺序存储的,而是需要构造一个巨大的bitset来表示。而使用Block k-d tree索引结构,则可以直接定位满足查询条件的叶子节点块在磁盘上的位置,然后顺序读取,显著提高了范围查询的效率。

(8)在使用Block k-d tree的数据结构进行范围查询时,磁盘读取是顺序读取,因此对范围查询有很大的优势。然而在某些场景下使用PointRangeQuery会非常慢,因为它需要将满足查询条件的docid集合拿出来单独处理。这个处理过程在org.apache.lucene.search.PointRangeQuery#createWeight方法中可以读取到,主要逻辑是在创建scorer对象的时候,顺带先将满足查询条件的docid都选出来,然后构造成一个代表docid集合的bitset。

在执行advance操作时,就会在这个bitset上完成。由于这个构建bitset的过程类似于构造Query cache的过程,所有的耗时都在build_scorer上。因此,使用PointRangeQuery在该场景下会非常慢。另外,对于term查询,如果数值型字段被转换为PointRangeQuery,也会遇到同样的问题。在这种情况下,无法像Postlings list那样按照docid顺序存放满足查询条件的docid集合,因此无法实现postings list上借助跳表做蛙跳的操作。

(9)对于像isDeleted这样只有两个可能取值(是/否)的字段,Elasticsearch会自动根据倒排索引的文档数和Term的文档频率来判断是否使用倒排索引进行查询。如果该Term的文档频率太高,超过了一定的阈值,Elasticsearch会认为使用倒排索引查询的效率不如使用全表扫描,因此会放弃使用倒排索引,转而使用全表扫描。这个阈值可以通过设置index.max_terms_count来调整。如果该字段在查询时频繁被使用,可以考虑将其映射为一个不分词、不使用倒排索引的字段,这样可以避免在查询时产生额外的开销。

(10)当进行多个term查询并列的时候,在Elasticsearch中执行顺序不是由查询时写入的顺序决定的。实际上,Elasticsearch会根据每个filter条件的区分度来评估执行顺序,将高区分度的filter条件先执行,以此可以加速后续的filter循环速度,从而提高查询效率。举例来说,如果一个查询条件的结果集很小,那么Elasticsearch就会优先执行这个条件。为了实现这一点,当使用term进行查询时,每个term都会记录一个词频,即这个term在整个文档中出现的次数。这样Elasticsearch就能判断每个term的区分度高低,从而决定执行顺序。综上所述,当使用多个term查询时,Elasticsearch会根据每个filter条件的区分度来决定执行顺序,以此提高查询效率。

(11)为了快速查找索引 Term 的位置,可以采用哈希表作为索引表来提高查找效率。同时,为了减少倒排链的查询和读取时间,可以采用 RoaringBitmap 数据结构来存储倒排链,并且结合 RLE Container 实现倒排链的压缩存储。这样,可以将倒排链的合并问题转化为排序问题,从而实现批量合并,大大降低了合并的性能消耗。另外,根据数据的分布,自动选择 bitmap/array/RLE 容器,可以进一步提高 RLE 倒排索引的性能。

(12)在增量索引场景下,如果增量索引的变更量非常大,会导致频繁更新内存 RLE 倒排索引,进而带来内存和性能的消耗。为了解决这个问题,可以将 RLE 倒排索引的结构固化到文件中,在写索引时就可以完成对倒排链的编码,避免了频繁更新内存索引的问题。这种做法可以提升索引的写入性能,同时保证了查询的高效性和稳定性。

通过开启慢查询配置定位慢查询

一般而言,当 Elasticsearch 查询所花费的时间超过一定阈值时,系统会记录该查询的相关信息并将其记录在慢查询日志中以供查看。在实际的应用场景中,可以通过对慢查询日志的分析来确定哪些查询较为耗时,从而帮助进行性能优化。通过开启慢查询配置可以快速定位Elasticsearch查询速度缓慢的问题,并进行相应的性能调优,提高系统的查询效率和用户体验。对于 ElasticSearch这类搜索引擎而言,通过设置开启慢查询日志可以快速定位查询速度较慢的原因。

🍊 数据结构优化

针对 Elasticsearch 的使用场景,需要根据实际情况进行文档数据结构的设计,以便更好地发挥 Elasticsearch 的搜索和分析能力。在设计文档结构时,需要将使用场景作为主要考虑因素,去掉不必要的数据。这有助于减少索引的大小、提高搜索和分析的效率。

在实际使用 Elasticsearch 进行数据存储和检索时,应根据具体场景灵活使用索引和文档类型,合理划分数据和定义字段。这有助于提高搜索和分析的精度和效率,并能够满足不同场景的需求。

因此,在使用 Elasticsearch 时,必须深入了解应用场景,进行合理的文档数据结构设计,去掉不必要的数据,提高搜索和分析的效率和精度,最终实现更好的业务效果和用户体验。

🎉 减少不需要的字段

如果Elasticsearch作为业务搜索服务的一部分,应该避免将一些不需要用于搜索的字段存储到Elasticsearch中。这种做法能够节省空间,同时在相同的数据量下,也能提高搜索性能。此外,应该避免使用动态值作为字段,因为动态递增的mapping可能会导致集群崩溃。同样,需要控制字段的数量,业务中不使用的字段应该不要索引。控制索引的字段数量、mapping深度、索引字段的类型,这是优化Elasticsearch性能的关键之一。

Elasticsearch在默认情况下设置了一些关于字段数、mapping深度的限制,即index.mapping.nested_objects.limit、index.mapping.total_fields.limit和index.mapping.depth.limit。其中,index.mapping.nested_objects.limit限制了Elasticsearch中嵌套对象的数量,它的默认值为10000。index.mapping.total_fields.limit限制了Elasticsearch中字段的数量,它的默认值为1000。index.mapping.depth.limit限制了Elasticsearch中mapping的嵌套深度,它的默认值为20。在实际使用中,根据业务需求可以适当调整这些限制值以获得更好的性能。

🎉 Nested Object vs Parent/Child

建议在mapping设计阶段尽量避免使用nested或parent/child的字段,因为这些查询性能较差,能不用就不用。如果必须使用nestedfields,要保证nestedfields字段不能过多,因为针对1个document,每一个nestedfield,都会生成一个独立的document,这将使doc数量剧增,影响查询效率,尤其是JOIN的效率。

默认Elasticsearch的限制是每个索引最多有50个nestedfields,如果需要增加或减少nestedfields的数量,可以修改配置文件中的index.mapping.nested_fields.limit参数。对于常规的文档存储和查询,使用NestedObject可以保证文档存储在一起,因此读取性能高;相反,对于需要独立更新父文档或子文档的情况下,可以使用Parent/Child结构,这样可以保证父子文档可以独立更新,互不影响;但是为了维护join关系,需要占用部分内存,读取性能较差。因此,在选择使用NestedObject还是Parent/Child结构时,需要根据具体的场景进行选择,子文档偶尔更新且查询频繁时可以选择NestedObject,而子文档更新频繁时可以选择Parent/Child结构。

🎉 静态映射

为确保集群的稳定性,在使用Elasticsearch的过程中,推荐选择静态映射方式。静态映射不仅能够保证数据类型的一致性,还能够提高查询效率。相反,如果使用动态映射,可能会导致集群崩溃,并带来不可控制的数据类型,从而影响业务的正常运行。此外,在Elasticsearch中,数据的存储类型分为匹配字段和特征字段两种。匹配字段用于建立倒排索引以进行query匹配,而特征字段如ctr、点击数、评论数等则用于粗排。因此,在设计索引时需要根据不同的功能选择不同类型的字段进行建立倒排索引,以满足业务的需求和提高查询效率。综上所述,静态映射是建立Elasticsearch稳定、高效的必要条件,而动态映射的使用应在必需时加以限制。

🎉 document 模型设计

MySQL经常需要进行一些复杂的关联查询,但是在Elasticsearch中并不推荐使用复杂的关联查询,因为一旦使用会影响性能。因此,最好的做法是在Java系统中先完成关联,将关联好的数据直接写入Elasticsearch中。这样,在搜索时,就不需要使用Elasticsearch的搜索语法来完成关联搜索。

在设计document模型时,需要非常重视,因为在搜索时执行复杂的操作会影响性能。Elasticsearch支持的操作有限,因此需要避免考虑使用Elasticsearch进行一些难以操作的事情。如果确实需要使用某些操作,最好是在document模型设计时就完成。此外,需要尽量避免使用复杂操作,例如join/nested/parent-child搜索,因为它们的性能都很差。

🍊 集群架构设计

为了提高Elasticsearch服务的整体可用性,需要合理的部署集群架构。Elasticsearch集群采用主节点、数据节点和协调节点分离的架构,即将主节点和数据节点分开布置,同时引入协调节点,以实现负载均衡。在5.x版本以后,数据节点还可进一步细分为Hot-Warm的架构模式。

🎉 主节点和数据节点

在Elasticsearch配置文件中,有两个非常重要的参数,分别是node.master和node.data。这两个参数配合使用,可以提高服务器的性能。主节点配置node.master:true和node.data:false,表示该节点只作为一个主节点,不存储任何索引数据。推荐每个集群运行3个专用的Master节点,以提高集群的弹性。在使用时,还需设置discovery.zen.minimum_master_nodes参数为2,以避免脑裂的情况。因为三个主节点仅负责集群的管理,不包含数据、不进行搜索和索引操作,因此它们的CPU、内存和磁盘配置可以比数据节点少很多。

数据节点配置node.master:false和node.data:true,只作为一个数据节点,专门用于存储索引数据,实现功能单一,降低资源消耗率。Hot-Warm架构是将数据节点分成热节点和暖节点,热节点只保存最新的数据,暖节点则保存旧的数据,以实现不同数据的不同存储需求。

引入协调节点可以实现负载均衡,减少节点间的通信压力,提升服务的整体性能。在协调节点上可以运行诸如Kibana、Logstash、Beats等工具,以进行数据可视化、数据采集等操作。在配置协调节点时,还需设置discovery.zen.minimum_master_nodes参数为2,避免脑裂的情况。

总之,合理的部署Elasticsearch集群架构,可以提高服务的整体可用性,减少节点间的通信负担,降低资源消耗率,优化服务的整体性能。

🎉 hot节点和warm节点

hot节点主要是用于存储索引数据并保存最近频繁被查询的索引。由于索引是一项CPU和IO密集型操作,因此建议使用SSD磁盘类型来保持高性能的写入操作。同时,为了保证高可用性,建议至少部署3个最小化的hot节点。如果需要增加性能,可以增加服务器数量。要将节点设置为hot类型,elasticsearch.yml应该包含以下配置:node.attr.box_type:hot。对于针对指定索引操作,可以通过设置index.routing.allocation.require.box_type:hot使其将索引写入hot节点。

warm节点主要是处理大量不经常访问的只读索引的设计。由于这些索引是只读的,因此warm节点倾向于挂载大量普通磁盘来替代SSD。内存和CPU的配置应该与hot节点保持一致,节点数量一般也应该大于或等于3。要将节点设置为warm类型,elasticsearch.yml应该包含以下配置:node.attr.box_type:warm。同时,也可以在elasticsearch.yml中设置index.codec:best_compression以保证warm节点的压缩配置。

当索引不再频繁查询时,可以使用index.routing.allocation.require.box_type:warm将索引标记为warm,从而确保索引不写入hot节点,以便将SSD磁盘资源用于处理更为关键的操作。一旦设置了该属性,Elasticsearch会自动将索引合并到warm节点。

🎉 协调节点

协调(coordinating)节点是分布式系统中的一个节点,用于协调多个分片或节点返回的数据,进行整合后返回给客户端。该节点不会被选作主节点,也不会存储任何索引数据。在Elasticsearch集群中,所有的节点都有可能成为协调节点,但可以通过设置node.master、node.data、node.ingest都为false来专门设置协调节点。

协调节点需要具备较好的CPU和较高的内存。在查询时,通常涉及从多个node服务器上查询数据,并将请求分发到多个指定的node服务器,对各个node服务器返回的结果进行汇总处理,最终返回给客户端。因此,协调节点在查询负载均衡方面发挥了重要的作用。除此之外,可以通过设置node.master和node.data的值来特别指定节点的功能类型,如:node.master:false和node.data:true,该节点仅用于数据存储和查询,node.master:true和node.data:false,该节点仅用于协调请求等。设置节点的功能类型可以使其功能更加单一,从而降低其资源消耗率,提高集群的性能。

🎉 关闭data节点服务器中的http功能

在Elasticsearch集群中,关闭data节点服务器中的http功能是一种有效的保障数据安全和提升服务性能的方法。具体实现方式是对所有的数据节点进行http.enabled:false的配置参数设置,同时不安装head、bigdesk、marvel等监控插件,这样data节点服务器只需处理索引数据的创建、更新、删除和查询等操作。而http服务可以在非数据节点服务器上开启,相关监控插件也可以安装到这些服务器上,用于监控Elasticsearch集群的状态和数据信息。通过这种方法,可以在保证数据安全的前提下,提升Elasticsearch集群的服务性能。

🎉 一台服务器上只部署一个node

在一台物理服务器上,可以通过设置不同的启动端口来启动多个node服务器节点。然而,由于服务器的CPU、内存、硬盘等资源是有限的,当在同一台服务器上启动多个node节点时,会导致资源的竞争和争夺,从而影响服务器性能。因此,建议在进行服务器节点部署时,将不同的node节点部署在不同的物理服务器上,从而实现资源的充分利用,提高服务器性能和可靠性。同时,为了确保多个node节点之间的通信和协调,可以使用负载均衡器等技术手段来实现节点间的负载均衡和故障转移,从而保障应用程序的稳定运行。

🎉 集群分片设置

在Elasticsearch中,一旦创建好索引后,就不能再调整分片的设置。由于一个分片对应于一个Lucene索引,而Lucene索引的读写会占用大量的系统资源,因此分片数不能设置过大。因此,在创建索引时,合理配置分片数是非常重要的。一般来说,应当遵循以下原则:

(1)控制每个分片占用的硬盘容量不超过Elasticsearch的最大JVM堆空间设置,通常不应超过32GB。因此,如果索引的总容量在500GB左右,分片大小应在16个左右。当然,最好同时考虑原则2。

(2)考虑节点数量。通常情况下,每个节点对应一台物理机。如果分片数过多,即大大超过了节点数,很可能会导致在某个节点上存在多个分片。一旦该节点发生故障,即使保持了一个以上的副本,仍然有可能导致数据丢失,从而无法恢复整个集群。因此,一般都设置分片数不超过节点数的三倍。

🍊 可用性优化

在可用性方面,Elasticsearch原生版本存在三个问题:

(1)系统健壮性不足:系统在面对意外情况时无法保持正常运行,具体表现为系统容易导致集群雪崩和节点OOM。这种情况在大流量的情况下尤为明显,此时系统的负载会变得非常高,容易导致节点内存耗尽,甚至导致集群崩溃。造成这种情况的主要原因是内存资源不足和负载不均。具体来说,内存资源不足可能是由于系统中存在内存泄漏或者内存管理不当等引起的。而负载不均则可能是由于集群中的节点在处理任务时,存在一些节点负载过高,而另一些节点负载过轻的情况。为了解决这个问题,需要采取一些优化措施,以提升系统的健壮性和稳定性。具体来说,可以优化服务限流和节点均衡策略。限流策略的作用是控制系统的访问量,防止系统因为大量请求而导致崩溃或者响应变得异常缓慢。节点均衡策略则是通过对任务进行分配,以使得每个节点的负载均衡,避免一些节点负载过高,而另一些节点负载过轻的情况。这些优化措施可以有效增强系统的健壮性,使得系统更加稳定可靠。

(2)容灾方案欠缺:尽管Elasticsearch自身提供了副本机制,以确保数据的安全性,但是对于涉及多个可用区的容灾策略,需要云平台额外实现。此外,即使在存在副本机制和跨集群复制的情况下,仍然需要提供低成本的备份回滚能力,以应对可能存在的误操作和数据删除的风险。针对这些问题,建议采取以下措施:首先,对于多可用区容灾方案的实现,可以考虑采用云平台提供的跨可用区副本和快照备份功能,以确保数据可靠性和可用性。其次,可以部署多个集群,实现数据的跨集群复制,以进一步提高数据的安全保障。同时,需要在数据备份和回滚方面做好充分的准备,确保在发生误操作或数据删除时能够迅速恢复数据。

(3)内核Bug:Elasticsearch是一种开源搜索引擎,其内核存在一些Bug,可能会影响其可用性。为了解决这些问题,Elasticsearch修复了一系列与内核可用性相关的问题,包括Master任务堵塞、分布式死锁、滚动重启速度慢等问题。此外,为了确保用户能够及时获得修复后的版本,Elasticsearch及时提供了新版本给用户升级。这些措施充分展示了Elasticsearch对用户可用性的关注,并且以负责任的方式解决问题。这些改进将有助于提升Elasticsearch的性能和可靠性,进一步满足用户在搜索领域的需求。

接下来,将针对用户在可用性层面常遇到的两类问题展开分析:

(1)当高并发请求过多时,会导致集群崩溃的问题。为了解决这个问题,可以采用一些方法来提升集群的吞吐能力。其中,可以优化集群的配置,例如增加硬件资源、提升网络带宽、调整线程池大小等。另外,可以采用异步I/O方式来提高请求的处理效率,从而缓解集群压力。此外,负载均衡技术也是一个值得推荐的方法,通过将请求分配到不同的节点上,可以避免某些节点过载而导致集群崩溃的情况发生。总之,需要采取多种方法综合应对高并发请求的问题,从而提升集群的稳定性和吞吐能力。

(2)当进行单个大查询时,很容易出现节点因负载过大而崩溃的情况。为了解决这个问题,可以采用一些优化措施。首先是数据分片和副本,这可以将数据分散到多个节点上,减少单个节点的负载,同时保证数据的可靠性和高可用性。其次是搜索建议,它可以根据用户输入的关键词提供相关的搜索建议,减少用户不必要的查询请求。最后是聚合结果优化,它可以对查询结果进行聚合,减少不必要的数据传输和计算,提高查询效率和稳定性。通过这些优化措施,可以有效地减轻单个查询对节点的负载,提升系统的查询效率和稳定性,达到更好的用户体验和服务质量。

🍊 高并发请求压垮集群

高并发请求是一种常见的场景,可能会导致集群崩溃。例如,早期内部的一个日志集群,其中写入量在一天内突然增加了5倍,导致集群中的多个节点的Old GC卡住而脱离集群,集群变成了RED状态,写入操作停止了。这个场景可能会对集群造成很大的损失。对于挂掉的节点,进行内存分析后发现,大部分内存都被反序列化前后的写入请求所占用。这些写入请求是堆积在集群的接入层位置上的。接入层是指用户的写入请求先到达其中一个数据节点,称之为数据节点。然后由该协调节点将请求转发给主分片所在节点进行写入,主分片写入完毕再由主分片转发给从分片写入,最后返回给客户端写入结果。从内存分析结果看,这些堆积的位置导致了节点的崩溃,因此根本原因是协调节点的接入层内存被打爆。

经过问题原因的分析,制定了针对高并发场景下的优化方案。这个方案包括两个关键点:加强对接入层的内存管理和实现服务限流。为了避免集群崩溃,需要确保接入层内存不会被输入请求打爆,因此需要加强内存管理。在实现服务限流的方面,需要一个能够控制并发请求数量,并且能够精准地控制内存资源的方案。这个方案还要具有通用性,能够作用于各个层级实现全链限流。

🍊 服务限流

一般情况下,数据库的限流策略是从业务端或者独立的代理层配置相关的业务规则,进行资源预估等方式进行限流。但是这种方式适应能力较弱、运维成本高、业务端很难准确地预估资源消耗。原生版本本身也有限流策略,但是单纯地基于请求数的限流不能控制资源使用量,而且只作用于分片级子请求的传输层,对于接入层无法起到有效的保护作用。

因此,优化方案是基于内存资源的漏桶策略。将节点JVM内存作为漏桶的资源,当内存资源足够的时候,请求可以正常处理,当内存使用量到达一定阈值的时候分区间阶梯式平滑限流,处理中的请求和merge操作都可以得到保证,从而保证节点内存的安全性。这个方案不仅可以控制并发数,还可以控制资源使用量并且具有通用性,可以应用于各个层级实现全链限流。

在限流方案中,一个重要的挑战是如何实现平滑限流。采用单一的阈值限流很容易出现请求抖动的情况,例如请求一上来就会立即触发限流,因为内存资源不足,而稍微放开一点请求量又会迅速涌入,使内存资源再次极度紧张。因此,通过设置高低限流阈值区间、基于余弦变换实现请求数和内存资源之间的平滑限流方案。在该区间中,当内存资源足够时,请求通过率达到100%;当内存到达限流区间时,请求通过率逐步下降。而当内存使用量下降时,请求通过率也会逐步上升,而不是一下子放开。经过实际测试,平滑的区间限流能够在高压力下保持稳定的写入性能。平滑限流方案是对原生版本基于请求数漏桶策略的有效补充,作用范围更广泛,能覆盖协调节点、数据节点的接入层和传输层。但需要说明的是,该方案并不会替代原生的限流方案,而是对其进行有效的补充。

🍊 单个大查询打挂节点

在某些分析场景中,需要进行多层嵌套聚合,这可能导致返回的结果集非常大,因此可能导致某个请求将节点打挂。在这种聚合查询流程中,请求首先到达协调节点,然后被拆分为分片级子查询请求,然后发送给目标分片所在的数据节点进行子聚合。最后,协调节点将所有分片结果收集并进行归并、聚合、排序等操作。然而,这个过程中存在两个主要问题点。

第一个问题是,当协调节点大量汇聚结果并反序列化之后,可能会导致内存膨胀。这可能是由于结果集太大,或者节点内存不足等原因造成的。

第二个问题是,二次聚合可能会产生新的结果集,这可能导致内存爆炸。

为了解决上述单个大查询的问题,可以采用以下五个优化方案。

首先,针对内存开销问题,可以通过增加节点内存大小或将查询结果进行分批处理来优化。这样能够在降低内存使用率的同时,提高查询效率。

接着,针对内存浪费严重的写入场景,优化方案主要是实现弹性的内存buffer,并对于读写异常的请求及时进行内存回收。要注意,这里所提到的内存回收策略并不是指GC策略。JVMGC债务管理主要评估JVMOldGC时长和正常工作时长的比例来衡量JVM的健康情况,特殊情况下会重启JVM以防止长时间hang死,这与内存回收策略是两个不同的方面。通过内存利用率优化,整个公有云的Elasticsearch集群的可用性得到了提升,达到了4个9。内存利用率提升了30%,在高压力场景下节点稳定性更强,基本能保证节点不会OOM,集群也不会雪崩。总的来说,内存利用率,内存回收策略以及JVMGC债务管理都是优化内存利用率的重要方面,它们能够提高系统吞吐量,减少节点OOM的发生,保障系统的稳定性和可用性。通过对这些方面的优化,能够有效地提高系统的性能表现,使系统更加健康和稳定。

其次,在进行聚合操作时,需要特别注意减少中间结果的存储和传输。对于大规模数据集的查询,优先考虑使用分布式计算框架,如Apache Spark等。

然后,在数据库管理中,单个查询内存限制是一个非常有用的功能。当一个查询过于庞大时,其会占用大量的内存资源,从而影响其他所有请求的响应时间。通过设置单个查询内存限制,可以有效地控制查询的内存使用量,从而保证整个数据库系统的正常运行。除此之外,滚动重启速度优化也是一个非常实用的功能。尤其是在大规模集群环境下,单个节点的重启时间往往较长,如果要重启整个集群,可能会导致整个系统长时间处于不可用状态。通过优化滚动重启速度,可以将单个节点的重启时间从10分钟降至1分钟以内,大幅缩短了重启时间,从而提高了系统的可用性。值得一提的是,这个优化已经在7.5版本中被合并了,因此用户不需要再自己手动进行配置。如果遇到大集群滚动重启效率问题,可以关注此功能,以提高数据库系统的可靠性和稳定性。

最后,第三个优化方案的重点是内存膨胀预估加流式检查。该方案主要分为两个阶段:第一阶段在协调节点接收数据节点返回的响应结果反序列化之前做内存膨胀预估,并在内存使用量超过阈值时直接熔断请求;第二阶段在协调节点reduce过程中,流式检查桶数,每增加固定数量的桶检查一次内存,如果超限则直接熔断。这样用户不再需要关心最大桶数,只要内存足够就能最大化地满足业务需求。不足之处是大请求还是被拒掉了,但是可以通过官方已有的batch reduce的方式缓解,即每收到部分子结果就先做一次聚合,这样能降低单次聚合的内存开销。该方案已经提交给官方并合并了,将在最近的7.7.0版本中发布。

🍊 性能优化

性能优化的场景可以分为写入和查询两个部分。在写入方面,主要包括海量时序数据场景,如日志和监控,通常能够实现千万级别的吞吐。带有id的写入会导致性能衰减一倍,因为需要首先查询记录是否存在。在查询方面,主要包括搜索场景和分析场景。搜索服务需要高并发并且具有低延迟;而聚合分析主要涉及大型查询,需要大量的内存和CPU开销。

从性能影响面的角度来看,硬件资源和系统调优通常是直接可控的,例如资源不足时可以进行扩容,调整参数深度来进行调优等。然而,存储模型和执行计划通常涉及内核优化,因此普通用户难以直接进行调整。接下来,将重点介绍存储模型和执行计划的优化。

存储模型的优化是一个关键问题。Elasticsearch底层Lucene基于LSM Tree的数据文件。原生默认的合并策略是按文件大小相似性合并,一次性固定合并10个文件,采用近似分层合并。这种合并方式最大的优点是效率高,可以快速降低文件数。但是,文件不连续会导致查询时的文件裁剪能力较弱,例如查询最近1小时的数据,有可能会将1小时的文件拆分到几天前的文件中,进而增加了必须检索的文件数量。业界通常采用解决数据连续性的合并策略,例如基于时间窗口的合并策略,如以Cassandra、HBase为代表的策略。其优点在于数据按时间序合并,查询效率高,还可以支持表内TTL。缺点是仅适用于时序场景,并且文件大小可能不一致,从而影响合并效率。另一类策略由LevelDB、RocksDB为代表的分层合并策略构成,一层一组有序,每次抽取部分数据向下层合并,优点在于查询高效。但如果相同的数据被合并多次,这将影响写入吞吐。

最后是优化合并策略,其目标是提高数据连续性、收敛文件数量,提升文件裁剪的能力以提高查询性能。实现策略是按时间序分层合并,每层文件按创建时间排序。除了第一层外,所有层次都按照时间序和目标大小进行合并,而不是固定每次合并文件数量,保证了合并效率。对于少量未合并的文件和冷分片文件,采用持续合并策略,将超过默认5分钟不再写入的分片进行持续合并,并控制合并并发和范围,以降低合并成本。

🍊 执行引擎的优化

在Elasticsearch中,有一种聚合叫做Composite聚合,它支持多字段的嵌套聚合,类似于MySQL的group by多个字段,同时也支持流式聚合,即以翻页的形式分批聚合结果。使用Composite聚合时,只需要在查询时聚合操作下面指定composite关键字,并指定一次翻页的长度和group by的字段列表,每次拿到的聚合结果会伴随着一个after key返回,下一次查询可以拿着这个after key查询下一页的结果。

Composite聚合的实现原理是利用一个固定size的大顶堆,size就是翻页的长度,全量遍历一把所有文档迭代构建这个基于大顶堆的聚合结果,最后返回这个大顶堆并将堆顶作为after key。第二次聚合时,同样的全量遍历一把文档,但会加上过滤条件排除不符合after key的文档。然而,这种实现方式存在性能问题,因为每次拉取结果都需要全量遍历一遍所有文档,并未实现真正的翻页。

为了解决这个问题,提出了一种优化方案,即利用index sorting实现after key跳转以及提前结束(earlytermination)。通过index sorting,可以实现数据的有序性,从而实现真正的流式聚合,大顶堆仍然保留,只需要按照文档的顺序提取指定size的文档数即可快速返回。下一次聚合时,可以直接根据请求携带的afterkey做跳转,直接跳转到指定位置继续向后遍历指定size的文档数即可返回。这种优化方案可以避免每次翻页全量遍历,大幅提升查询性能。

在Elasticsearch7.6版本中,已经实现了覆盖数据顺序和请求顺序不一致的优化场景。该版本在性能层面进行了全面优化,从底层的存储模型、执行引擎、优化器到上层的缓存策略都有相应的提升。具体来说,该版本在存储模型方面采用了更加高效的数据结构和算法,以减少磁盘I/O、内存消耗等问题,提高读写性能。在执行引擎方面,优化了查询和聚合操作的执行过程,使其能快速响应请求并返回数据。而在优化器方面,针对不同的查询场景进行了优化,以减少计算量,提高查询效率。最后,在缓存策略方面,采用了更加智能的缓存机制,以加速常用请求的响应速度。以上这些优化措施的整合,实现了对覆盖数据顺序和请求顺序不一致的场景的优化,并带来了更高效、更可靠的Elasticsearch体验。

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
9天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
30 6
|
9天前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
23 4
|
11天前
|
缓存 监控 Java
如何运用JAVA开发API接口?
本文详细介绍了如何使用Java开发API接口,涵盖创建、实现、测试和部署接口的关键步骤。同时,讨论了接口的安全性设计和设计原则,帮助开发者构建高效、安全、易于维护的API接口。
35 4
|
16天前
|
SQL Java 程序员
倍增 Java 程序员的开发效率
应用计算困境:Java 作为主流开发语言,在数据处理方面存在复杂度高的问题,而 SQL 虽然简洁但受限于数据库架构。SPL(Structured Process Language)是一种纯 Java 开发的数据处理语言,结合了 Java 的架构灵活性和 SQL 的简洁性。SPL 提供简洁的语法、完善的计算能力、高效的 IDE、大数据支持、与 Java 应用无缝集成以及开放性和热切换特性,能够大幅提升开发效率和性能。
|
17天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
34 2
|
17天前
|
存储 Java 开发者
成功优化!Java 基础 Docker 镜像从 674MB 缩减到 58MB 的经验分享
本文分享了如何通过 jlink 和 jdeps 工具将 Java 基础 Docker 镜像从 674MB 优化至 58MB 的经验。首先介绍了选择合适的基础镜像的重要性,然后详细讲解了使用 jlink 构建自定义 JRE 镜像的方法,并通过 jdeps 自动化模块依赖分析,最终实现了镜像的大幅缩减。此外,文章还提供了实用的 .dockerignore 文件技巧和选择安全、兼容的基础镜像的建议,帮助开发者提升镜像优化的效果。
|
17天前
|
监控 Java 数据库连接
在Java开发中,数据库连接管理是关键问题之一
在Java开发中,数据库连接管理是关键问题之一。本文介绍了连接池技术如何通过预创建和管理数据库连接,提高数据库操作的性能和稳定性,减少资源消耗,并简化连接管理。通过示例代码展示了HikariCP连接池的实际应用。
17 1
|
10天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
23 0
|
11天前
|
Java API Android开发
kotlin和java开发优缺点
kotlin和java开发优缺点
25 0
WK
|
16天前
|
开发框架 移动开发 Java
C++和Java哪个更适合开发移动应用
本文对比了C++和Java在移动应用开发中的优劣,从市场需求、学习难度、开发效率、跨平台性和应用领域等方面进行了详细分析。Java在Android开发中占据优势,而C++则适合对性能要求较高的场景。选择应根据具体需求和个人偏好综合考虑。
WK
31 0