003.Elasticsearch-6.6.0生产环境集群部署指南

1. 硬件配置和集群规划

1.1 内存

ES是很消耗内存的,消耗的不是JVM的内存,一般来说ES用的JVM Heap还是比较少的,主要是使用机器的物理内存,ES底层基于Lucene,Lucene是基于磁盘文件来读写和保存索引数据的,包括倒排索引、正排索引等,Lucene的特点就是基于OS FileSystem Cache,尽量将频繁访问的磁盘数据在操作系统的内存中进行缓存,以此来提升磁盘文件读写的性能,ES的性能80%取决于在分配完JVM Heap之后剩下的服务器物理内存,这些系统内存将用做ES的磁盘索引文件的缓存,如果OS Cache能够缓存更多的磁盘文件的数据,索引文件的数据,那么索引读写的性能会高很多,特别是检索的性能。

如果大量的索引文件无法缓存在OS Cache中,那么搜索、聚合的时候大量的请求都是读写磁盘文件,性能当然是上不去的,检索和聚合操作的时间数据级可能有ms级变为s级,假如使用ES基于千万级别的数据量做搜索,要耗费10s+,那大概率就是因为大量请求读写磁盘了。

ES的排序和聚合都会消耗很多内存,所以给ES进程分配足够的JVM Heap内存是很重要的,除此之外,还要给足够的内存给OS FileSystem Cache。一般而言,除非业务的数据量很小,比如一些信息管理系统,要做一个内部的检索引擎,数据量最多百万级,那对机器配置的要求还是很低的,而如果,业务数据量过亿,甚至达到百亿级别,那么单台ES节点的物理内存应该为64GB。64G的内存,是比较理想的状态,根据实际的数据量来说,有时32GB和16GB的内存也是可以的,但生产环境很少使用小于8G的内存,另外由于我们会使用多个ES节点来搭建集群,所以每个ES节点的内存也没有必要大于64GB。

1.2 CPU

大多数的ES集群对于CPU的要求都会比较低一些,因此一台机器有多少个CPU Core其实对生产环境的ES集群部署相对来说没有那么的重要了,至少没有内存那么重要。当然,肯定是要用多核处理器的,一般来说2个CPU Core-8个Cpu Core都是可以的。

此外,如果要选择是较少的CPU Core但是CPU性能很高,还是较多的CPU Core但是CPU性能较为一般,那么肯定是选择性能较为一般但是更多的CPU Core,因为更多的CPU Core可以提供更强的并发处理能力,远比单个CPU 性能高带来的效果更加明显。

1.3 磁盘

对于ES的生产环境来说,磁盘是非常重要的,尤其是对那些大量写入的ES集群,比如互联网公司将每天的实时日志数据以高并发的速度写入ES集群。在服务器上,磁盘是最慢的那个资源,所以对于大量写入的ES集群来说,会很容易因为磁盘的读写性能造成整个集群的性能瓶颈。

如果我们能够使用SSD固态硬盘,那么当然是最好的,SSD的性能比机械硬盘可以高很多倍,可以让ES的读写性能都高很多倍。所以,如果公司的经济条件允许,使用固态硬盘当然是最好的。

使用SSD硬盘,那么需要检查I/O scheduler,需要正确的配置IO scheduler,将数据写入磁盘时,IO scheduler会决定什么时候数据才会真正的写入磁盘,即把OS Cache中的数据刷写到磁盘中,大多数机器上,默认的IO scheduler是CFQ,也就是completely fair queuing,这个scheduler会给每个进程都分配一些时间分片(Time slice),然后会优化每个进程的数据如何写入磁盘中,优化的思路主要是根据磁盘的物理布局来决定如何将数据写入磁盘,进而提升写入磁盘的性能,这是针对机械硬盘做出的优化,因为机械硬盘是一种旋转存储介质,是通过机械旋转磁盘+磁头进行磁盘读写的机制,但是这种默认的scheduler执行机制,对于SSD来说是不太高效的,因为SSD不涉及到机械磁盘旋转和磁头读取这种传统的读写机制,对于SSD来说,应该用deadline/noop scheduler,deadline scheduler会基于写操作被pending(等待)了多长时间来进行写磁盘优化,而noop scheduler就是一个简单的FIFO队列先进先出的机制,调整IO Scheduler可以带来很大的性能提升,甚至可以达到数百倍。

如果没有办法使用SSD,只能使用机械硬盘,那么至少得尽量正确读写速度最快的磁盘,比如高性能的服务器磁盘,此外,无论是对于机械硬盘还是SSD,使用RAID 0也是一种提升磁盘读写速度的高效的方式。RAID 0也被称之为条带式(Striping)存储机制,在RAID各种级别中性能是最高的。RAID 0的基本原理,是把连续的数据分散存储到多个磁盘上进行读写,也就是对数据进行条带式存储。这样系统的磁盘读写请求就可以被分散到多个磁盘上并行执行。但是没有必要使用镜像或者RAID的其他模式,因为我们不需要通过RAID来实现数据高可用存储,es的replica副本机制本身已经实现了数据高可用存储。

最后,我们要避免跟网络相关的存储模式(NAS, network-attached storage),比如基于网络的分布式存储模式。虽然很多供应商都说他们的NAS解决方案性能非常高,而且比本地存储的可靠性更高。但是实际上用起来会有很多性能和可靠性上的风险,一般因为网络传输会造成较高的延时,同时还有单点故障的风险。

1.4 网络

对于ES这种分布式系统来说,快速而且可靠的网络是非常的重要的。因为高速网络通信可以让ES的节点间通信达到低延时的效果,高带宽可以让shard的移动和恢复,以及分配等操作更加的快速。现代的数据中心的网络对于大多数的集群来说,性能都足够高了。比如千兆网卡,这都是可以的。

但是要避免一个集群横跨多个数据中心,比如异地多机房部署一个集群,因为那样的话跨机房,跨地域的传输会导致网络通信和数据传输性能较差。ES集群是一种P2P模式的分布式系统架构,而不是Master-Slave主从分布式系统。在ES集群中,所有的node都是相等的,任意两个node间的互相通信都是很频繁和正常的。因此如果部署在异地多机房,那么可能会导致node间频繁跨地域进行通信,通信延时会非常高,甚至造成集群运行频繁不正常。

就跟NAS存储模式一样,很多供应商都说跨地域的多数据中心是非常可靠的,而且低延时的。一般来说,可能的确是这样,但是一旦发生了网络故障,那么集群就崩溃了。通常来说,跨地域多机房部署一个ES集群带来的效益,远远低于维护这样的集群所带来的额外成本。

1.5 单台超级服务器 VS 少量中等配置服务器 VS 大量低配服务器

