概要

本篇继续讲解Elasticsearch集群部署的细节问题

集群重启问题

如果我们的Elasticsearch集群做了一些离线的维护操作时,如扩容磁盘,升级版本等,需要对集群进行启动,节点数较多时,从第一个节点开始启动,到最后一个节点启动完成,耗时可能较长,有时候还可能出现某几个节点因故障无法启动,排查问题、修复故障后才能加入到集群中,此时集群会干什么呢?

假设10个节点的集群,每个节点有1个shard,升级后重启节点,结果有3台节点因故障未能启动,需要耗费时间排查故障,如下图所示:

整个过程步骤如下:

  1. 集群已完成master选举(node6),master发现未加入集群的node1、node2、node3包含的shard丢失,便立即发出shard恢复的指令。

  2. 在线的7台node,将其中一个replica shard升级为primary shard,并且进行为这些primary shard复制足够的replica shard。

  3. 执行shard rebalance操作。

  4. 故障的3台节点已排除,启动成功后加入集群。

  5. 这3台节点发现自己的shard已经在集群中的其他节点上了,便删除本地的shard数据。

  6. master发现新的3台node没有shard数据,重新执行一次shard rebalance操作。

这个过程可以发现,多做了四次IO操作,shard复制,shard首次移动,shard本地删除,shard再次移动,这样凭空造成大量的IO压力,如果数据量是TB级别的,那费时费力不讨好。

出现此类问题的原因是节点启动的间隔时间不能确定,并且节点越多,这个问题越容易出现,如果可以设置集群等待多少个节点启动后,再决定是否对shard进行移动,这样IO压力就能小很多。

针对这个问题,我们有下面几个参数:

  • gateway.recover_after_nodes:集群必须要有多少个节点时,才开始做shard恢复操作。
  • gateway.expected_nodes: 集群应该有多少个节点
  • gateway.recover_after_time: 集群启动后等待的shard恢复时间

如上面的案例,我们可以这样设置:

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

这三个参数的含义:集群总共有10个节点,必须要有8个节点加入集群时,才允许执行shard恢复操作,如果10个节点未全部启动成功,最长的等待时间为5分钟。

这几个参数的值可以根据实际的集群规模来设置,并且只能在elasticsearch.yml文件里设置,没有动态修改的入口。

上面的参数设置合理的情况,集群启动是没有shard移动的现象,这样集群启动的时候就可以由之前的几小时,变成几秒钟。

JVM和Thread Pool设置

一提到JVM的调优,大家都有手痒的感觉,好几百个JVM参数,说不定开启了正确的按钮,从此ES踏上高性能、高吞吐量的道路。现实情况可能是我们想多了,ES的大部分参数是经过反复论证的,基本上不用咱们太操心。

JVM GC

Elasticsearch默认使用的垃圾回收器是CMS。

## GC configuration
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly

CMS回收器是并发式的回收器,能够跟应用程序工作线程并发工作,最大程度减少垃圾回收时的服务停顿时间。

CMS还是会有两个停顿阶段,同时在回收特别大的heap时也会有一些问题。尽管有一些缺点,但是CMS对于要求低延时请求响应的软件来说,还是最佳的垃圾回收器,因此官方的推荐就是使用CMS垃圾回收器。

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

听起来还挺好的,只是G1还是比较年轻的一种垃圾回收器,而且经常会发现一些新的bug,这些bug可能会导致jvm挂掉。稳定起见,暂时不用G1,等G1成熟后ES官方推荐后再用不迟。

线程池

我们开发Java应用系统时,对系统调优的一个常见手段就是调整线程池,但在ES中,默认的threadpool设置是非常合理的,对于所有的threadpool来说,除了搜索的线程池,都是线程数量设置的跟cpu core一样多的。如果我们有8个cpu core,那么就可以并行运行8个线程。那么对于大部分的线程池来说,分配8个线程就是最合理的数量。

搜索会有一个更加大的threadpool,线程数量一般被配置为:cpu core * 3 / 2 + 1。

Elasticsearch的线程池分成两种:接受请求的线程和处理磁盘IO操作的线程,前面那种由ES管理,后一种由Lucene管理,它们之间会进行协作,ES的线程不会因为IO操作而block住,所以ES的线程设置跟CPU核数一样或略大于CPU核数即可。

