本文翻译自QBox官方博客“Elasticsearch性能调优权威指南”系列文章的第一篇,主要从集群拓扑结构、分片与副本、容量规划以及内存优化等方面介绍了性能调优的基本原理和实践策略。后续还有第二篇和第三篇,敬请期待。

作者:Adam Vanderbush

译者:杨振涛

搜索和分析是现代软件应用的两大关键特性。准实时地处理海量数据的扩展性和性能,是许多应用系统的基本要求,比如移动应用、web以及数据分析应用。文本框的自动完成、搜索联想、本地化或者地理位置搜索以及聚合导航是目前满足业务需求可用性的基本标准。

调优是必需、必要和重要的!任何系统的调优都必需有性能度量指标的支持,因此对监控的清晰理解,以及对变化的度量指标的映射,对所有Elasticsearch用户来说非常必要。

本系列教程的3篇文章将会介绍一些性能调优的技巧和方法,并解释与每一步最相关的系统配置设置和度量。

1集群状态大小(索引和分片的容量规划)

如果说一个分片太少而1000个分片太多的话,那么到底如何确定多少个分片呢?这个问题一般情况下很难回答。有太多的变量了:所用的硬件、文档大小和复杂度、索引和分析文档的方式、执行查询的类型、执行的聚合、建模数据的方式,等等。

很容易就能在elasticsearch中创建大量索引以及许许多多的分片,但创建每一个索引和分片都是一笔开销。单单是管理负荷就能降低ES集群的性能;如果有太多的索引或分片的话,甚至可能导致集群状态变成红色。有太多索引或分片且超负荷的ES集群,其索引和查询性能也会受到非常大的影响。

ES提供了弱结构化的灵活性。可以增加一个带有任意数量属性的JSON文档到一个索引库,而无需事先告知ES任何关于属性的信息。这些新的属性——包括它们的名称,它们的类型,以及放在什么样的索引库等——全都是自动加入到ES的索引mapping和集群状态信息中的。管理负荷最重要的一点是集群状态的大小,它包含了集群中每一个索引的所有mapping信息。

集群状态API提供了整个集群的综合状态信息。

一个有3个模板和5个索引库的单节点集群,状态API返回的数据大致如下:

{
  "cluster_name": "elasticsearch",
  "version": 6,
  "state_uuid": "skxF0gCYTAGQAUU-ZW4_GQ",
  "master_node": "VyKDGurkQiygV-of4B1ZAQ",
  "blocks": {},
  "nodes": {
    "VyKDGurkQiygV-of4B1ZAQ": {
      "name": "Siege",
      "transport_address": "127.0.0.1:9300",
      "attributes": {}
    }
  },
  "metadata": {
    "cluster_uuid": "QMSq7EOfToS-v5dKc0GdUA",
    "templates": {
      "template_one": {
        "template": "template_one_*",
        "order": 0,
        "settings": { ... },
        "mappings": { ... }
      },
      "template_two": { ... },
      "template_three": { ... }
    },
    "indices": {
      "index_one": {
        "state": "open",
        "settings": { ... },
        "mappings": { ... },
        "aliases": [ ... ]
      },
      "index_2": { ... },
      "index_3": { ... },
      "index_4": { ... },
      "index_5": { ... }
    }
  },
  "routing_table": {
    "indices": {
      "index_one": {
        "shards": {
          "0": [
            {
              "state": "STARTED",
              "primary": true,
              "node": "VyKDGurkQiygV-of4B1ZAQ",
              "relocating_node": null,
              "shard": 0,
              "index": "index_one",
              "version": 18,
              "allocation_id": {
                "id": "hAZf59wDSNS7im1wOdToHA"
              }
            }
          ]
        }
      },
      "index_two": { ... },
      "index_three": { ... },
      "index_four": { ... },
      "index_five": { ... } 
    }
  },
  "routing_nodes": {
    "unassigned": [
      {
        "state": "UNASSIGNED",
        "primary": true,
        "node": null,
        "relocating_node": null,
        "shard": 0,
        "index": "index_three",
        "version": 0,
        "unassigned_info": {
          "reason": "CLUSTER_RECOVERED",
          "at": "2017-02-08T18:46:11.027Z"
        }
      },
      { ... }
    ],
    "nodes": {
      "VyKDGurkQiygV-of4B1ZAQ": [
        {
          "state": "STARTED",
          "primary": true,
          "node": "VyKDGurkQiygV-of4B1ZAQ",
          "relocating_node": null,
          "shard": 0,
          "index": "index_one",
          "version": 18,
          "allocation_id": {
            "id": "hAZf59wDSNS7im1wOdToHA"
          }
        },
        { ... },
        { ... }
      ]
    }
  }
}

