小土刀

Elasticsearch 集群指南

Elasticsearch 集群的设置虽然比较简单,但是具体使用其实有很多需要注意的地方。本文结合工作中的实战经验,分享一下 Elasticsearch 集群的相关技巧。


更新历史

  • 2016.11.01: 更新导入数据过慢的解决方案
  • 2016.09.28: 初稿完成,Elasticsearch 版本 2.4.0
  • 2016.11.19: 更新通天塔之日志分析平台系列文章链接

通天塔之日志分析平台系列文章

安装

虽然 Elasticsearch 的安装比较简单,不过我还是写了一个安装脚本,可以在这里查看,具体来说其实就两步,下载和解压,如下:

wget https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.4.0/elasticsearch-2.4.0.tar.gz
tar -xvzf elasticsearch-2.4.0.tar.gz

分别在集群中每台机器中完成安装即可,具体的启动也非常简单,如果要在前台,直接 ./bin/elasticsearch 即可,如果要放到后台,则使用 nohup ./bin/elasticsearch &

配置

配置文件位于 config 文件夹中,其中 elasticsearch.yml 是 elasticsearch 的配置,而 logging.yml 是输出日志相关的设置。配置文件的内容有很多,不过因为默认值基本都够用了,所以我们只需要配置很少的内容。假设现在有两个节点,内部 IP 地址分别为 A: 10.1.1.0B: 10.1.1.1,那么配置为:

# 节点 A
cluster.name: wdxtubes
node.name: "es01"
bootstrap.mlockall: true
network.host: 10.1.1.0
network.publish_host: 10.1.1.0
discovery.zen.ping.unicast.hosts: ["10.1.1.1"]
discovery.zen.fd.ping_timeout: 120s
discovery.zen.fd.ping_retries: 6
discovery.zen.fd.ping_interval: 30s
# 节点 B
cluster.name: wdxtubes
node.name: "es02"
bootstrap.mlockall: true
network.host: 10.1.1.1
network.publish_host: 10.1.1.1
discovery.zen.ping.unicast.hosts: ["10.1.1.0"]
discovery.zen.fd.ping_timeout: 120s
discovery.zen.fd.ping_retries: 6
discovery.zen.fd.ping_interval: 30s

这里需要注意的是我们采用单播的方式来进行集群中机器的查找,因为 elasticsearch 已经尽量帮我们做好了集群相关的工作,只要保证 cluster.name 一致,就可以自动发现。另外,我们调大了超时的间隔和互相 ping 发送的频率以及重试次数,防止某台机器在 Full GC 的时候因未能及时响应而造成的连锁反应(后面会详细说明)

多说一句,机器配置的时候,最好确保两台机器可以互相 ping 通,并开放所有端口的内部访问(如果是用云主机的话,尤其需要注意这一点)

如果需要扩展的话,只需要保证 cluster.name 一致即可,比如说现在新加入一台 C: 10.1.1.2,那么配置可以这么写

# 节点 C
cluster.name: wdxtubes
node.name: "es03"
bootstrap.mlockall: true
network.host: 10.1.1.2
network.publish_host: 10.1.1.2
discovery.zen.ping.unicast.hosts: ["10.1.1.0"]
discovery.zen.fd.ping_timeout: 120s
discovery.zen.fd.ping_retries: 6
discovery.zen.fd.ping_interval: 30s

这里 discovery.zen.ping.unicast.hosts 中只需要填写原有集群中任意一台机器的地址即可。

然后我们可以在集群中的机器上使用 curl http://10.1.1.0:9200/_cluster/health 来查看集群状态。比如:

{
"cluster_name":"wdxtub-es",
"status":"green",
"timed_out":false,
"number_of_nodes":2,
"number_of_data_nodes":2,
"active_primary_shards":821,
"active_shards":1642,
"relocating_shards":0,
"initializing_shards":0,
"unassigned_shards":0,
"delayed_unassigned_shards":0,
"number_of_pending_tasks":0,
"number_of_in_flight_fetch":0,
"task_max_waiting_in_queue_millis":0,
"active_shards_percent_as_number":100.0
}

如果状态是 green,那就没有问题啦。下面我们会结合不同的实例进行介绍

重启

Elasticsearch 的重启是一个非常需要按规矩操作的过程,否则会带来一系列的意想不到的问题,所以一定要按照官方建议的步骤来进行。

首先,因为 Elasticsearch 自带的高可用机制,一旦一个节点下线,就会在集群内部进行数据的重分配,会带来很多不必要的开销,所以需要先关闭,关闭方法是给集群发送一个请求,这个请求可以动态修改集群的设置:

PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.enable": "none"
}
}

而在重启之后需要进行数据恢复,如果停止索引并发送一个同步刷新请求,这个过程就会快很多,需要注意的是,如果此时有任何正在进行的索引操作,这个 flush 操作会失败,因此必要时我们可以重试多次,这是安全的:

