Elasticsearch原理分析——节点的启动和关闭


文章目录

  • Elasticsearch原理分析——节点的启动和关闭
  • 1. 启动流程做了什么
  • 2. 启动流程分析
  • 2.1 启动脚本
  • 2.2 解析命令行参数和配置文件
  • 2.3 加载安全配置
  • 2.4 检查内部环境
  • 2.5 检查外部环境
  • 2.5.1 堆大小检查
  • 2.5.2 文件描述符检查
  • 2.5.3 内存锁定检查
  • 2.5.4 最大线程数检查
  • 2.5.5 最大虚拟内存检查
  • 2.5.6 最大文件大小检查
  • 2.5.7 虚拟内存区域最大数量检查
  • 2.5.8 OnError与OnOutOfMemoryError检查
  • 2.6 启动内部模块
  • 2.7 启动keepalive线程
  • 3. 节点关闭流程
  • 4. 关闭流程分析
  • 5. 分片读写过程中执行关闭
  • 5.1 写过过程关闭
  • 5.2 读取过程关闭
  • 6. 主节点被关闭
  • 7. 小结
  • 8. 关注我



本章分析单个节点的启动和关闭流程。看看进程是如何

解析配置、检查环境、初始化内部模块的,以及在节点被**“kill”**的时候如何处理的。

1. 启动流程做了什么

总体来说,节点启动流程的任务是做下面几类工作:

  • 解析配置,包括配置文件和命令行参数。
  • 检查外部环境和内部环境,例如,JVM版本、操作系统内核参数等。
  • 初始化内部资源,创建内部模块,初始化探测器。
  • 启动各个子模块和keepalive线程。

2. 启动流程分析

2.1 启动脚本

当我们通过启动脚本bin/elasticsearch启动ES时,脚本通过exec加载Java程序。代码如下:

exec \ #执行命令
    "$JAVA" \ #Java程序路径
    $ES_JAVA_OPTS \ #JVM选项
    -Des.path.home="$ES_HOME" \ #设置path.home路径
    -Des.path.conf="$ES_PATH_CONF" \ #设置path.conf路径
    -Des.distribution.flavor="$ES_DISTRIBUTION_FLAVOR" \
    -Des.distribution.type="$ES_DISTRIBUTION_TYPE" \
    -cp "$ES_CLASSPATH" \ #设置 java classpath
    org.elasticsearch.bootstrap.Elasticsearch \ #指定main函数所在类
    "$@" #传递给main函数命令行参数

ES_JAVA_OPTS变量保存了JVM参数,其内容来自对config/jvm.options配置文件的解析。

如果执行启动脚本时添加了-d参数:

bin/elasticsearch -d

则启动脚本会在exec中添加<&- &。<&-的作用是关闭标准输入,即进程中的0号fd。&的作用是让进程在后台运行。

2.2 解析命令行参数和配置文件

目前支持的命令行参数有下面几种,默认启动时都不使用,如下表所示:

参数

含义

-E

设定某项配置。例如,设置集群名称:-E “cluster.name=my_cluster”,一般通过配置文件来设置,而不是在命令行设置

-V, --version

打印版本号信息

-d, --daemonize

后台启动

-h, --help

打印帮助信息

-p, --pidfile

启动时在指定路径创建一个pid文件,其中保存了当前进程的pid,之后可以通过查看这个pid文件来关闭进程

-q, --quiet

关闭控制台的标准输出和标准错误输出

-s, --silent

终端输出最少信息(默认为normal)

-v, --verbose

终端输出详细信息

实际工程应用中建议在启动参数中添加-d和-p,例如:

bin/elasticsearch -d -p es.pid

此处解析的配置文件有下面两个,jvm.options是在启动脚本中解析的。

  • elasticsearch.yml #主要配置文件
  • log4j2.properties #日志配置文件

2.3 加载安全配置

什么是安全配置?本质上是配置信息,既然是配置信息,一般是写到配置文件中的。ES的几个配置文件在之前的章节提到过。此处的“安全配置”是为了解决有些敏感的信息不适合放到配置文件中的,因为配置文件是明文保存的,虽然文件系统有基于用户权限的保护,但这仍然不够。因此ES把这些敏感配置信息加密,单独放到一个文件中:config/elasticsearch.keystore。然后提供一些命令来查看、添加和删除配置。

