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

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


本章分析单个节点的启动和关闭流程。看看进程是如何 解析配置、检查环境、初始化内部模块的,以及在节点被**“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通用资源监视服务
HttpServerTransportHTTP传输服务,提供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的过程:

在这里插入图片描述
节点关闭过程中,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

8. 关注我

搜索微信公众号:java架构强者之路
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值