非常哇塞的 ES读场景、写场景 性能优化指南!你值得拥有!

2022-01-05 10:43:47 浏览数 (1)

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。

ES作为NoSQL数据库里非常重要的一员,使用越来越广泛。虽然它因为索引延迟的原因,数据在时效性上有一些缺陷,但其大容量、分布式的优秀设计,使得它在时效性要求并不是特别高的类实时搜索领域,能够大展身手。

根据使用场景和用途,ES可以分为写入和读取两种典型的应用方式。比如ELKB,我们就需要额外关注它的写优化;再比如从MySQL中同步数据到ES的宽表,我们就需要额外关注它的读优化

废话不多说,我们直接show一下优化方法。如果你对ES的一些概念还不是很清楚,建议收藏本文慢慢看。

1.写入优化

日志属于写多读少的业务场景,对写入速度要求很高。拿我们其中一个集群来说,单集群日志量达到百TB,每秒钟日志写入量达到10W条。

数据写入,主要有三个动作:flush、refresh和merge。通过调整它们的行为,即可在性能和数据可靠性之间进行权衡。

1.1 translog异步化

首先,ES需要写一份translog,它类似于MySQL中的redolog,为的是避免在断电的时候数据丢失。ES默认每次请求都进行一次flush,但对于日志来说,这没有必要,可以将这个过程改为异步的,刷盘间隔为60秒。参数如下:

代码语言:javascript复制
curl-H"Content-Type: application/json"-XPUT'http://localhost:9200/_all/_settings?preserve_existing=true'-d'{
  "index.translog.durability" : "async",
  "index.translog.flush_threshold_size" : "512mb",
  "index.translog.sync_interval" : "60s"
}'

这可以说是最重要的一步优化了,对性能的影响最大,但在极端情况下会有丢失部分数据的可能。对于日志系统来说,是可以忍受的。

1.2 增加refresh间隔

除了写translog,ES还会将数据写入到一个缓冲区中。但是注意了!此时,缓冲区的内容是无法被搜索到的,它还需要写入到segment里面才可以,也就是刷新到lucence索引里面。这就是refresh动作,默认1秒。也就是你写入的数据,大概率1秒之后才会被搜索到。

这也是为什么ES不是实时搜索系统的原因,它从数据写入到数据读出,一般是有一个合并过程的,有一定的时间差。

通过index.refresh_interval可以修改这个刷新间隔。

对于日志系统来说,当然要把它调大一点啦。xjjdog这里调整到了120s,减少了这些落到segment的频率,I/O的压力自然会小,写入速度自然会快。

代码语言:javascript复制
curl-H"Content-Type: application/json"-XPUT'http://localhost:9200/_all/_settings?preserve_existing=true'-d'{
  "index.refresh_interval" : "120s"
}'

1.3 merge

merge其实是lucene的机制,它主要是合并小的segment块,生成更大的segment,来提高检索的速度。

原因就是refresh过程会生成一大堆小segment文件,数据删除也会产生空间碎片。所以merge,通俗来讲就像是碎片整理进程。像postgresql等,也有vaccum进程在干同样的事。

显而易见,这种整理操作,既浪费I/O,又浪费CPU。

如果你的系统merge非常频繁,那么调整merge的块大小和频率,是一个比较好的方法。

2.读取优化

2.1 指定路由

如果你向ES里写数据,那么它会为你设置一个离散的隐藏ID,落到哪个分片,是不一定的。如果你根据一个查询条件查询数据,你设置了6个shards的话,它要查询6次才行。如果能够在路由的时候就知道数据在哪个分片上,查询速度自然会上升,这就要求我们在构造数据的时候,人工指定路由规则。它的实际运行规则如下:

代码语言:javascript复制
shard = hash(routing) % number_of_primary_shards

比如,一个查询会变成这样。

代码语言:javascript复制
GET my-index-000001/_search
{
  "query": {
    "terms": {
      "_routing": [ "user1" ] 
    }
  }
}

当然,如果你的查询维度较多,又对数据的查询速度有非常高的有求,根据routing存放多份数据是一个比较好的选择。

2.2 rollover冷热分离

rollover根据索引大小,文档数或使用期限自动过渡到新索引。当rollover触发后,将创建新索引,写别名将更新为指向新索引,所有后续更新都将写入新索引,比如indexname-000001.这种模式。

从rollover这个名字可以看出来,它和Java的log日志有一定的相似之处,比如Log4j的RollingFileAppender。

当索引变的非常大,通常是几十GB,那它的查询效率将变的非常的低,索引重建的成本也较大。实际上,很多索引的数据在时间维度上有较为明显的规律,一些冷数据将很少被用到。这个时候,建立滚动索引将是一个比较好的办法。