POST /_flush/synced

现在我们可以停止集群中的各个节点,完成重启或升级的操作。具体单台机器的操作可以看这里

完成之后,我们最好先启动那些 node.master 设置为 true 的节点(这也是默认设置),等到集群选举出了 master 节点,就可以继续添加数据节点了(即那些 node.master 为 false 且 node.data 为 true 的),这里我们可以用以下方式进行监控

GET _cat/health
GET _cat/nodes

每个节点加入集群之后,就会开始恢复本地保存的首要分片,一开始 _cat/health 查询的结果是 red,之后会变成 yellow,也就意味着所有的首要分片已经恢复了,但是其他的复制分片还没有恢复,因为我们一开始已经设置不恢复复制分片。

最后一步,我们需要重新开启集群的数据重分配,以保证集群的高可用性,操作也很简单:

PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.enable": "all"
}
}

当使用 _cat/health 的结果为 green 时,则重启和恢复顺利完成。

监控

无论是 Elasticsearch 官方还是社区,有很多插件可以完成监控的任务,但是本文只介绍默认的 API,主要是 _cat_cluster 这两个接口,具体的文档可以在 cat APIcluster API 中查看,这里简要介绍一下。

对于 _cat 接口,在请求后面加上 ?v 就会输出详细信息,例如:

wdxtub:~$ curl 10.1.1.10:9200/_cat/master?v
id host ip node
AoVFmiU4Q2SAHNVcMGPsWQ 10.1.1.11 10.1.1.11 node-2

如果对于字段的名字有疑问,可以使用 ?help,例如:

wdxtub:~$ curl 10.1.1.10:9200/_cat/master?help
id | | node id
host | h | host name
ip | | ip address
node | n | node name

如果只想要查看指定字段,可以利用 ?h= 来进行指定,例如:

wdxtub:~$ curl 10.1.1.10:9200/_cat/nodes?h=ip,port,heapPercent,name
10.1.1.11 9300 64 node-2
10.1.1.10 9300 71 node-1

对于带数字的输出,可以利用管道来进行排序,比如下面的命令就可以按照索引大小来进行排序(这里的 -rnk8 指的是按照第八列排序):

wdxtub:~$ curl 10.1.1.10:9200/_cat/indices?bytes=b | sort -rnk8
green open slog-2016-09-11 5 1 9729152 0 11128793222 5564396611
green open slog-2016-09-12 5 1 8355880 0 9539380440 4769690220
green open slog-2016-09-25 5 1 6720954 0 7415719218 3707859609
green open slog-2016-09-19 5 1 5840177 0 6575155002 3287577501
green open slog-2016-09-10 5 1 5858916 0 6504251544 3252125772

其他比较常用的命令如下所示,具体的可以参阅文档,这里不再赘述:

  • _cat/count 文档总数
  • _cat/count/[index_name] 某个索引的文档总数
  • _cat/fielddata?v 显示每个节点的字段的堆内存使用量
  • _cat/health?v 节点的健康状况
    • 可以使用下面的命令来自动检查集群状况
    • while true; do curl localhost:9200/_cat/health; sleep 120; done
  • _cat/indices?v 查看每个索引的详细信息,配合管道命令可以有很多应用,比如
    • 找出所有状态为 yellow 的索引 curl localhost:9200/_cat/indices | grep ^yell
    • 排序 curl 'localhost:9200/_cat/indices?bytes=b' | sort -rnk8
    • 指定列及内存使用状况 curl 'localhost:9200/_cat/indices?v&h=i,tm'
  • _cat/nodes 展示集群的拓扑结构
  • _cat/pending_tasks?v 显示正在排队的任务
  • _cat/recovery?v 显示分片恢复的过程
  • _cat/thread_pool?v 显示线程池相关信息,有很多信息,可以根据需要进行查询
  • _cat/shards?v 显示分片的相关信息
  • _cat/shards/[index-name] 显示指定索引的分片信息

_cluster 的接口的用法和 _cat 类似,这里就不再赘述了。

优化

合理计划服务器

在 Elasticsearch 的配置文件中,可以根据两个配置(node.masternode.data)选项来分配不同节点的角色,以达到提高服务器性能的目的。

  • node.master: false; node.data: true - 该节点只作为数据节点,用于存储和查询,资源消耗会较低
  • node.master: true; node.data: false - 该节点只作为 master 节点,不存储数据,主要负责协调索引请求和查询请求
  • node.master: false; node.data: falst - 该节点不作为 master 节点,也不存储数据,主要用于查询时的负载均衡(做结果汇总等工作)

另外,一台服务器最好只部署一个节点以维持服务器稳定,毕竟资源是有限的,多开也没啥

数据节点就是数据节点