一般来说,对于ES集群而言,是建议少量的中等性能的服务器而不是使用多台低性能服务器,因为对于运维和管理来说,管理5个物理机组成的ES集群,远远比管理100个虚拟机组成的ES集群要简单的多。但要避免使用那种超大资源量的超级服务器,这样可能造成资源无法完全利用,在一个物理机上部署多个ES节点,导致集群的运维更加复杂。

1.6 JVM

建议使用最新的JDK版本,除非ES明确说明要用哪个JDK版本。ES和Lucene都是一种满足特殊需求的软件,在Lucene的单元测试和集成测试中,经常会发现JVM自身的一些Bug。这些Bug涵盖的范围很广,因此尽量用最新的JDK版本,bug会少一些。就ES 5.x版本而言,建议用JDK8,而不是JDK7,同时JDK6已经不再被支持了。

如果我们用Java编写ES应用程序,而且在使用Transport Client或者Node Client,要确保运行我们的应用程序的JDK版本跟ES服务器运行的JDK版本是一样的。在ES中,会使用到Java的一些基本功能,例如本地序列化机制、IP地址,异常信息等等,而JDK7可能在不同的minor版本之间修改序列化格式,所以如果Client和Server的JDK7版本不一致,可能有序列化的问题。

同时官方推荐,绝对不要随便调整JVM的设置。虽然JVM有几百个配置选项,而且我们可以手动调优JVM,遇到一个性能场景的时候,每个人都会第一时间想到去调优JVM,但是ES官方还是推荐我们不要随便调节JVM参数。因为ES是一个非常复杂的分布式软件系统,ES的默认JVM配置都是基于真实业务场景中长期的实践得到的。随便调节JVM配置反而有可能导致集群性能变得更加差,以及出现一些未知的问题。在很多情况下,将自定义的JVM配置全部删除,性能是保持的最好的。

查看ES版本与操作系统和JVM版本的兼容性:https://www.elastic.co/cn/support/matrix

1.7 容量规划

在规划你的ES集群的时候,一般要规划你需要多少台服务器,每台服务器要有多少资源,能够支撑你预计的多大的数据量。但是这个东西其实不是一概而论的,要视具体的读写场景,包括你执行多么复杂的操作,读写QPS来决定的。不过一般而言,对于很多的中小型公司,都是建议ES集群承载的数据量在10亿规模以内。用最合理的技术做最合理的事情。

这里可以给出几个在国内使用ES的几个场景,ES是做搜索的,当然可以做某个系统的搜索引擎。比如网站或者APP的搜索引擎,或者是某些软件系统的搜索引擎,此外ES还可以用来做数据分析。做网站或者APP的搜索引擎,一般数据量会相对来说大一些,但是通常而言,一个网站或者APP的内容都是有限的,不会无限膨胀,通常数据量从百万级到亿级不等,因此用于搜索的数据都放在ES中是合理的。

一些软件系统或者特殊项目的搜索引擎,根据项目情况不同,数据量也是从百万量级到几十亿,甚至几百亿,或者每日增量几亿,都有可能,那么此时就要根据具体的业务场景来决定了。如果数据量特别大,日增量都几亿规模,那么其实建议不要将每天全量的数据都写入ES中,ES也不适合这种无限规模膨胀的场景。ES是很耗费内存的,无限膨胀的数据量,会导致我们无法提供足够的资源来支撑这么大的数据量。可以考虑是不是将部分热数据,比如最近几天的数据,放到ES中做高频高性能搜索,然后将大量的很少访问的冷数据放大数据系统做离线批量处理,比如Hadoop系统里面。

要让ES达到ms级的响应速度,你必须要有足够的OS Cache去缓存几乎大部分的索引数据,预计一下,你的数据量有多大,需要多少台机器,每台机器要多少资源来支撑才可以达到多大的性能,假如你有100G,你有5台8核64G的服务器,总共大概300G内存,这300G内存,一般要分给ES的JVM Heap一半,就是150G,除了数据本身,ES的存储的时候,还要加入一些额外的信息,所以数据磁盘落地大概是150G,你的系统本身还要使用内存,其他服务可能也要用,所以剩余的150G内存是无法将150G数据都缓存到OS Cache中的,所以不是每条请求都会走内存,所以这样的性能不是最好,根据经验,除非是你的机器的内存资源,完全可以容纳所有的磁盘文件,否则ES的请求响应时间是秒,这样是很正常的。

如果数据量在10亿以内的规模,那么一般而言,如果提供5台以上的机器,每台机器的配置到8核64G的配置,一般而言都能hold住。当然,这个也看具体的使用场景,如果你读写特别频繁,或者查询特别复杂,那么可能还需要更多的机器资源。如果你要承载更大的数据量,那么就相应的提供更多的机器和资源。

要提升你的ES的性能,最重要的,还是说规划合理的数据量,物理内存资源大小。

以上估算是一个极其粗略的估算,真正的生产环境需要更精细的规划。

2. 重要的操作系统设置

理想情况下,Elasticsearch应该独占整个服务器上的资源。

2.1 禁用交换内存

大部分OS都会将磁盘中的热点数据缓存到内存中,假如内存不足,则使用磁盘来临时代替内存,这样可能会导致JVM推内存中的数据甚至整个JVM执行页被交换到磁盘中,这对性能非常不利,应该不惜一切代价避免,它可能导致GC持续数分钟而不是毫秒级别,并且可能导致节点响应缓慢甚至断开与集群的连接。

有三种方法可以禁用Swap,首选项就是完全禁用,如果不能完全禁用,那么是最小化Swap的使用率还是开启ES的内存锁定,就要取决于你得机器的资源使用情况了。

  • 禁用swap

    • 暂时禁用
    sudo swapoff -a
    
    • 永久禁用swap:修改/etc/fstab文件,注释掉所有的swap相关的行
    UUID=24f24210-ba32-46b0-83dd-ae024beb6ced /                       xfs     defaults        0 0
    UUID=3742cade-c78c-430d-83a0-2984e2b56b15 /boot                   xfs     defaults        0 0
    # UUID=e205fcaf-91dd-4e5c-b2ed-55a816a94743 swap                    swap    defaults        0 0
    
  • 减少swap使用率

    修改/etc/sysctl.conf文件:

    vm.swappiness = 1
    

    此配置设置尽量不使用swap,除非在系统紧急情况下

  • ES开启内存锁定,elasticsearch.yml

    bootstrap.memory_lock: true
    

    此配置生效后,在启动ES集群的时候,会占用在jvm.options文件配置的JVM堆内存大小的内存,无论是否使用,都是长期占用,这样可以避免Elasticsearch使用的JVM堆内存被交换到磁盘上,ES集群启动后,使用以下请求查看此配置是否生效:

    GET _nodes?filter_path=**.mlockall
    

    如果返回false,则代表锁定内存失败,日志中的相关信息为Unable to lock JVM Memory,最常见的原因是由于权限不足而无法锁定内存,解决办法如下(使用其中一个即可):

    如果是使用zip或者tar.gz压缩包安装的Elasticsearch,则使用以下任意一个方法:

    • 使用root用户执行ulimit -l unlimited
    • 修改/etc/security/limits.conf文件,将memlock设置为unlimited

    如果使用RPM包安装的Elasticsearch,则使用以下任意一个方法:

    • 编辑/etc/systemd/system/elasticsearch.service.d/override.conf文件,没有的话直接创建,添加内容如下:

      [Service]
      LimitMEMLOCK=infinity
      
    • 或者直接使用sudo systemctl edit elasticsearch编辑此文件,同样也是加入上面的内容

    然后执行sudo systemctl daemon-reload命令重新加载配置

    内存无法锁定的另一个原因是,挂载临时目录时使用noexec选项,可以通过指定一个新的临时目录来解决:

    export ES_JAVA_OPTS="$ES_JAVA_OPTS -Djna.tmpdir="
    ./bin/elasticsearch
    