哪种配置信息适合放到安全配置文件中?例如,X-Pack中的security相关配置,LDAP的base_dn等信息(相当于登录服务器的用户名和密码)。

2.4 检查内部环境

内部环境指ES软件包本身的完整性和正确性。包括:

  • 检查Lucene版本,ES各版本对使用Lucene版本是有要求的,在这里检查Lucene版本以防止有人替换不兼容的jar包。
  • 检查jar冲突,发现冲突则退出进程。

2.5 检查外部环境

ES中的“节点”在实现时被封装为Node模块。在Node类中调用其他内部组件,同时对外提供启动和关闭方法,对外部环境的检查就是在Node.start()中进行的。

外部环境指运行时的JVM、操作系统相关参数,这些在ES中称为“Boostrap Check”。在早期的ES版本中,ES检测到一些不合理的配置会记录到日志中继续运行。但是有时候用户会错过这些日志。为了避免后期才发现问题,ES在启动阶段对那些很重要的参数做检查,一些影响性能的配置会被标记为错误,让用户足够重视这些参数。

所有这些检测被单独封装在BoostrapChecks类中。目前有下面这些检测项:

2.5.1 堆大小检查

如果JVM初始堆大小(Xms) 与最大堆大小(Xmx)的值不同,则使用期间 JVM 堆大小调整时可能会出现停顿。因此应该设置为相同值。

如果开启了 bootstrap.mempry_block,则 JVM 将在启动时锁定对的初始大小。如果初始堆大小与最大堆大小不同,那么在堆大小发生变化后,可能无法保证所有 JVM 堆都锁定在内存中。

要通过本项检查,就必须配置堆大小。

2.5.2 文件描述符检查

UNIX架构的系统中,“文件”可以是普通的物理文件,也可以是虚拟文件,网络套接字也是文件描述符。ES进程需要非常多的文件描述符。例如,每个分片有很多段,每个段都有很多文件。同时包括许多与其他节点的网络连接等。

要通过此项检查,就需要调整系统的默认配置。在Linux下,执行 ulimit -n 65536(只对当前终端生效),或者在**/etc/security/limits.conf*文件中配置 - nofile 65536”(所有用户永久生效)。Ubuntu下limits.conf默认被忽略,需要开启pam_limits.so模块。

由于Ubuntu版本更新比较快,而生产环境不适合频繁更新,因此我们推荐使用CentOS作为服务器操作系统。

* soft nofile 131072
* hard nofile 131072
2.5.3 内存锁定检查

ES允许进程只使用物理内存,避免使用交换分区。实际上,我们建议生产环境中直接禁用操作系统的交换分区。现在已经部署因为内存不足而需要交换到硬盘上的时代,对于服务器来说,当内存真的用完时,交换到硬盘上会引起更多问题。

开启bootstrap.memory_lock选项来让ES锁定内存,在开启本项检测,而锁定失败的情况下,本项检查执行失败。

2.5.4 最大线程数检查

ES将请求分解为对个节点执行,每个阶段使用不同的线程池来执行。因此ES进程需要创建很多线程,本项检查就是确保ES进程有创建足够多线程的权限。本项检查只对Linux系统进行。你需要调节进程可以创建的最大线程数,这个值至少是2048。

要通过这项检查,可以修改**/etc/security/limits.conf文件的nproc**来完成配置

* soft nproc 131072
* hard nproc 131072

/etc/security/limits.d/90-nproc.conf

* soft nproc 131072
* hard nproc 131072
2.5.5 最大虚拟内存检查

Lucene使用mmap来映射部分索引到进程地址空间,最大虚拟内存检查确保ES进程拥有足够多的地址空间,这项检查只对Linux执行。

要通过这项检查,可以修改**/etc/security/limits.conf文件,设置asunlimited**。

* soft as unlimited
* hard as unlimited
2.5.6 最大文件大小检查

段文件和事务日志文件存储在本地磁盘中,它们可能会非常大,在有最大文件大小限制的操作系统中,可能会导致写入失败。建议将最大文件的大小设置为无限。

要通过这项检查,可以修改**/etc/security/limits.conf文件,修改fsizeunlimited**。