服务器的计算能力是非常有限的,线程池的数量过大会导致上下文频繁切换,更费资源,如果threadpool大小设置为50,100,甚至500,会导致CPU资源利用率很低,性能反而下降。

只需要记住:用默认的线程池,如果真要修改,以CPU核数为准。

heap内存设置最佳实践

Elasticsearch默认的jvm heap内存大小是2G,如果是研发环境,我会改成512MB,但在生产环境2GB有点少。

在config/jvm.options文件下,可以看到heap的设置:

# Xms represents the initial size of total heap space
# Xmx represents the maximum size of total heap space -Xms2g
-Xmx2g

分配规则

Elasticsearch使用内存主要有两个大户:jvm heap和lucene,前者ES用来存放很多数据结构来提供更快的操作性能,后者使用os cache缓存索引文件,包括倒排索引、正排索引,os cache内存是否充足,直接影响查询检索的性能。

一般的分配规则是:jvm heap占用小于一半的内存,剩下的全归lucene使用。

如果单台机器总内存64GB,那么heap顶格内存分配为32GB,因为32GB内存以下,jvm会使用compressed oops来解决object pointer耗费过大空间的问题,超过32GB后,jvm的compressed oops功能关闭,这样就只能使用64位的object pointer,会耗费更多的空间,过大的object pointer还会在cpu,main memory和LLC、L1等多级缓存间移动数据的时候,吃掉更多的带宽。最终的结果可能是50GB内存的效果和32GB一样,白白浪费了十几GB内存。

这里涉及到jvm的object pointer指针压缩技术,有兴趣可以单独了解一下。

如果单台机器总内存小于64GB,一般heap分配为总内存的一半即可,具体要看预估的数据量是多少。

如果使用超级机器,1TB内存的那种,官网不建议上那么牛逼的机器,建议分配4-32GB内存给heap,其他的全部用来做os cache,这样数据量全部缓存在内存中,不落盘查询,性能杠杠滴。

最佳实践建议

  1. 将heap的最小值和最大值设置为一样大。
  2. elasticsearch jvm heap设置得越大,就有越多的内存用来进行缓存,但是过大的jvm heap可能会导致长时间的gc停顿。
  3. jvm heap size的最大值不要超过物理内存的50%,才能给lucene的file system cache留下足够的内存。
  4. jvm heap size设置不要超过32GB,否则jvm无法启用compressed oops,将对象指针进行压缩,确认日志里有[node-1] heap size [1007.3mb], compressed ordinary object pointers [true] 字样出现。
  5. 最佳实践数据:heap size设置的小于zero-based compressed ooops,也就是26GB,但是有时也可以是30GB。通过-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode开启对应,确认有heap address: 0x00000000e0000000, size: 27648 MB, Compressed Oops mode: 32-bit字样,而不是heap address: 0x00000000f4000000, size: 28672 MB, Compressed Oops with base: 0x00000000f3ff0000字样。

swapping问题

部署Elasticsearch的服务尽可能关闭到swap,如果内存缓存到磁盘上,那查询效率会由微秒级降到毫秒级,会造成性能急剧下降的隐患。

关闭办法:

  1. Linux系统执行 swapoff -a 关闭swap,或在/etc/fstab文件中配置。

  2. elasticsearch.yml中可以设置:bootstrap.mlockall: true 锁住自己的内存不被swap到磁盘上。

使用命令 GET _nodes?filter_path=**.mlockall 可以查看是否开启mlockall

响应信息:

{
"nodes": {
"A1s1uus7TpuDSiT4xFLOoQ": {
"process": {
"mlockall": true
}
}
}
}

Elasticsearch启动的几个问题

  1. root用户启动实例的问题

    如果你用root用户启动Elasticsearch的实例,将得到如下的错误提示:
org.elasticsearch.bootstrap.StartupException: java.lang.RuntimeException: can not run elasticsearch as root
at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:140) ~[elasticsearch-6.3.1.jar:6.3.1]
at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:127) ~[elasticsearch-6.3.1.jar:6.3.1]
at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86) ~[elasticsearch-6.3.1.jar:6.3.1]
at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124) ~[elasticsearch-cli-6.3.1.jar:6.3.1]
at org.elasticsearch.cli.Command.main(Command.java:90) ~[elasticsearch-cli-6.3.1.jar:6.3.1]
at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:93) ~[elasticsearch-6.3.1.jar:6.3.1]
at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:86) ~[elasticsearch-6.3.1.jar:6.3.1]
Caused by: java.lang.RuntimeException: can not run elasticsearch as root
at org.elasticsearch.bootstrap.Bootstrap.initializeNatives(Bootstrap.java:104) ~[elasticsearch-6.3.1.jar:6.3.1]
at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:171) ~[elasticsearch-6.3.1.jar:6.3.1]
at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:326) ~[elasticsearch-6.3.1.jar:6.3.1]
at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:136) ~[elasticsearch-6.3.1.jar:6.3.1]
... 6 more

无它,建立一个用户,专门用来启动Elasticsearch的,如esuser,相应的系统目录和数据存储目录都赋予esuser账户为归属者。

  1. 启动时提示elasticsearch process is too low,并且无法启动成功

完整的提示信息:

max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]
memory locking requested for elasticsearch process but memory is not locked

解决办法:设置系统参数,命令行中的esuser为建立的Linux用户。

[root@elasticsearch01 bin]# vi /etc/security/limits.conf

# 在文件最后添加
esuser hard nofile 65536
esuser soft nofile 65536
esuser soft memlock unlimited
esuser hard memlock unlimited

设置完成后,可以通过命令查看结果:

# 请求命令
GET _nodes/stats/process?filter_path=**.max_file_descriptors # 响应结果
{
"nodes": {
"A1s1uus7TpuDSiT4xFLOoQ": {
"process": {
"max_file_descriptors": 65536
}
}
}
}
  1. 提示vm.max_map_count [65530] is too low错误,无法启动实例

完整的提示信息:

max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

解决办法:添加vm.max_map_count配置项

临时设置:sysctl -w vm.max_map_count=262144

永久修改:修改vim /etc/sysctl.conf文件,添加vm.max_map_count设置

[root@elasticsearch01 bin]# vim /etc/sysctl.conf

# 在文件最后添加
vm.max_map_count=262144 # 执行命令
[root@elasticsearch01 bin]# sysctl -p

Elasticsearch实例启停

实例一般使用后台启动的方式,在ES的bin目录下执行命令:

[esuser@elasticsearch01 bin]$ nohup ./elasticsearch &
[1] 15544
[esuser@elasticsearch01 bin]$ nohup: 忽略输入并把输出追加到"nohup.out"

这个elasticsearch没有stop参数,停止时使用kill pid命令。

[esuser@elasticsearch01 bin]$ jps | grep Elasticsearch
15544 Elasticsearch
[esuser@elasticsearch01 bin]$ kill -SIGTERM 15544

发送一个SIGTERM信号给elasticsearch进程,可以优雅的关闭实例。

小结

本篇接着上篇的内容,讲解了集群重启时要注意的问题,JVM Heap设置的最佳实践,以及Elasticsearch实例启动时常见的问题解决办法,最后是Elasticsearch优雅关闭的命令。

专注Java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区

可以扫左边二维码添加好友,邀请你加入Java架构社区微信群共同探讨技术