2.2 增加文件描述符的打开个数

确保运行Elasticsearch的用户的文件描述符打开数的上限为65536或更高。

  • 以tar.gz压缩包安装的Elasticsearch,需要使用以下方法增加打开文件描述符的上限:

    • 在启动ES集群之前,使用root用户执行ulimit -n 65536

    • 编辑/etc/security/limits.conf文件,将nofile设置成65536

      *       soft    nofile  65536
      *       hard    nofile  65536
      
  • 以RPM方式安装的Elasticsearch则不使用做任何修改,在安装过程中已经自动做过设置了

2.3 设置虚拟内存

ES使用mmapfs目录来存储index数据,操作系统的默认mmap count限制是很低的,可能会导致OOM,使用以下任意方式解决:

  • 使用root用户执行sysctl -w vm.max_map_count=262144

  • 修改/etc/sysctl.conf文件,加入:

    vm.max_map_count = 262144
    

    重启系统,使用sysctl vm.max_map_count来验证一下数值是否修改成功

对于使用RPM方式安装的ES来说,无需这一步修改,已经自动修改好了

2.4 设置线程数

ES用了很多线程池来应对不同类型的操作,确保ES用户能创建的最大线程数量至少在4096以上,使用root用户执行ulimit -u 4096来临时设置,或者编辑/etc/security/limits.conf文件,添加如下内容:

*       soft    nproc   4096
*       hard    nproc   4096

来永久修改此配置,同样,对于使用RPM方式安装的ES来说,无需这一配置

2.5 DNS缓存设置

默认情况下,Elasticsearch JVM会无限期的缓存正向解析主机名,反向解析主机名则缓存十秒,Elasticsearch启动后覆盖了默认的配置,改为缓存正向解析主机名60s,反向解析主机名缓存时间不变,这些配置适用于大多数环境,如果不是这些默认值,可以通过在jvm.options文件中加入es.networkaddress.cache.ttl=es.networkaddress.cache.negative.ttl=的配置来修改。

2.6 开发模式和生产模式

默认情况下,Elasticsearch假设用户在开发模式下工作,如果以上任何设置的配置都不正确,将在日志文件中写入警告,但是用户仍然可以启动和运行Elasticsearch节点

一旦配置了诸如network.host之类的网络设置,Elasticsearch就会认为用户在生产环境运行节点,并将上述警告升级为异常。 这些异常将按导致Elasticsearch节点无法启动,这是一项重要的安全措施。

3. Elasticsearch集群配置

Elasticsearch有3个配置文件:

  • elasticsearch.yml :Elasticsearch配置文件
  • jvm.options:Elasticsearch JVM配置文件
  • log4j2.properties:Elasticsearch日志配置文件

这些配置文件默认在${ELASTICSEARCH_HOME}/config目录下,可以通过设置环境变量ES_PATH_CONF来修改配置目录的默认位置:

export ES_PATH_CONF=/path/to/my/config

elasticsearch.yml的书写该是包括以下两种:

path:
    data: /var/lib/elasticsearch
    logs: /var/log/elasticsearch
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch

如果一些配置在环境变量中,例如你设置了环境变量ELATICSEARCH_DATA_PATH=/data/es,那么可以在配置文件中读取这些环境变量:

path.data: ${ELATICSEARCH_DATA_PATH}

3.1 Elasticsearch基本配置

3.1.1 ES的默认参数

ES的默认参数是非常好的,适合绝大多数的情况,尤其是一些性能相关的配置。因此刚开始部署一个生产环境下的es集群时,几乎所有的配置参数都可以用默认的设置。有很多的生产环境场景下,都是因为ES集群管理人员自己去调整某些配置,结果导致集群出现了严重的故障,比如MySQL或者Oracle这种关系型数据库,也许是需要非常重的调优,但是ES是真的不用。如果我们现在面临着一些ES的性能问题,通常建议的解决方案是更好的进行数据结构的布局,或者增加更多的节点和机器资源。在ES的性能调优中,真的很少有那种某个参数一调节,直接性能提升上百倍的情况,即使有这种参数,ES官方也早就将其设置为默认的最佳值了。

但是在生产环境中,还是有极少数跟公司和业务相关的配置是需要我们修改的。这些设置都是具体的公司和业务相关联的,是没法预先给予最好的默认配置的。

3.1.2 集群名称和节点名称

默认情况下,ES会启动一个名称为my-application的集群。建议一定要将自己的集群名称重新进行命名,主要是避免公司网络环境中,某个开发人员的测试节点无意中加入你的集群。

此外,每个node启动的时候,ES也会分配一个随机的名称。这个也不适合在生产环境中,因为这会导致我们没法记住每台机器。而且每次重启节点都会随机分配,就导致node名称每次重启都会变化。因此通常我们在生产环境中是需要给每个node都分配一个名称的

cluster.name: elasticsearch
node.name: node01

3.1.3 文件路径

  • 数据目录、日志目录以及插件目录

默认情况下,ES会将log、data等重要文件都放在ES的安装目录中。这有一个问题,就是在进行ES升级的时候,可能会导致这些目录被覆盖掉。导致我们丢失之前安装好的plugin,已有的log,还有已有的数据,以及配置好的配置文件。

所以一般建议在生产环境中,必须将这些重要的文件路径,都重新设置一下,放在ES安装目录之外。path.data用于设置数据文件的目录,path.logs用于设置日志文件的目录,path.data可以指定多个目录,用逗号分隔即可。如果多个目录在不同的磁盘上,那么这就是一个最简单的RAID 0的方式,将数据在本地进行条带化存储了,可以提升整体的磁盘读写性能。ES会自动将数据在多个磁盘的多个目录中条带化存储数据

path.logs: /var/log/elasticsearch
path.data: /var/data/es1,/var/data/es2