* soft fsize unlimited
* hard fsize unlimited
2.5.7 虚拟内存区域最大数量检查

ES进程需要创建很多内存映射区,本项检查是要确保内核允许创建至少262144个内存映射区。该检查只对Linux执行。

要通过这项检查,可以执行下面命令(临时生效,重启后失效):

sysctl -w vm.max_map_count=262144

或者在**/etc/sysctl.conf文件中添加一行vm.max_map_count=262144**,然后执行下面的命令(立即,且永久生效):

vm.max_map_count=262144
sysctl -p
2.5.8 OnError与OnOutOfMemoryError检查

如果JVM遇到致命错误(OnError)或(OnOutOfMemoryError),那么JVM选项OnError和OnOutOfMemoryError可以执行任意命令。

但是,默认情况下,ES的系统调用过滤器是启用的(seccomp),fork会被阻止。因此,使用OnError或OnOutOfMemoryError和系统调用过滤器不兼容。

若要通过此项检查,则不要启用OnError或OnOutOfMemoryError,而是升级到Java 8u92,并使用ExitOnOutOfMemoryError。

防止es节点内存溢出后处于僵死状态且无法恢复,影响整个集群,在进程出现OOM时让进程宕掉,退出ES集群并引发告警,然后重启。
在config/jvm.options中增加JVM启动参数:

-XX:+ExitOnOutOfMemoryError

2.6 启动内部模块

环境检查完毕,开始启动各子模块。子模块在Node类中创建,启动它们时调用各自的start()方法,例如:

  • discovery.start();
  • clusterServer.start();
  • nodeConnectionsService.start();

子模块的start方法基本就是初始化内部数据、创建线程池、启动线程池等操作。

2.7 启动keepalive线程

调用keepAliveThread.start()方法启动keepalive线程,线程本身不做具体的工作。主线程执行完启动流程后会退出,keepalive线程是唯一的用户线程,作用是保持进程运行。在Java程序中,只是要有一个用户线程。当yoghurt线程数为零时退出进程。

3. 节点关闭流程

现在我们探讨一下单个节点的关闭流程。设想当我们为ES集群更新配置、升级版本时,需要通过 “kill” ES 进程来关闭节点。但是kill操作是否安全?如果此时节点有正在执行的读写操作会有什么影响?如果节点时Master该如何处理?关闭流程是怎么实现的?kill节点都会带来哪些风险?

答案是:ES进程会捕获SIGTERM信号(kill命令默认信号)进行处理,调用个模块的stop方法,让它们有机会停止服务,安全退出。

  1. 主节点被关闭
    集群重启期间,如果主节点被关闭,则集群会重新选主,在这期间,集群有一个短暂的无主状态。如果集群中的主节点是单独部署的,则新主当选后,可以跳过gateway和recovery过程,否则新主需要重新分配旧主所持有的分片:提升其他副本为主分片,以及分配新的副本分片。
  2. 数据节点被关闭
    如果数据节点被关闭,则读写请求的TCP连接也会因此关闭,对客户的来说写操作执行失败。但是写流程已经到达Engine环节的会正常写完,只是客户端无法感知结果。此时客户端重试,如果使用自动生成ID,则数据内容会重复。

综合来说,滚动升级产生的影响是中断当前写请求,以及主节点重启可能引起的分片分配过程。提升新的主分片一般都比较快,因此对集群的写入可用性影响不大。

当索引部分主分片未分配时,使用自动生成ID的情况下,如果持续写入,则客户端对失败重试可能会成功(请求到达已分配成功的主分片),但是会在不同的分片之间产生数据倾斜,倾斜程度视期间数量而定。

4. 关闭流程分析

在节点启动过程中,Bootstrap#setup方法中添加了shutdown hook,当进程收到系统SIGTERM(kill默认信号)或SIGINT信号时,调用节点关闭流程。

每个模块的Service中都是些了doStop和doClose,用于处理这个模块的正常关闭流程。节点总的关闭流程位于Node#cloase,在close方法的实现中,先调用一遍各个模块的doStop,然后再次遍历各个模块执行doClose。主要实现代码如下:

if(lifecycle.started()){
    stop();//调用各个模块的dostop方法
}
List<Closeable> toClose = new ArrayList<>();
//在toClose中添加所需要关闭的Service,以nodeService为例
toClose.add(nodeService);
......
//调用各模块doClose方法
IOUtils.close(toClose);