默认情况下对集群状态接口的请求会路由到master节点,以便能返回最新的集群状态信息。为了调试,可以通过在查询中增加设置local=true参数,来获取某个特定节点的集群状态信息。

由于集群状态会增长(取决于分片、索引、mapping以及模板的数量),所以可以通过URL来过滤集群状态信息,只返回指定部分。

metrics是一个由逗号分隔的下列属性组成的列表:

  • version – 表示返回集群状态版本。
  • master_node – Shows the elected master_node part of the response表示返回被选举的master_node部分。
  • nodes – 表示返回nodes部分。
  • routing_table – 表示返回routing_table部分。如果设置了逗号分隔的索引库列表,则只会返回指定索引库的部分。
  • metadata – 表示返回metadata部分。如果设置了逗号分隔的索引库列表,则只会返回指定索引库的部分。
  • blocks – 表示返回blocks部分。

为了确定主分片数量,可以对集群状态、消息和容量做出如下规划:

  • 使用生产环境的硬件配置,在单台服务器上创建一个集群。
  • 创建一个与生产环境配置和分词器一样的索引库,只设置一个主分片,不设置副本。
  • 写入真实的文档数据(或者能拿到的与真实环境最接近的)。
  • 执行真实的查询和聚合(或者能拿到的与真实环境最接近的)。

一旦定义好了单个分片的容量,就能很容易推算出整个索引所需的分片数量:使用需要索引的所有数据量,加上考虑对未来增长的扩展容量,再除以单个分片的容量。

2Elasticsearch集群的内部原理(拓扑结构)

Elasticsearch提供了一个非常大的工具箱用于规划复杂的集群拓扑结构。通过使用节点属性和分片分配过滤策略,可以使用存储热点索引的健壮节点,和存储历史数据的廉价节点,一起来组成多样化的集群。集群中的不同节点有着不同的角色(data 和/或 master ,或者两者之外的client节点)和不同的属性(比如zone)。在专用节点中区分masterdata节点的一个好处是,只需设置3个master候选节点,并把参数minimum_master_nodes设置为2。从来不需要修改这个设置,无论往集群添加了多少个专用节点。设置合理的集群拓扑结构有助于规避脑裂之类的问题。

1.候选主节点

master节点主责一些集群级别的轻量操作,比如创建和删除索引,跟踪集群中有哪些节点,以及觉得哪些分片分配到哪些节点。一个稳定的master节点对集群健康非常重要。Master节点必须可以访问data/ 目录(就像data节点一样),因为该目录存放了节点重启期间集群状态的持久化数据。

创建一个单独的master候选节点:

node.master: true 
node.data: false 
node.ingest: false
  • 开启默认的node.master角色。
  • 禁用node.data角色(默认开启)。
  • 禁用node.ingest角色(默认开启)。

2.数据节点

Data节点存放了包含已索引文档的所有分片,它负责处理跟数据相关的CRUD、检索以及聚合等一系列操作,这些操作可能是I/O密集、内存密集或CPU计算密集的。因此监控这些资源非常重要,可以在过载时增加数据节点的对应资源。设置专用数据节点的主要好处是分离了masterdata角色。

创建一个专用data节点:

node.master: false 
node.data: true 
node.ingest: false
  • 禁用node.master角色(默认开启)。
  • 开启默认的node.data角色。
  • 禁用node.ingest角色(默认开启)。

3.Ingest节点

Ingest节点可以执行由一个或多个ingest处理器组成的预处理pipeline。根据ingest处理器执行的操作类型的不同,以及所需资源,可以适当地设置专用ingest节点,该节点将只执行这一特定任务。

创建一个专用的ingest节点:

node.master: false 
node.data: false 
node.ingest: true
  • 禁用node.master角色(默认开启)。
  • 禁用node.data角色(默认开启)。
  • 开启默认的node.ingest角色。

4.专用协调节点

如果考虑除了处理master职责、存储数据以及预处理文档之外的能力,那么我们还剩下一个协调节点,只负责路由请求、处理检索的reduce步骤,以及分发批量索引请求。本质上,专用协调节点就像一个智能负载均衡器。

创建一个专用协调节点:

node.master: false 
node.data: false 
node.ingest: false
  • 禁用node.master角色(默认开启)。
  • 禁用node.data角色(默认开启)。
  • 禁用node.ingest角色(默认开启)。

3开启内存锁检查以禁止发生交换

内存交换就是把内存中的某一页拷贝到预先配置好的叫做交换空间的磁盘上,以便从内存释放该页。物理内存加上交换空间一起就是可用的虚拟内存大小。

交换对于性能和节点稳定性来说都很糟糕的,应该尽可能地避免。它可能引发持续数分钟而不是几毫秒的GC,还可能导致节点响应过慢甚至从集群失联。磁盘与内存相比读写都非常慢,内存速度可以用纳秒来衡量,而磁盘是用毫秒,因此访问磁盘比访问物理内存要慢数万倍。交换发生越多,处理就会越慢,所以应当尽可能地规避交换的发生。

JVM发生一次major GC时,可能会影响到堆内存中的每一页。如果所有页都被交换到磁盘,那接下来它们一定会再次被交换回内存。这会导致大量的磁盘振荡,而Elasticsearch更需要使用这些资源来处理服务请求。禁止交换的方式之一,是通过Elasticsearch的bootstrap.mlockall参数来要求JVM通过mlockall设置堆内存锁。但是,Elasticsearch某些情况下设置锁也会失败(比如elasticsearch用户没有memlock限制)。内存锁检查可以用来验证bootstrap.mlockall设置是否开启,或者确认JVM是否能成功设置堆内存锁。

有三种方式来禁用交换:

1.打开bootstrap.mlockall

通过Elasticsearchmlockall属性配置,可以要求节点不发生内存交换(注意只在Linux/Unix系统上有效),在config/elasticsearch.yml文件中增加下列配置项即可实现:

bootstrap.mlockall: true

在5.x版本中已经改为:

bootstrap.memory_lock: true.

mlockall默认设置为false,即节点默认允许发生交换。一旦在配置文件中设置了该值,需要重启Elasticsearch节点使之生效。重启后,可以通过下列请求的返回信息来查看是否成功生效:

curl -XGET localhost:9200/_nodes?
filter_path=**.mlockall

其应该会返回如下信息:

{"nodes":{"VyKDGurkQiygV-of4B1ZAQ":{"process":{"mlockall":true}}}}

如果看到mlockallfalse,则表示mlockall请求失败,同时也会在日志文件中看到类似“不能锁定JVM内存”的字样;在Linux/Unix系统上最常见的原因是,运行Elasticsearch的用户可能没有权限锁定内存,这可以通过如下方式授权:

  • 启动Elasticsearch前,使用root用户设置ulimit -l unlimited,或者在/etc/security/limitx.conf配置文件中设置memlockunlimited
  • 如果使用RPM或者Debian系统,可在系统配置文件(或者参考下文使用systemd)中设置MAX_LOCKED_MEMORY为unlimited
  • 如果使用支持systemd的系统,可在systemd配置文件中设置LimitMEMELOCK为无限大。

如果设置了mlocklall属性,必须保证通过-DXmx选项或者ES_HEAP_SIZE为Elasticsearch节点设置了足够大的内存。

Elasticsearch安装时默认配置1GB的堆内存大小;如果使用该默认值,集群很可能配置得不正确。修改该值的最简单方法是设置名为ES_HEAP_SIZE的环境变量。让服务器进程启动时,会读取该环境变量并设置相应的堆内存大小。比如,可以通过命令行如下设置:

export ES_HEAP_SIZE=10g

还可以在启动进程时通过JVM选项来设置该值:

确保最小(Xms)和最大值(Xmx)相同可以防止运行时的堆内存重新分配大小,这个开销非常大。一般而言,相比显式设置-Xmx和-Xms,更推荐设置ES_HEAP_SIZE环境变量。

2.禁止所有交换文件

另一方面我们可以完全禁止交换。通常一个JVM上只跑一个Elasticsearch服务,其内存使用由JVM选项决定,那也就没什么必要进行交换了。在Linux系统上,可以通过执行sudo swapoff -a来临时禁止交换;如果想永久禁止,需要编辑/etc/fstab文件并把交换相关的配置行注释起来。

3.配置虚拟内存

Linux系统上的另一个可选项是,确保sysctl的值vm.swappiness设置为1,这能减少内核的交换趋势,从而在正常情况下不会发生交换,同时当故障发生时依然允许整个系统进行交换。

https://cloud.tencent.com/developer/article/1404727

发表评论

电子邮件地址不会被公开。 必填项已用*标注