在RAID 0的存储级别下,每个磁盘上回存储一部分数据,但是如果一个磁盘故障了,那么可能导致这台机器上的部分数据就丢失了。如果我们的ES是有replica的,那么在其他机器上还是会有一份副本的。如果path.data指定了多个目录,为了尽量减少数据丢失的风险,ES会将某个shard的数据都分配到一个磁盘上去。这就意味着每个shard都仅仅会放在一个磁盘上。ES不会将一个shard的数据条带化存储到多个磁盘上去,因为如果一个磁盘丢失了,会导致多个shard的部分数据丢失。

但是这又引入了性能的问题,如果我们给一个机器添加更多的磁盘来提升单个索引的读写性能,但这个索引在这台机器上的shard仅仅存在于一个磁盘上。因此path.data指定多个目录,仅仅对于你的一台机器上存储了多个index的多个shard时,才会有效果的。因为不同index的shard可能就被存储到不同的磁盘上去,对多个index的不同shard读写可以走不同磁盘,提升了性能。

虽多个data path是一个很有用的功能,但是ES毕竟不是一个专门的RAID软件。如果我们要对RAID存储策略进行更多的配置,提高存储的健壮性以及灵活性,还是要用专门的RAID软件来进行机器的磁盘数据存储,而不是用多个data path策略。

综上所述,多个data path功能在实际的生产环境中,其实是较少使用的

3.1.4 配置文件目录

ES的配置文件默认存放在$ES_HOME/config下,可以在ES的启动命令中来重新设置:

./bin/elasticsearch -Epath.conf=/path/to/my/config/

或者在环境变量中修改默认的配置目录:

export ES_PATH_CONF=/path/to/my/config

3.2 日志配置

ES使用log4j2来记录日志,log4j2可以通过log4j2.properties文件来进行配置,比如下面的这份配置文件:

# ${sys:es.logs.base_path}取path.logs的值
# ${sys:es.logs.cluster_name}取cluster.name的值
# ${sys:es.logs.node_name}取node.name的值

# 设置RollingFile Appender
appender.rolling.type = RollingFile 
appender.rolling.name = rolling
# 日志文件名称,例如/var/log/elasticsearch/production.log
appender.rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}.log 
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] [%node_name]%marker %.-10000m%n
# 滚动日志的名称,例如/var/log/elasticsearch/production-yyyy-MM-dd-i.log
appender.rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}-%d{yyyy-MM-dd}-%i.log.gz 
appender.rolling.policies.type = Policies
# 日志滚动策略依赖于时间
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
# 每天滚动一次日志
appender.rolling.policies.time.interval = 1
# 每个自然天滚动一次,而不是没隔24小时滚动一次
appender.rolling.policies.time.modulate = true 
# 日志滚动策略也依赖于日志文件大小
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
# 文件超过256M就滚动一次
appender.rolling.policies.size.size = 256MB 
# 处理滚动日志的策略
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.fileIndex = nomax
# 删除滚动日志
appender.rolling.strategy.action.type = Delete 
appender.rolling.strategy.action.basepath = ${sys:es.logs.base_path}
# 按照日志名称来删除滚动日志
appender.rolling.strategy.action.condition.type = IfFileName 
# 要删除的日志
appender.rolling.strategy.action.condition.glob = ${sys:es.logs.cluster_name}-* 
# 仅当累积了太多压缩日志时才删除
appender.rolling.strategy.action.condition.nested_condition.type = IfAccumulatedFileSize
# 积压的压缩日志大于2G时删除
appender.rolling.strategy.action.condition.nested_condition.exceeds = 2GB
# 根据最后修改时间来删除日志
appender.rolling.strategy.action.condition.nested_condition.type = IfLastModified
# 超过7天还没有被修改的日志就删除
appender.rolling.strategy.action.condition.nested_condition.age = 7D 

3.3 Zen Discovery集群发现机制和Master相关配置

通常我们在每台机器部署并启动一个ES进程,怎么让多台机器上的多个ES进程,互相发现对方,然后完美的组成一个ES集群呢?

默认情况下,ES进程会绑定在自己的回环地址上,也就是127.0.0.1,然后扫描本机上的9300~9305端口号,尝试跟这些端口上启动的其他ES进程进行通信,然后组成一个集群。这对于在本机上搭建ES集群的开发环境是很方便的。但是对于生产环境下的集群是不行的,需要将每台ES进程绑定在一个非回环的IP地址上,才能跟其他节点进行通信,同时需要使用集群发现机制(discovery)来跟其他节点上的ES node进行通信,同时discovery机制也负责ES集群的Master选举

ES node中有Master Node和Data Node两种角色。

ES 是一种Peer to Peer,也就是p2p点对点的分布式系统架构,不是Hadoop生态普遍采用的那种Master-Slave主从架构的分布式系统。集群中的每个node是直接跟其他节点进行通信的,几乎所有的API操作,比如index,delete,search等都不是Client跟Master通信,而是Client跟任何一个node进行通信,那个node再将请求转发给对应的node来进行执行。

两个角色,Master Node,Data Node。正常情况下,就只有一个Master Node。master node的责任就是负责维护整个集群的状态信息,也就是一些集群元数据信息,同时在新node加入集群或者从集群中下线时,或者是创建或删除了一个索引后,重新分配shard。包括每次集群状态如果有改变的化,那么master都会负责将集群状态同步给所有的node。

Master Node负责接收所有的集群状态变化相关的信息,然后将改变后的最新集群状态推动给集群中所有的Data Node,集群中所有的node都有一份完整的集群状态。只不过Master Node负责维护而已。其他的node,除了master之外的Data Node,就是负责数据的读写。

如果要让多个Node组成一个es集群,首先第一个要设置的参数,就是cluster.name,多个node的cluster.name一样,才满足组成一个集群的基本条件。cluster.name的默认值是my-application,在生产环境中,一定要修改这个值,否则可能会导致未知的node无端加入集群,造成集群运行异常。

而ES中默认的discovery机制,就是zen discovery机制,zen discovery机制提供了unicast discovery集群发现机制,集群发现时的节点间通信是依赖的Transport Module,也就是ES底层的网络通信模块和协议。

ES默认配置是使用unicast集群发现机制,从而让经过特殊配置的节点可以组成一个集群,而不是随便哪个节点都可以组成一个集群。但是默认配置下,unicast是本机,也就是localhost,因此只能在一台机器上启动多个node来组成一个集群。虽然ES还是会提供multicast plugin作为一个发现机制,但是已经不建议在生产环境中使用了。虽然我们可能想要multicast的简单性,就是所有的node可以再接收到一条multicast ping之后就立即自动加入集群。但是multicast机制有很多的问题,而且很脆弱,比如网络有轻微的调整,就可能导致节点无法发现对方。因此现在建议在生产环境中用unicast机制,提供一个ES种子节点作为中转路由节点就可以了。

