几个实用的Elasticsearch优化策略

783 阅读6分钟

我当过几次晋升答辩的评委,ES应该算是答辩过程中比较热点出现的词。

场景一:

问:“当你们的业务主表数据量大了,会不会有性能瓶颈?你准备如何处理?分库、分表、还是历史数据归档?”

答:“没事,我准备把数据放在ES里一份,不会有性能问题。”

问:“为什么存ES就不会有性能问题?”

答:“。。。”

场景二:

问:“你们现在系统的QPS越来越高了,后续准备怎么优化?”

答:“我准备把数据放在ES里一份,用ES扛。”

问:“为什么存ES里,就能扛住更大的QPS?”

答:“再多加服务器。”

问:“。。。”

场景三:

问:“你们的系统被上游系统依赖的越来越多,查询维度越来越多,你准备如何解决?”

答:“我准备把数据放在ES里一份,用ES解决查询维度的问题。”

问:“实时查询场景怎么办?”

答:“。。。”

我在想,现在的ES是不是热水化了,脑海里浮现了这些场景:

女:“亲爱的,我肚子疼。”

男:“多喝点儿热水。”

女:“亲爱的,我今天心情不好。”

男:“多喝点儿热水。”

女:“亲爱的,我感觉很孤独。”

男:“多喝点儿热水。”

ES确实是一件利器,作为MySQL和Redis的补充,解决很多复杂查询场景的问题。但是,ES本身也是需要根据适用场景的进行调优,才能使其优势最大化。

下面说几个实用的Elasticsearch优化策略:

force merge

索引是由若干分片构成,一个分片是一个物理上的Lucene索引,一个分片包含若干个索引段(Segment)。

当写入一条文档的时候,实际是写入到内存缓冲区中,而默认每秒钟一次的refresh,则是将内存buffer中的数据写入到一个新的段中,使其搜索查询可见(by id查询可做到实时)。

一个段被写入后,就不能再被删除和更新。因此,被删除的文档则是在.del文件中被标记删除。被更新的文档,实际上是删除旧文档,再添加新的文档。在查询时,需要从返回结果中过滤掉已删除的文档。

  • 如果数据更新或删除的数据比较多,那么每个段中的有效数据密度会变低,降低了查询的性能。
  • 每个搜索请求都必须轮流检查每个段,所以段越多,搜索也就越慢。

因此,通过段合并,可以将小的段合并成大的端,并将已删除的数据从段中物理删除,以此提高搜索性能。

不过,由于段合并对系统资源,IO和CPU产生巨大消耗,建议在凌晨业务低峰期进行

操作指令:POST /${index_name}/_forcemerge?max_num_segments=1

自定义路由

自定义路由能够很好地提升查询性能,当我们在执行一个搜索请求的时候:

1、请求会被随机发送到索引的一个节点。

2、该节点再将这个查询请求分发到这个索引的每个分片上,可能是主分片,也可能是副本分片。

3、每个分片执行这个搜索查询并返回结果到最初接受请求的节点。

4、该节点在进行合并和排序后并返回给客户端。

而自定义路由模式,可以使我们的查询更具目的性,我们不必盲目地去分发查询请求,取而代之的是:我们要告诉ES我们的数据在哪个分片上,这样就不用查询多个分片,并最终归并查询结果。

操作指令:

PUT /index/student/1?routing=key1

{

    "name":"Nancy",

    "age":10

}

GET /index/student/_search?routing=key1

{

  "query": {

    "term": {

      "name": "Nancy"

    }

  }

}


分片数量

ES采用过度分配机制,默认5个分片和一个副本。

其在单节点环境也是如此分配,优势在于,在扩容时仅需要增加服务器,ES会重新平衡集群,将部分分片移到新机器上;否则的话,需要重新创建一个拥有更多分片的新索引,并重新索引数据。

如果我们可以根据一些已知场景进行分片分配,则是一种非常行之有效的优化手段。

举个例子:我们预知索引数据增长有限,那么我们可以不分配这么多的分片,甚至只使用一个分片,这样在进行查询请求分发和查询结果归并的时候,执行速度会变得更快。

也就是说,分片的数量不是越多越好,而是越适合于索引的数据量越好。


refresh_interval

如前面所说,当写入一条文档的时候,实际是写入到内存缓冲区中,而默认每秒钟一次的refresh,则是将内存buffer中的数据写入到一个新的段中,使其搜索查询可见(by id查询可做到实时)。所以,ES被称之为 —— 近实时搜索引擎。

当我们在大批量写入文档的时候,且此时不对索引进行搜索查询,可将refresh_interval设置为-1,禁用内存自动刷新,可以有效提升文档的写入性能。写入结束后,当我们开始使用该索引时,再把参数调回来为默认值1秒。

另:refresh_interval设置为-1的时候,当内存缓冲区满了,会进行refresh操作。\


translog

ES增加了translog(事务日志),在每一次对ES进行写操作时均进行了日志记录,其作用完全等于同MySQL的redo log,实现了ACID中的持久性。

index.translog.durability:默认设置为request,即每次写请求完成之后执行磁盘的fsync操作。

但在很多场景下,我们ES的数据其实是可以接受有损的,比如日志场景,那么每次写请求都进行刷盘,对IO资源造成极大的消耗,是完全得不偿失的。

这种场景下,我们可以将index.translog.durability设置为async,同时,index.translog.sync_interval这个参数,代表进行刷盘的间隔,默认5秒(在index.translog.durability = async时有效),我们也可以酌情进行设置。


执行偏好

查询执行偏好,查询url里添加preference=_local,这样可以优先在本地节点执行操作,最小化网络延时。


其实很多时候,所谓的优化,无非是根据实际的使用场景,在性能和实时性上的一种取舍,在数据持久性和性能上的一种取舍,在读性能和写性能上的一种取舍,以及在业务高低峰期性能上的一种取舍。还是那句话,没有银弹,只有取舍,架构师就是要正确地选择取舍。

因此,我们不但要理解ES的实现原理,更要理解当前的业务场景,才能有针对性地进行优化。以及,把ES和MySQL、Redis结合起来使用,才能整体地满足于业务系统所需。