各个模块的关闭有一定的顺序关系,以doStop为例,按下表所示的顺序调用各个模块的doStop方法。

服务

简介

ResourceWatcherService

通用资源监视服务

HttpServerTransport

HTTP传输服务,提供REST接口服务

SnapshotsService

快照服务

SnapshotShardsService

负责启动和停止shard级快照

IndicesClusterStateService

收到集群状态信息后,处理其中索引相关操作

Discovery

集群拓扑管理

RoutingService

处理reroute(节点之间迁移shard)

ClusterService

集群管理服务。主要处理集群任务,发布集群状态

NodeConnectionsService

节点连接管路服务

MonitorService

提供进程级、系统级、文件系统和JVM的监控服务

GatewayService

负责集群元数据持久化与恢复

SearchService

处理搜索请求

TransportService

底层传输服务

plugins

当前的所有插件

IndicesService

负责创建、删除索引等索引操作

综合来看,关闭顺序大致如下:

  • 关闭快照和HTTPServer,不再响应用户REST请求。
  • 关闭机器拓扑管理,不再响应ping请求。
  • 关闭网络模块,让节点离线。
  • 执行各个插件的关闭流程。
  • 关闭IndicesService。

最后才关闭IndicesService,是因为这期间需要等待释放的资源最多,时间最长。

5. 分片读写过程中执行关闭

下面分别对读和写执行过程中关闭节点进行分析。

5.1 写过过程关闭

线程在写入数据时,会对Engine(引擎)加写锁。IndicesService的doStop方法对本节点上全部索引并执行removeIndex,当执行到Engine的flushAndClose(先flush然后关闭Engine),也会对Engine加写锁。由于写入操作已经加了写锁,此时写锁会等待,直到写入完毕。因此数据写入过程不会被中断。但是由于网络模块被关闭,客户端的连接会被断开。客户端应当为失败处理,虽然ES服务端的写流程还在继续。

5.2 读取过程关闭

线程在读取数据时,会对Engine加读锁。flushAndClose时的写锁会等待读取过程执行完毕。但是由于连接被关闭,无法发送给客户端,导致客户端读失败。

下图展示了Engine的flushAndClose的过程:

es 关闭9200 es 关闭分析_虚拟内存

节点关闭过程中,IndicesService的doStop对Engine设置了超时,如果flushAnd一直等待,则CountDownLatch.await默认1天才会继续后面的流程。

6. 主节点被关闭

主节点关闭时,没有想象中的特殊处理,节点正常执行关闭流程,当TransportSerice模块被关闭后,集群重新选举新的Master。因此。滚动重启期间会有一段时间处于无主状态。

7. 小结

  1. 总体来说,节点启动流程做的是初始化和检查工作,各个子模块启动后异步的工作,加载本地数据,或者选主、加入集群等,在后面的章节中单独介绍。
  2. 节点在关闭时有机会处理未写完的数据,但是写完后可能来不及通知客户端。包括线程池中尚未执行的任务,在一定的超时时间内都有机会执行完。

集群健康从Red变为Green的时间主要消耗在维护主副分片的一致性上。我们也可以选择在集群健康为Yellow时就允许客户端写入,但是会牺牲一些数据安全性。

/etc/security/limits.conf
文件描述符配置
* soft nofile 131072
* hard nofile 131072


最大线程数检查
* soft nproc 131072
* hard nproc 131072
/etc/security/limits.d/90-nproc.conf
* soft nproc 1024

最大虚拟内存检查
* soft as unlimited
* hard as unlimited
最大文件大小检查
* soft fsize unlimited
* hard fsize unlimited

虚拟内存区域最大数量检查
/etc/sysctl.conf
vm.max_map_count=262144
件描述符配置
* soft nofile 131072
* hard nofile 131072


最大线程数检查
* soft nproc 131072
* hard nproc 131072
/etc/security/limits.d/90-nproc.conf
* soft nproc 1024

最大虚拟内存检查
* soft as unlimited
* hard as unlimited
最大文件大小检查
* soft fsize unlimited
* hard fsize unlimited

虚拟内存区域最大数量检查
/etc/sysctl.conf
vm.max_map_count=262144