另外还需要在集群中规划出专门的Master Eligible Node和Data node,一个节点只要它是Master Eligible Node,才有可能被选举为真正的Master Node,选举出真正的Master Node之后,其他的Master Eligible Node,将在Master Node故障之后,通过选举,从中再产生一个新的Master Node,同时,所有的非Master Node,都是Data Node,也就是说,Master Eligible Node只是有机会成为Master Node,只要你不是Master Node,你就是Data Node,而不是Master Eligible Node的Data Node是没有升级成为Master Node的资格的。

如果是一个小集群,10个以内的节点,那就所有节点都可以作为Master Eligible Node以及Data node即可,超过10个node的集群再单独拆分Master Eligible Node和Data Node。

# 设置为Master Eligible Node
node.master: true
# 设置为Data Node
node.data: true

默认情况下,ES会将自己绑定到127.0.0.1上,对于运行一个单节点的开发模式下的ES是ok的。但是为了让节点间可以互相通信以组成一个集群,需要让节点绑定到一个IP地址上

network.host: 192.168.0.1

只要不是本地回环地址,ES就会认为我们从开发模式迁移到生产模式,同时会启用一系列的bootstrap check

  • ping:ping是一个node用discovery机制来发现其他node的一个过程

  • unicast:unicast discovery集群发现机制是要求配置一个主机列表,这些主机作为Gossip通信协议的路由器,如果通过hostname来指定,那么在ping的时候会被解析为IP地址

discovery.zen.ping.unicast.hosts: ["host1", "host2"]

简单来说,如果要让多个节点发现对方并且组成一个集群,那么就得有一个中间的公共节点,当不同的节点发送请求到这些公共节点,通过这些公共节点交换各自的信息,进而让所有的node感知到其他的node存在,并且进行通信,最后组成一个集群。这就是基于gossip流言式通信协议的unicast集群发现机制。

当一个node与discovery.zen.ping.unicast.hosts中的一个成员通信之后,就会接收到一份完整的集群状态,接着这个node再跟master通信,并且加入集群中。这就意味着,discovery.zen.ping.unicast.hosts是不需要列出集群中的所有节点的,只要提供少数几个node,比如3个,让新的node可以连接上即可,如果我们给集群中分配了几个节点作为专门的master节点,那么这里配置那些master节点即可,这个配置中也可以指定端口:

discovery.zen.ping.unicast.hosts: ["host1", "host2:9201"]

为集群选举出一个master是很重要的,ES集群会自动完成这个操作,node.master设置为false的节点是无法称为Master的,discovery.zen.minimum_master_nodes参数用于设置对于一个ES集群来说,必须拥有的最少的正常在线的Master Eligible Node的个数,否则会发生"集群脑裂"现象,假如在集群中设置了3个Master Eligible Node,那么这个值应该为(master_eligible_nodes / 2) + 1,即2

discovery.zen.minimum_master_nodes: 2

集群脑裂

discovery.zen.minimum_master_nodes参数对于集群的可靠性来说,是非常重要的。这个设置可以预防脑裂问题,也就是预防一个集群中存在两个master。

这个参数的作用,就是告诉ES直到有足够的master候选节点时,才可以选举出一个master,否则就不要选举出一个master。这个参数必须被设置为集群中master候选节点的quorum数量,也就是大多数。quorum的算法,就是:master候选节点数量 / 2 + 1,比如我们有10个节点,都能维护数据,也可以是master候选节点,那么quorum就是10 / 2 + 1 = 6,如果我们有三个master候选节点,还有100个数据节点,那么quorum就是3 / 2 + 1 = 2,Elasticsearch要求最少有3个节点,如果我们有2个节点,都可以是master候选节点,那么quorum是2 / 2 + 1 = 2。此时就有问题了,因为如果一个Master挂掉了,那么剩下一个master候选节点,是无法满足quorum数量的,也就无法选举出新的master,集群就彻底挂掉了。此时就只能将这个参数设置为1,如果发生了网络分区,那么两个分区中都会有一个Master,还是无法避免集群脑裂

那么这个是参数是如何避免脑裂问题的产生的呢?比如我们有3个节点,quorum是2,现在网络故障,1个节点在一个网络区域,另外2个节点在另外一个网络区域,不同的网络区域内无法通信。这个时候有两种情况:

  • 如果master是单独的那个节点,另外2个节点是master候选节点,那么此时那个单独的master节点因为没有指定数量的候选master node在自己当前所在的集群内,因此就会取消当前master的角色,尝试重新选举,但是无法选举成功。然后另外一个网络区域内的node因为无法连接到master,就会发起重新选举,因为有两个master候选节点,满足了quorum,因此可以成功选举出一个master。此时集群中就会还是只有一个master。

  • 如果master和另外一个node在一个网络区域内,然后一个node单独在一个网络区域内。那么此时那个单独的node因为连接不上master,会尝试发起选举,但是因为master候选节点数量不到quorum,因此无法选举出master。而另外一个网络区域内,原先的那个master还会继续工作。这也可以保证集群内只有一个master节点。

在ES集群是可以动态增加和下线节点的,所以可能随时会改变quorum。所以这个参数也是可以通过API随时修改的,特别是在节点上线和下线的时候,都需要作出对应的修改。而且一旦修改过后,这个配置就会持久化保存下来。

PUT /_cluster/settings
{
    "persistent" : {
        "discovery.zen.minimum_master_nodes": 2
    }
}

此外还有一些其他的关于集群发现机制相关的配置,以下将列出上述讨论的参数以及一些其他的参数做总结:

#设置集群为single-node模式,这样的话,ES将不会再从外部去发现其他节点,默认不配资,代表可以发现其他节点
discovery.type: single-node
discovery.zen.ping.unicast.hosts: ["host1", "host2"]
#主机名被DNS解析为IP地址的超时时间
discovery.zen.ping.unicast.resolve_timeout: 5s
#在决定开始选举或加入现有集群之前节点将等待多长时间
discovery.zen.ping_timeout: 3s
#节点决定加入现有集群后,向Master发送加入请求,超时时间默认discovery.zen.ping_timeout时间的20倍
discovery.zen.join_timeout: 20
#Master发送修改集群状态的消息给其他节点,如果在此时间内还没有至少discovery.zen.minimum_master_nodes个备用Master节点回复,则此次修改集群状态的动作不会发生
discovery.zen.commit_timeout: 30s
#满足上面的条件后,Master发送修改集群状态的消息给全部Node,然后全部Node开始修改自身的集群状态信息,Master只有会等待所有的Node响应,最多等待此处的时间,然后才会开始下一次状态修改的流程
discovery.zen.publish_timeout: 30s
#设置集群中备用master的quorum,如果集群中备用的Master的个数少于此配置,将引发集群脑裂的现象
discovery.zen.minimum_master_nodes: 2
#选举Master的时候,是否忽略node.master=false的节点发送的ping消息,默认false
discovery.zen.master_election.ignore_non_master_pings: false
#当集群中没有存活的Master后,禁用外部请求允许的操作,write:外部请求只能读,不能写,all:外部请求不能读写ES集群
discovery.zen.no_master_block: write
#Master与Data Node互相发送ping消息的时间间隔
ping_interval: 1s
#Master与Data Node互相发送ping消息超时时间
ping_timeout: 30s
#Master与Data Node互相发送ping消息失败后的重试次数
ping_retries: 3