滚动索引一般可以与索引模板结合使用,实现按一定条件自动创建索引,ES的官方文档有具体的_rollover建立方法。

2.3 使用BoolQuery替代TermQuery

Bool查询现在包括四种子句,must、filter、should和must_not。Bool查询是true、false对比,而TermQuery是精确的字符串比对,所以如果需求相似,BoolQuery自然会快于TermQuery。

2.4 将大查询拆成分段查询

有些业务的查询比较复杂,我们不得不拼接一张非常大的宽表放在ES中,这有两个比较明显的问题。

  1. 宽表的数据往往需要从其他数据源中回查拼接而成,数据更新时对源库或者ES本身都有较大的压力
  2. 业务的查询JSON需要书写的非常复杂,查询效率未知,一次查询锁定的内存过高,无法进行深入优化

其实,宽表不论在RDBMS中还是ES中,都会与复杂的查询语句有关,其锁定时间都较长,业务也不够灵活。

应对这种场景的策略,通常将复杂的数据查询,转移到业务代码的拼接上来。比如,将一段非常冗长的单条查询,拆分成循环遍历的100条小查询。所有的数据库都对较小的查询请求有较好的响应,其整体性能整体上将优于复杂的单条查询。

这对我们的ES索引建模能力和编码能力提出了挑战。毕竟,在ES层面,互不相关的几个索引,将作为整体为其他服务提供所谓的数据中台接口。

2.5 增加第一次索引的速度

很多业务的索引数据往往来自于MySQL等传统数据库,第一次索引往往是全量索引,后面才是增量索引。必要的时候,也会进行索引的重建,大量的数据灌入造成了ES的索引速度建立缓慢。

为了缓解这种情况,建议在创建索引的时候,把副本数量设置成1,即没有从副本。等所有数据索引完毕,再将副本数量增加到正常水平。

这样,数据能够快速索引,副本会在后台慢慢复制。

3.通用优化

当然,我们还可以针对ES做一些通用的优化。比如,使用监控接口或者trace工具,发现线程池有明显的瓶颈,则需要调整线程池的大小。

具体的优化项如下。

3.1 线程池优化

新版本对线程池的配置进行了优化,不需要配置复杂的search、bulk、index线程池。有需要配置下面几个就行了:thread_pool.get.size, thread_pool.write.size, thread_pool.listener.size, thread_pool.analyze.size。具体可观测_cat/thread_pool接口暴露的数据进行调整。

3.2 物理冷热分离

上面的rollover接口,我们可以实现索引滚动。但是如何将冷数据存放在比较慢但是便宜的节点上?如何将某些索引移动过去?

ES支持给节点打标签,具体方式是在elasticsearch.yml文件中增加一些属性。比如:

代码语言:javascript复制
//热节点
node.attr.temperature: hot 
//冷节点
node.attr.temperature: cold 

节点有了冷热属性后,接下来就是指定数据的冷热属性,来设置和调整数据分布。ES提供了index shard filtering功能来实现索引的迁移。

首先,可以对索引也设置冷热属性。

代码语言:javascript复制
PUT hot_data_index/_settings
{
    "index.routing.allocation.require.temperature": "hot"
}

这些索引将自动转移到冷设备上。我们可以写一些定时任务,通过_cat接口的数据,自动的完成这个转移过程。

3.2 多磁盘分散I/O

其实,可以通过配置多块磁盘的方式,来分散I/O的压力,但容易会造成数据热点集中在单块磁盘上。

ES支持在一台机器上配置多块磁盘,所以在存储规模上有更大的伸缩性。在配置文件中,配置path.data属性,即可挂载多块磁盘。

代码语言:javascript复制
path.data : /data1, /data2, /data3

值得注意的是,如果你是在扩容,那么就需要配合reroute接口进行索引的重新分配。

3.3 减少单条记录的大小

Lucene的索引建立过程,非常耗费CPU,可以减少倒排索引的数量来减少CPU的损耗。第一个优化就是减少字段的数量;第二个优化就是减少索引字段的数量。具体的操作,是将不需要搜索的字段,index属性设置为not_analyzed或者no。至于_source和_all,在实际调试中效果不大,不再赘述。

End

ES的使用越来越广泛,从ELKB到APM,从NoSQL到搜索引擎,ES在企业中的地位也越来越重要。本文通过分析ES写入和读取场景的优化,力求从原理到实践层面,助你为ES加速。希望你在使用ES时能够更加得心应手。

通常,一个ES集群对配置的要求是较高的,尤其是APM等场景,甚至会占到PaaS平台的1/3资源甚至更多。ES提供了较多的配置选项,我们可以根据应用场景,调整ES的表现,使其更好的为我们服务。

作者简介:小姐姐味道 (xjjdog)。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。

0 人点赞