如果有配置数据节点,那么可以关闭其 http 功能,让它专注于索引的操作。插件之类的也最好安装到非数据节点服务器上,这样是一个兼顾数据安全和服务器性能的考虑。具体的配置项是 http.enabled: false

线程池配置

针对 Elasticsearch 的不同操作,可以配置不同大小的线程池,这个需要根据业务需求确定最佳值,场景的操作有:index, search, suggest, get, bulk, percolate, snapshot, snapshot_data, warmer, refresh。

这里以 index(创建/更新/删除索引数据)和 search(搜索操作)为例:

threadpool:
index:
type: fixed
size: 24(逻辑核心数*3)
queue_ size: 1000
search:
type: fixed
size: 24(逻辑核心数*3)
queue_ size: 1000

分片与副本

默认的参数是 5 个分片(shard)和 1 个副本(replica),碎片数目越多,索引速度越快;副本数目越多,搜索能力及可用性更高。分片的数目是在一开始就设定好的,但是副本的数目是可以后期修改的。

而在恢复数据的时候,可以先减少分片刷新索引的时间间隔,如

curl -XPUT 'http://10.1.1.0:9200/_settings' -d '{
"index" : {
"refresh_interval" : "-1"
}
}'

完成插入之后再恢复

curl -XPUT 'http://10.1.1.0:9200/_settings' -d '{
"index" : {
"refresh_interval" : "1s"
}
}'

查询

查询中最重要的思路就是 routing,尽量减少慢查询的次数。而当索引越来越大的时候,每个分片也会增大,查询速度就会变慢。一个可行的解决思路就是分索引,比方说不同类型的数据利用不同的 routing 进行分离。

还有一个从业务出发的思路,就是不索引不需要的字段,这样就可以减小集群所需资源的量。

JVM 设置

关于 JVM 的设置我还在摸索中,不过有几个技巧:

  • JVM 的堆大小不要超过 32G,来源 Don’t Cross 32 GB!
  • 使用 bootstrap.mlockall: true,启动时就锁定内存
  • 用较小的 heapsize 配合 SSD

排查

Full GC 问题

这里以一个实例来介绍我是如何在生产环境中排查和修复 Elasticsearch 集群忽然响应时间剧增的问题的。

情况是这样的,随着接入 Elasticsearch 的数据量增大,忽然有一个周末出问题了 - ES 集群的查询和插入都变得巨慢无比。监控报警都把邮箱和手机发爆炸了。

那么问题来了,究竟是哪里出了乱子?

因为发送数据的客户端和服务器近期并没有特别大的改动,我检查了 Kafka 队列也一切正常,于是可以锁定问题出在 Elasticsearch 身上。

第一反应就是先去看 Elasticsearch 的日志,发现根据日志显示,一致在不停的垃圾回收。因此对症下药,把 JVM 的堆内存改大。但是在集群重启之后仍然会出现性能急剧下降的状况,于是继续检查日志,发现是因为 JVM 进行 Full GC 的时间过长,导致 ES 集群认为拓扑结构改变,开始迁移数据所导致。而迁移数据本身又会导致 Full GC,让情况更糟的是,在 Full GC 结束之后,集群的拓扑结构又再次改变,于是就陷入了这样的死循环。

破局的方法其实非常简单粗暴,把检测集群拓扑的时间间隔和超时次数加大一点,留足够的时间给 JVM 进行 Full GC 即可。

导入数据过慢问题

最近在从 MySQL 数据库中导入大量数据到 Elasticsearch 的时候,出现写入极其缓慢,甚至在使用了 bulk(批量)接口之后也没有改善的问题。奇怪的是,从 MySQL 的表 A 和表 B 中导入甚至会有几十倍的速度差距,这是为什么呢?

经过一步一步排查,基本上 ES 的文档和可以配置的参数都调整过之后并没有改善,于是开始从数据源入手,最后发现表 A 和 表 B 的数据顺序是不太一样的。表 A 中基本是顺序递增的数据,主键(自增长 ID)基本对应于时间顺序;而表 B 中则基本是随机插入的,所以按照数据库中的 ID 进行顺序导出,就会发现相邻记录对应的日期可能相差很大,而正好我们在 ES 中又是根据日期来进行索引的切割的,导致每次都需要在不同的索引中进行切换,速度自然上不去。

所以我们把从 MySQL 数据库中选择数据的语句利用 timestamp 作为 order by 的标准,导入速度就很快了。

这里有一点需要注意每次除了 ID 之外,还需要记录 timestamp 的值,这样才能保证是顺序导入的 where id > xxx and timestamp > xxx,其中 timestamp 每次需要归 0。

总结

经历了一次事故排查之后,我对 Elasticsearch 集群的相关认识也达到了比较深的程度,虽然在黑暗中探索的那两天很痛苦,不过总算是走出来了,于是赶紧把经验分享出来,这样大家可以少走弯路。

参考链接

您的支持是对我创作最大的鼓励!

热评文章