3.4 Gateway相关配置

在集群重启的时候,有一些配置会影响shard恢复的过程。首先,我们需要理解默认配置下,shard恢复过程会发生什么事情。如果我们有10个node,每个node都有一个shard,可能是primary shard或者replica shard,你有一个index,有5个primary shard,每个primary shard有一个replica shard。如果我们将整个集群关闭了进行一些维护性的操作,比如给机器安装新的磁盘之类的事情。当我们重启集群的时候,肯定节点是一个接一个的启动的,可能会出现5个节点先启动了,然后剩下5个节点还没启动。

也许是因为剩下的5个节点没来得及启动,或者是因为一些原因耽搁了,总之不管是什么原因,就是现在只有5个节点是在线的。这5个节点会通过gossip协议互相通信,选举出一个master,然后组成一个集群。他们会发现数据没有被均匀的分布,因为有5个节点没有启动,那么那5个节点上的shard就是不可用的,集群中就少了一半的shard。此时在线的5个node就会将部分replica shard提升为primary shard,同时为每个primary shard复制足够的replica shard。

最后,剩下的5个节点加入了集群。但是这些节点发现本来是他们持有的shard已经被重新复制并且放在之前的5个node上了,此时他们就会删除自己本地的数据。然后集群又会开始进行shard的rebalance操作,将最早启动的5个node上的shard均匀分布到后来启动的5个node上去。

在这个过程中,这些shard重新复制,移动,删除,再次移动的过程,会大量的耗费网络和磁盘资源。对于数据量庞大的集群来说,可能导致每次集群重启时,都有TB级别的数据无端移动,可能导致集群启动会耗费很长时间。但是如果所有的节点都可以等待整个集群中的所有节点都完全上线之后,所有的数据都有了以后,再决定是否要复制和移动shard,情况就会好很多。

所以现在问题我们已经知道了,那么我们就可以配置一些参数来解决这个问题。首先我们需要设置一个参数,gateway.recover_after_nodes,这个参数可以让ES等待,直到有足够的node都上线之后,再开始shard recovery的过程。所以这个参数是跟具体的集群相关的,要根据我们的集群中节点的数量来决定。此外,还应该设置一个集群中至少要有多少个node,等待这些node的时间:gateway.expected_nodesgateway.recover_after_time

gateway.recover_after_nodes: 3
gateway.expected_nodes: 10
gateway.recover_after_time: 5m

经过上面的配置之后,es集群的行为会变成下面这样,等待至少3个节点在线且最多等待5分钟,或者10个节点都在线,再开始shard recovery的过程。这样就可以避免少数node启动时,就立即开始shard recovery,消耗大量的网络和磁盘资源,甚至可以将shard recovery过程从数小时缩短为数分钟。

以下为Gateway相关配置的总结:

#当集群中存在这么多节点之后,可以立即开始Recovery
gateway.expected_nodes: 0
#当集群中存在这么多Master节点之后,可以立即开始Recovery
gateway.expected_master_nodes: 0
#当集群中存在这么多Data节点之后,可以立即开始Recovery
gateway.expected_data_nodes: 0
#当上述条件达不到的时候,最多等待多长时间,开始Recovery
gateway.recover_after_time: 5m
#在等待前3个条件达到期间,只要发现有这么多节点在线,就开始Recovery
gateway.recover_after_nodes: 3
#在等待前3个条件达到期间,只要发现有这么Master节点在线,就开始Recovery
gateway.recover_after_master_nodes: 0
#在等待前3个条件达到期间,只要发现有这么多Data节点在线,就开始Recovery
gateway.recover_after_data_nodes: 0

3.5 JVM配置

通过以下两种方式来设置JVM堆内存

  • 修改jvm.options文件

    -Xms1g
    -Xmx1g
    
  • 设置环境变量

    ES_JAVA_OPTS="-Xms2g -Xmx2g"
    ES_JAVA_OPTS="-Xms4000m -Xmx4000m"
    