Elasticsearch系列---生产集群部署(下)的更多相关文章

  1. Elasticsearch系列---生产集群部署(上)

    概要 本篇开始介绍Elasticsearch生产集群的搭建及相关参数的配置. ES集群的硬件特性 我们从开始编程就接触过各种各样的组件,而每种功能的组件,对硬件要求的特性都不太相同,有的需要很强的CP ...

  2. 1.ElasticSearch系列之集群部署

    第一步:安装JDK JDK要求jdk1.8+,不安装也可以,ES自带JDK 第二步:系统配置 2.1 禁用交换区 sudo swapoff -a 2.2 开最大文件数的限制 编辑文件 /etc/sec ...

  3. Elasticsearch系列---生产集群的索引管理

    概要 索引是我们使用Elasticsearch里最频繁的部分日常的操作都与索引有关,本篇从运维人员的视角,来玩一玩Elasticsearch的索引操作. 基本操作 在运维童鞋的视角里,索引的日常操作除 ...

  4. Kubernetes(k8s)集群部署(k8s企业级Docker容器集群管理)系列之集群部署环境规划(一)

    0.前言 整体架构目录:ASP.NET Core分布式项目实战-目录 k8s架构目录:Kubernetes(k8s)集群部署(k8s企业级Docker容器集群管理)系列目录 一.环境规划 软件 版本 ...

  5. elk 系列:Elasticsearch 7.2 集群部署+TLS 加密+认证登陆

    背景 2019年5月21日,Elastic官方发布消息: Elastic Stack 新版本6.8.0 和7.1.0的核心安全功能现免费提供. 这意味着用户现在能够对网络流量进行加密.创建和管理用户. ...

  6. 使用ElasticSearch全文检索以及集群部署

    ElasticSearch  即 ES 安装:版本---elasticsearch-2.4.6.tar.gz tar -zxvf elasticsearch-2.4.6.tar.gz 由于es不允许使 ...

  7. 【分布式缓存系列】集群环境下Redis分布式锁的正确姿势

    一.前言 在上一篇文章中,已经介绍了基于Redis实现分布式锁的正确姿势,但是上篇文章存在一定的缺陷——它加锁只作用在一个Redis节点上,如果通过sentinel保证高可用,如果master节点由于 ...

  8. 剑指架构师系列-Redis集群部署

    初步搭建Redis集群 克隆已经安装Redis的虚拟机,我们使用这两个虚拟机中的Redis来搭建集群. master:192.168.2.129 端口:7001 slave:192.168.2.132 ...

  9. 面试系列32 集群部署时的分布式session如何实现

    session是啥?浏览器有个cookie,在一段时间内这个cookie都存在,然后每次发请求过来都带上一个特殊的jsessionid cookie,就根据这个东西,在服务端可以维护一个对应的sess ...

随机推荐

  1. Git与GitHub常用操作

    --------------------------基本操作--------------------------clone 拷贝远程仓库commit 本地提交push 远程提交pull 更新本地--- ...

  2. Least Cost Bracket Sequence(贪心)

    Least Cost Bracket Sequence(贪心) Describe This is yet another problem on regular bracket sequences. A ...

  3. JVM系列(2)-GC

    1.什么是GC? 大白话说就是垃圾回收机制,内存空间是有限的,你创建的每个对象和变量都会占据内存,gc做的就是对象清除将内存释放出来,这就是GC要做的事. 2.需要GC的区域 说起垃圾回收的场所,了解 ...

  4. Docker之从零开始制作docker镜像

    以前学习docker是直接docker pull命令直接拉取Linux中已有镜像,并创建容器,添加应用程序,但是docker镜像一开始是怎么来的呢?下面将从零开始介绍整个docker镜像的制作过程(初 ...

  5. 「雕爷学编程」Arduino动手做(7)——旋转电位器模块

    37款传感器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器,依照实践(动手试试)出真知的理念,以学习和交流为目的,这里准备逐一做做实验 ...

  6. 汇编语言 简单的Hello World

    DATA SEGMENT STRING DB 'Hello World!','$' DATA ENDS CODE SEGMENT ASSUME CS:CODE, DS:DATA START: MOV ...

  7. 剑指Offer01之二维数组中查找目标数

    剑指Offer之二维数组中查找目标数 题目描述 ​ 在一个二维数组中(每个一维数组的长度相等),每一行都是从左到右递增的顺序排序,每一列都是从上到下递增的顺序排序,输入这样一个二维数组和一个整数,判断 ...

  8. 08-Python之路---初识函数

    Python之路---初识函数️ 程序员三大美德: 懒惰 因为一直致力于减少工作的总工作量. 缺乏耐性 因为一旦让你去做本该计算机完成的事,你将会怒不可遏. 傲慢 因为被荣誉感冲晕头的你会把程序写得让 ...

  9. 根据name获取控件

    javascript: document.getElementsByName("name")[index]; jquery: $("tr[name='name']&quo ...

  10. BitArray虽好,但请不要滥用,又一次线上内存暴增排查

    一:背景 1. 讲故事 前天写了一篇大内存排查在园子里挺火,这是做自媒体最开心的事拉,干脆再来一篇满足大家胃口,上个月我写了一篇博客提到过使用bitmap对原来的List<CustomerID& ...