JVM堆内存该设置多大?

  • 最小堆内存和最大堆内存一定要设置成一样的

  • 将机器上少于一半的内存分配给ES的堆内存

    虽然Heap对于ES来说是非常重要的,其中存放很多内存中的数据结构来提供更快的操作性能。但影响ES的性能更大的因素在于系统级别的缓存,其中保存着索引文件数据,所以一定保证有足够的内存用作系统缓存,在一个只有ES进程的服务器上,建议把50%的内存给堆,把50%的内存留给系统缓存,如果不能满足这种条件,宁愿少给JVM Heap也不要少给系统缓存

  • JVM堆内存不要大于32GB,如果Heap小于32G,JVM会用一种技术来压缩对象的指针(object pointer),在Java中,所有的对象都会被分配到Heap中,然后被一个pointer引用。object pointer会指向heap中的对象,引用的是二进制格式的地址

    对于32位的系统来说,JVM最大的heap size就是4G(2^32/1024/1024/1024),对于64位的系统来说,heap size可以更大,但是64位的object pointer会耗费更多的空间,因为object pointer更大了,比浪费更多内存空间更恶劣的是,过大的object pointer会在cpu,朱内存和LLC、L1等多级缓存间移动数据的时候,消耗更多的带宽

    所以JVM用了一种技术,叫做compressed oops来解决object pointer耗费过大空间的问题。这个技术的核心思想是,不要让object pointer引用内存中的二进制地址,而是让object pointer引用object offset。这就意味着32位的pointer可以引用400万个对象,而不是400万字节。这也意味着,使用32位的pointer,最大的heap大小可以到32G。此时只要heap size在32G以内,jvm就会自动启用32位的object pointer,因为32位的对象指针,足够引用32G的内存了,就可以用32位的pointer替代64位的pointer。但是32位的pointer比64位的pointer可以耗费更少的内存耗费。

    如果给jvm heap分配的内存小于32G,此时jvm会自动使用32位的object pointer,同时是让pointer指向对象的offset,32位的object pointer就足以引用32G的内存,同时32位的pointer占用的内存空间很少,对cpu和memory之间移动数据的带宽开销也很少。这个过程就叫做compressed oops。

    但是一旦越过了32G这个界限,就是给jvm heap分配了超过32G的内存,就没有办法用32位的pointer+引用object offset的模式了,因为32位的pointer最多引用32G的内存。不用32位pointer,就只能用64位pointer,才能引用超过32G的内存空间。此时pointer就会退回到传统的object pointer引用对象的二进制地址的模式,此时object pinter的大小会急剧增长,更多的cpu到内存的带宽会被占据,更多的内存被耗费。实际上,不用compressed oops时,你如果给jvm heap分配了一个40~50G的内存的可用空间,object pointer可能都要占据十几G的内存空间,可用的空间量,可能跟使用了compressed oops时的32GB内存的可用空间几乎是一样的。因此,即使我们有很多内存,但是还是要分配给heap在32GB以内,否则的话浪费更多的内存,降低cpu性能,而且会让jvm回收更大的heap。

    设置JVM堆内存的一个核心思想是,一定要保证compressed oops这种存储指针的方式是打开的,可以给jvm option加入-XX:+PrintFlagsFinal,然后在启动后查看日志,观察是否启用compressed oop,如果开启了,可以看到:[env] [Illyana Rasputin] heap size [989.8mb], compressed ordinary object pointers [true],根据实际经验,设置31GB是保险的,而正好设置为32GB是不敢保证一定打开compressed oop的。

    超级计算机的内存如何分配?

    如果我们的机器是一台超级服务器,内存资源甚至达到了1TB,或者512G,128G,首先ES官方是建议避免用这种超级服务器来部署ES集群的,但是如果公司就是任性,那么:

    • 业务是否在做大量的全文检索?是的话,分配4-31G的内存给JVM Heap,然后全部的内存都留给系统缓存
    • 业务是否在做大量的排序或者聚合操作?聚合操作是不是针对数字、日期或者未分词的未分词的字符串?如果是的话,那么还是给ES 4-32G的内存,其他的留给系统缓存,可以将聚合好用的正排索引,doc values放在os cache中
    • 业务是否针对分词的字符串做大量的排序或聚合操作?如果是的话,那么就需要使用fielddata,这就得给jvm heap分配更大的内存空间。此时不建议在服务器上运行一个ES节点,而是在服务器上运行多个节,假如服务器有128G的内存,可以运行两个ES节点,然后每个节点分配31G的堆内存,剩下的留给OS Cache,如果在一台机器上运行多个ES node,建议设置:cluster.routing.allocation.same_shard.host: true,ES的分片要分布在不同的服务器上,这个配置的作用是,假如你在A服务器上启动了3个ES进程,在B服务上启动了3个ES进程,那么这个配置设置为true,就会保证A服务器上的3个进程的副本在都在B服务器上,设置为false的话,就不会进行这样的检查,默认为false,这个配置只有在一台机器上启动了多个ES进程时才有效。但是假如只有一台服务器,那就所有的ES进程都不会有replica副本了。
  • JVM配置只要修改堆内存的大小即可,其他的参数例如GC相关参数尽量不要调,原因如下:

    JVM使用垃圾回收器来释放掉不用的内存,千万不要去调节默认的垃圾回收行为。ES默认用的垃圾回收器是CMS。CMS回收器是并发式的回收器,能够跟应用程序工作线程并发工作,最大程度减少垃圾回收时的服务停顿时间。但是CMS还是会有两个停顿阶段,同时在回收特别大的heap时也会有一些问题。尽管有一些缺点,但是CMS对于要求低延时请求响应的软件来说,还是最佳的垃圾回收器,因此官方的推荐就是使用CMS垃圾回收器。

    有一种最新的垃圾回收器叫做G1。G1回收器可以比CMS提供更少的回收停顿时间,而且能够这对大heap有更好的回收表现。它会将heap划分为多个region,然后自动预测哪个region会有最多可以回收的空间。通过回收那些region,就可以最小化停顿时长,而且可以针对大heap进行回收。

    但是,G1还是比较年轻的一种垃圾回收器,而且经常会发现一些新的bug,这些bug可能会导致jvm挂掉。lucene的测试套件就检查出来了G1的一些bug。因此ES官方不推荐现在使用G1垃圾回收器,也许在不久的未来,等G1更加稳定的时候,可以使用G1。

  • 线程池相关参数也不要调整

    在ES中,默认的threadpool设置是非常合理的,对于所有的threadpool来说,除了搜索的线程池,都是线程数量设置的跟cpu core一样多的。如果我们有8个cpu core,那么就可以并行运行8个线程。那么对于大部分的线程池来说,分配8个线程就是最合理的数量。

    也许我们会觉得有些线程可能会因为磁盘IO等操作block住,所以我们需要更多的线程。但是在ES中这并不是一个问题,大多数的磁盘IO操作都是由Lucene的线程管理的,而不是由ES管理的,因此ES的线程不需要关心这个问题。此外,threadpool还会通过在彼此之间传递任务来协作执行,我们不需要担心某一个网络线程会因为等待一次磁盘写操作,而导致自己被block住,无法处理网络请求。网络线程可以将那个磁盘写操作交给其他线程池去执行,然后自己接着回来处理网络请求。

    其实我们的进程的计算能力是有限的,分配更多的线程只会强迫CPU在多个线程上下文之间频繁来回切换。一个cpu core在同一时间只能运行一条线程,所以如果cpu要切换到另外一个线程去执行,需要将当前的state保存起来,然后加载其他的线程进来执行。如果线程上下文切换发生在一个cpu core内,那么还好一些,但是如果在多个cpu core之间发生线程上下文切换,那么还需要走一个cpu core内部的通信。这种线程上下文切换会消耗掉很多的cpu资源,对于现在的cpu来说,每次线程上下文切换,都会导致30微秒的时间开销,所以宁愿将这些时间花费在任务的处理上。

    也就是说,线程多了之后,CPU进行多个线程上下文切换带来的额外消耗要大于多个线程提供并发处理带来的好处

4. Bootstrap Checks

  1. bootstrap check:ES启动时会进行一些列的检查,确保系统设置和ES的配置是没有问题的,包括以下的检查:
  • development mode vs production mode

    默认情况下,ES绑定到localhost hostname,来进行http和内部通信。这对于简单测试日常的开发,都是非常方便的,但是对于生产环境是不行的。如果要组成一个ES集群,ES实例必须能够通过内部通信协议互相连通,所必须绑定通信到一个外部的接口上。因此如果一个ES实例没有绑定通信到外部接口,那么就认为ES是处于开发模式下。反之,如果绑定通信到外部接口(network.host设置为非本地地址),那么就是处于生产模式下。

    可以通过http.hosttransport.host,单独配置http的传输。这就可以配置一个ES实例通过http可达,但是却不触发生产模式

    有时用户需要将通信绑定到外部,解耦来测试client的调用。对于这种场景,ES提供了single-node恢复模式(将discovery.type设置为single-node),配置过后,一个节点会选举自己作为master,而且不会跟其他任何节点组成集群。

    如果在生产模式下运行一个single node实例,就可以规避掉启动时检查(不绑定到外部接口,或者将通信绑定到外部接口,但是设置discovery typesingle-node)。在这种场景下,可以设置es.enforce.bootstrap.checkstrue(通过jvm参数来设置,-Des.enforce.bootstrap.checks=true或者通过环境变量ES_JAVA_OPTS来设置),来强制bootstrap check的执行

  1. heap size check

    如果jvm启动的时候设置的初始堆大小和最大堆大小不同,可能会导致ES运行期间的服务暂停,因为jvm堆在系统运行期间可能会改变大小。为了避免这种jvm resize导致的ES进程暂停,建议启动jvm时,将初始堆大小和最大堆大小设置的相等。除此之外,如果bootstrap.memory_lock=true,jvm会在启动期间锁定jvm的初始大小。

    如果要通过heap size check,就必须合理设置heap size。默认情况下,ES的jvm堆的最小和最大都是1g。如果在生产环境中使用,应该配置合理的heap size确保ES有足够的堆内存可以使用。

  2. File descriptor check

    file descriptor是unix操作系统的一种数据结构,用来track打开的文件。在unix操作系统中,所有东西都是file,ES需要大量的file descriptor,比如说每个shard都由多个segment和其他文件组成,还有跟其他节点之间的网络通信连接。因为ES要使用大量的file descriptor,所以如果file descriptor耗尽的话,可能造成事故,例如数据丢失。尽量给ES的file descriptor提升到65536,甚至更高。

    GET _nodes/stats/process?filter_path=**.max_file_descriptors
    

    可以用上面这个请求检查每个node上的file descriptor数量

    *       soft    nofile  65536
    *       hard    nofile  65536
    
  3. memory lock check

    如果jvm进行一个major gc的话,那么就会涉及到heap中的每一个内存页,此时如果任何一个内存页被swap到了磁盘上,就会导致很多的磁盘读写开销,而这些磁盘读写开销如果节省下来,可以让ES服务更多的请求。有很多方法可以配置系统禁止swap。其中一种方法就是让jvm去lock heap内存在物理内存中,设置bootstrap.memory_lock=true即可

    # 检查mlockall是否开启
    GET _nodes?filter_path=**.mlockall
    
  4. maximum number of thread check

    ES会将每个请求拆分成多个stage,然后将stage分配到不同的线程池中去执行。在ES中有多个线程池来执行不同的任务。所以ES会创建许多的线程。最大线程数量的检查会确保是说,ES实例有权限去创建足够的线程。如果要通过这个检查,必须允许ES进程能够创建超过4096个线程。

    # /etc/security/limits.conf
    *       soft    nproc   4096
    *       hard    nproc   4096
    
  5. maximum size virtual memory check

    ES使用mmap来将索引映射到ES的address space中,这可以让jvm heap外但是内存中的索引数据,可以有非常告诉的读写速度。因此ES需要拥有unlimited address space。最大虚拟内存大小的检查,会要求ES进程有unlimited address space

    # /etc/security/limits.conf
    *       hard    as   unlimited
    
  6. max file size check

    作为各个分片的组成部分的segment文件可能会变得很大(超过几个GB), 如果ES可以创建的文件的最大大小受到限制的系统上,这可能导致写入失败。 因此,最安全的选择是最大文件大小不受限制,这就是max file size check强制检查的内容

    # /etc/security/limits.conf
    *       hard    fsize   unlimited
    
  7. maximum map count check

    要高效使用mmap的话,ES同样要求创建许多memory-mapped area,因此要求linux内核允许进程拥有至少262144个memory-mapped area

    # /etc/sysctl.conf
    vm.max_map_count = 262144
    
  8. client jvm check

    jvm有两种模式,client jvm和server jvm。不同的jvm会用不同的编译器来编译Java源码,client jvm是用于减少startup time和内存占用,server jvm是用于最大化性能。两种jvm之间的性能区别是很明显的。client jvm check会确保ES没有运行在client jvm下。必须使用server jvm模式来启动es,而server jvm是默认的。

  9. use serial collector check

    针对不同的工作负载,jvm提供了不同的垃圾回收器。串行化垃圾回收器对于单cpu机器或者小内存,是很有效的。但是对于ES来说,用串行化垃圾回收器,会成为一场性能上的灾难。因此这个check会确保ES没有被配置使用串行化垃圾回收器。ES默认的就是CMS垃圾回收器。

  10. system call filter check

    Elasticsearch会根据操作系统来安装各种的system call filter, 安装这些system call filter是为了防止执行与fork相关的system call,这是Elasticsearch的防御机制,避免执行任何任意代码,设置bootstrap.system_call_filter=false可以禁止此项检查,后果自负

  11. OnError and OnOutOfMemoryError check

    jvm参数,OnError和OnOutOfMemoryError允许在jvm遇到了fatal error或者是OutOfMemoryErro的时候,执行预定义的命令。但是默认情况下,es system call filter是启用的,这些filter是阻止forking操作的。因此,用OnError和OnOutOfMemroyError和system call filter是不兼容的。这个check会检查,如果启用了system call filter,还设置了这两个jvm option,那么就不能启动。所以不要在jvm option中设置这两个参数。

  12. early-access check

    jdk提供了一些新版本的早期试用版,这些版本不适合用作生产环境。这个check会检查有没有使用jdk的e早期试用版,我们应该用jdk稳定版本,而不是试用版本

  13. G1 GC check

    jdk 8的早期版本中的G1 GC,有已知的问题可能导致索引破损,在JDK 8u40之前的版本都有这个问题。这个check会检查是否使用了那种早期的JDk版本。

5. 优雅的启停ES服务

RPM方式安装的ES:

  • ES服务开机自启动:
sudo chkconfig --add elasticsearch
# 或者
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable elasticsearch.service
  • ES服务启动停止命令:
sudo -i service elasticsearch start
sudo -i service elasticsearch stop
# 或者
sudo systemctl start elasticsearch.service
sudo systemctl stop elasticsearch.service

tar.gz安装的ES:

启动停止命令:

# 启动
./bin/elasticsearch -d -p pid
# 可以在启动时进行配置
./bin/elasticsearch -d -p pid -Ekey=/etc/elasticsearch

# 停止
# 查询ES进程pid方法一
jps | grep Elasticsearch
14542 Elasticsearch
# 查询ES进程pid方法二
$ ./bin/elasticsearch -p /tmp/elasticsearch-pid -d
$ cat /tmp/elasticsearch-pid && echo
15516
# 停止服务
kill -SIGTERM 15516

ES服务异常挂掉的状态码:

JVM internal error              128
Out of memory error             127
Stack overflow error            126
Unknown virtual machine error   125
Serious I/O error               124
Unknown fatal error             1

你可能感兴趣的:(003.Elasticsearch-6.6.0生产环境集群部署指南)