工作中用到了 Elasticsearch 对接 fluent-bit,这里查些资料,做些摘录和总结
ES 快速开始
Elasticsearch安装
简介
Elastic Stack 是应对多种搜索场景的一站式解决方案。主要产品包括 Elasticsearch、Kibana、Beats 和 Logstash(也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。其中Beats 平台集合了多种单一用途数据采集器。它们可以从成百上千或成千上万台机器和系统向 Logstash 或 Elasticsearch 发送数据。目前对各个平台支持的都比较好,主流的OS基本都支持,另外容器环境也是支持的,如Docker/K8S等。
其中 Beats 平台产品模块又包括 Filebeat,Metricbeat,Packetbeat,Winlogbeat,Auditbeat,Heartbeat,Functionbeat。
本系列文章主要使用其中的 Filebeat 模块作为日志采集器,因为 Filebeat 足够小巧和轻量,非常适合作为 agent 部署在目标服务器上。老牌的 Logstash(ELK中的“L”)同样也可以完成日志采集工作,但在过去的日子里由于性能和资源占用问题,现在已经慢慢退出日志采集工作,主要负责日志的二次加工处理工作。
本文将选用其中的 Elasticsearch、Kibana、以及 Beats 平台中的 Filebeat 进行介绍(即EFK)。其中 Elasticsearch 用于日志的索引及搜索,Kibana用于结果展示,Filebeat 用于日志采集。
环境要求
由于 Elasticsearch 是一个Java应用,所以需要一个Java运行环境,Elasticsearch 自身已经集成了一个JDK(OpenJDK 13)随发行版一起发布,推荐使用集成的就好了,如果你仍坚持想使用自己系统上已有的JDK,没关系,只需要在环境变量中配置一个JAVA_HOME变量即可,需要注意的是Elasticsearch要求JDK最低版本为8。另外,官方推荐使用长期支持版本(LTS)的发行版,像Java9,10,12这些短期发行版本不推荐使用。
参见详细平台支持列表。
下载安装
下载
curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.4.1-linux-x86_64.tar.gz
tar -zxvf elasticsearch-7.4.1-linux-x86_64.tar.gz
cd elasticsearch-7.4.1
解压后的目录elasticsearch-7.4.1我们称为$ES_HOME
Elastic Stack产品很多配置选项都提供了默认值,对于初次接触的用户很容易上手。
启动
./bin/elasticsearch
默认情况下,Elasticsearch 运行在前台,要以后台进程运行,执行以下命令:
./bin/elasticsearch -d
检查服务
我们可以使用以下命令测试一下服务是否运行起来:
curl http://127.0.0.1:9200
Elasticsearch 默认监听9200及9300端口,请确认端口可用。
正常的话会返回以下内容:
{
"name" : "devServer1",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "7bThQB_TTWOHnQ70Jt6I6g",
"version" : {
"number" : "7.4.1",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "fc0eeb6e2c25915d63d871d344e3d0b45ea0ea1e",
"build_date" : "2019-10-22T17:16:35.176724Z",
"build_snapshot" : false,
"lucene_version" : "8.2.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
结束进程
前台运行的话,直接按Ctrl+C即可。后台运行的话,先查找进程ID,再kill掉即可:
ps -ef |grep Elastic
kill -15 pid
Kibana安装
Kibana是一个开源的数据可视化平台,与 Elasticsearch 一起,完成数据的搜索,展示及分析等。
推荐将Kibana与 Elasticsearch 安装在同一台服务器上,但这不是必须的,如果你将其安装在另一台服务器,请确保修改配置文件中的URL地址。
下载
curl -L -O https://artifacts.elastic.co/downloads/kibana/kibana-7.4.1-linux-x86_64.tar.gz
tar -zxvf kibana-7.4.1-linux-x86_64.tar.gz
cd kibana-7.4.1-linux-x86_64/
启动
如果你不是在本地运行的,请先修改config/kibana.yml
配置文件,找到#server.host: "localhost"
一行,将前面的注释去掉,并将localhost
修改为实际的IP地址,如192.168.0.132
。
./bin/kibana
Kibana默认监听5601端口,确保端口可用。
查看
Kibana默认监听本地5601
端口,打开地址http://127.0.0.1:5601
查看。如果你不是在本地运行的,修改为实际的IP地址,如192.168.0.132
。
由于默认没有启用鉴权,所以打开后直接就可以进入首页了。
Kibana默认也是运行在前台,但是官方并未提供后台运行的方案,所以只能按照普通的脚本来处理了,我们可以使用以下命令让其在后台运行:
mkdir -p logs && \
nohup ./bin/kibana >> logs/kibana.log 2>&1 &
Filebeat安装
Filebeat 是Beats家族中的一员。用于采集各种日志文件,它足够小巧,轻量,简单,可以作为agent安装在服务上。Filebeat 内置有多种模块(auditd、Apache、NGINX、System、MySQL 等等),可针对常见格式的日志大大简化收集、解析和可视化过程,只需一条命令即可。
Filebeat 运行的前提是 Elasticsearch 和Kibana已经运行起来。
下载
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.4.1-linux-x86_64.tar.gz
tar -zxvf filebeat-7.4.1-linux-x86_64.tar.gz
cd filebeat-7.4.1-linux-x86_64
准备日志收集目录
sudo mkdir -p /opt/data/my-app1-log && \
sudo chown devops:devops /opt/data/my-app1-log
注意权限,将devops
用户替换为你实际要运行Filebeat的用户。
准备一些测试日志
编辑/opt/data/my-app1-log/app.log
并存入以下示例日志内容
2019-10-06 11:40:36.652 INFO http-bio-10009-exec-302 - start someMethod for some params
2019-10-06 11:40:36.652 INFO http-bio-10009-exec-302 - getUser for someField null,paramId 1111
2019-10-06 11:40:36.653 DEBUG http-bio-10009-exec-302 - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@5f324b3c]
2019-10-06 11:40:36.653 DEBUG http-bio-10009-exec-302 - ==> Preparing: select * from t_user where login_name=?
2019-10-06 11:40:36.653 DEBUG http-bio-10009-exec-302 - ==> Parameters: 18888888888(String)
2019-10-06 11:40:37.254 INFO http-bio-10009-exec-302 - Filtering response of path: /some/controller/path
2019-10-06 11:40:37.254 INFO http-bio-10009-exec-308 - Filtering request of path: /some/controller/path
2019-10-06 11:40:37.254 INFO http-bio-10009-exec-308 - init session with something=<null>,xxxxxxxx,userId=1234
2019-10-06 11:40:37.254 INFO http-bio-10009-exec-308 - start someMethod for some params
2019-10-06 11:40:37.255 INFO http-bio-10009-exec-308 - getUser for someField null,paramId 2222
2019-10-06 11:40:37.255 DEBUG http-bio-10009-exec-308 - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@5f324b3c]
2019-10-06 11:40:37.255 DEBUG http-bio-10009-exec-308 - ==> Preparing: select * from t_user where login_name=?
2019-10-06 11:40:37.255 DEBUG http-bio-10009-exec-308 - ==> Parameters: 19999999999(String)
2019-10-06 11:40:37.262 INFO http-bio-10009-exec-171 - Filtering request of path: /some/controller/path
2019-10-06 11:40:37.262 INFO http-bio-10009-exec-171 - init session with something=<null>,xxxxxxxx,userId=1256
2019-10-06 11:40:37.262 INFO http-bio-10009-exec-171 - Filtering response of path: /some/controller/path
2019-10-06 11:40:37.262 INFO http-bio-10009-exec-308 - Filtering response of path: /another/controller/path
2019-10-06 11:40:37.263 INFO http-bio-10009-exec-135 - Filtering request of path: /another/controller/path
2019-10-06 11:40:37.263 INFO http-bio-10009-exec-135 - init session with something=<null>,xxxxxxxx,userId=5678
2019-10-06 11:40:37.277 DEBUG http-bio-10009-exec-135 - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@5f324b3c]
2019-10-06 11:40:37.277 DEBUG http-bio-10009-exec-135 - ==> Preparing: select a.* from sometable a where a.user_id = ?
2019-10-06 11:40:37.277 DEBUG http-bio-10009-exec-135 - ==> Parameters: 5678(Long)
2019-10-06 11:40:37.277 INFO http-bio-10009-exec-135 - Filtering response of path: /another/controller/path
配置输入源
vi filebeat.yml
,
在开头Filebeat inputs部分找到以下片段
- type: log
# Change to true to enable this input configuration.
enabled: false
# Paths that should be crawled and fetched. Glob based paths.
paths:
- /var/log/*.log
将enabled
的值修改为true
,并将paths
下的内容修改为如下所示:
- type: log
# Change to true to enable this input configuration.
enabled: true
# Paths that should be crawled and fetched. Glob based paths.
paths:
- /opt/data/my-app1-log/*.log
测试配置
可以使用test
命令对修改后的配置进行一个测试,确保没有问题。
./filebeat test config -e
如果没有问题的话最后会输出Config OK
字样。
启动
./filebeat -e
其中-e
参数表示将Filebeat本身的日志输出到标准错误中,并关闭syslog/file的输出。
同样,Filebeat官方也没有提供后台运行的方法,使用以下命令让它在后台运行:
nohup ./filebeat > /dev/null 2>&1 &
没问题的话,会看到以下内容输出(部分)
2019-11-24T18:17:07.729+0800 INFO instance/beat.go:292 Setup Beat: filebeat; Version: 7.4.1
2019-11-24T18:17:07.729+0800 INFO [index-management] idxmgmt/std.go:178 Set output.elasticsearch.index to 'filebeat-7.4.1' as ILM is enabled.
2019-11-24T18:17:07.729+0800 INFO elasticsearch/client.go:170 Elasticsearch url: http://localhost:9200
2019-11-24T18:17:07.730+0800 INFO [publisher] pipeline/module.go:97 Beat name: devServer1
2019-11-24T18:17:07.730+0800 INFO [monitoring] log/log.go:118 Starting metrics logging every 30s
2019-11-24T18:17:07.730+0800 INFO instance/beat.go:422 filebeat start running.
2019-11-24T18:17:07.730+0800 INFO registrar/registrar.go:145 Loading registrar data from /var/lib/filebeat-7.4.1-linux-x86_64/data/registry/filebeat/data.json
2019-11-24T18:17:07.730+0800 INFO registrar/registrar.go:152 States Loaded from registrar: 0
2019-11-24T18:17:07.730+0800 INFO crawler/crawler.go:72 Loading Inputs: 1
2019-11-24T18:17:07.730+0800 INFO log/input.go:152 Configured paths: [/opt/data/my-app1-log/*.log]
2019-11-24T18:17:07.735+0800 INFO input/input.go:114 Starting input of type: log; ID: 6209541038125597693
2019-11-24T18:17:07.735+0800 INFO crawler/crawler.go:106 Loading and starting Inputs completed. Enabled inputs: 1
2019-11-24T18:17:07.735+0800 INFO cfgfile/reload.go:171 Config reloader started
2019-11-24T18:17:07.735+0800 INFO cfgfile/reload.go:226 Loading of config files completed.
2019-11-24T18:17:07.736+0800 INFO log/harvester.go:251 Harvester started for file: /opt/data/my-app1-log/app.log
2019-11-24T18:17:10.729+0800 INFO add_cloud_metadata/add_cloud_metadata.go:87 add_cloud_metadata: hosting provider type not detected.
2019-11-24T18:17:11.729+0800 INFO pipeline/output.go:95 Connecting to backoff(elasticsearch(http://localhost:9200))
2019-11-24T18:17:11.825+0800 INFO elasticsearch/client.go:743 Attempting to connect to Elasticsearch version 7.4.1
2019-11-24T18:17:11.866+0800 INFO [index-management] idxmgmt/std.go:252 Auto ILM enable success.
2019-11-24T18:17:12.018+0800 INFO [index-management] idxmgmt/std.go:265 ILM policy successfully loaded.
2019-11-24T18:17:12.018+0800 INFO [index-management] idxmgmt/std.go:394 Set setup.template.name to '{filebeat-7.4.1 {now/d}-000001}' as ILM is enabled.
2019-11-24T18:17:12.018+0800 INFO [index-management] idxmgmt/std.go:399 Set setup.template.pattern to 'filebeat-7.4.1-*' as ILM is enabled.
2019-11-24T18:17:12.018+0800 INFO [index-management] idxmgmt/std.go:433 Set settings.index.lifecycle.rollover_alias in template to {filebeat-7.4.1 {now/d}-000001} as ILM is enabled.
2019-11-24T18:17:12.018+0800 INFO [index-management] idxmgmt/std.go:437 Set settings.index.lifecycle.name in template to {filebeat-7.4.1 {"policy":{"phases":{"hot":{"actions":{"rollover":{"max_age":"30d","max_size":"50gb"}}}}}}} as ILM is enabled.
2019-11-24T18:17:12.019+0800 INFO template/load.go:169 Existing template will be overwritten, as overwrite is enabled.
2019-11-24T18:17:12.125+0800 INFO template/load.go:108 Try loading template filebeat-7.4.1 to Elasticsearch
2019-11-24T18:17:12.332+0800 INFO template/load.go:100 template with name 'filebeat-7.4.1' loaded.
2019-11-24T18:17:12.332+0800 INFO [index-management] idxmgmt/std.go:289 Loaded index template.
2019-11-24T18:17:13.870+0800 INFO [index-management] idxmgmt/std.go:300 Write alias successfully generated.
2019-11-24T18:17:13.870+0800 INFO pipeline/output.go:105 Connection to backoff(elasticsearch(http://localhost:9200)) established
可以看到Filebeat检测到了一个日志输入并立即开始采集该日志:
2019-11-24T18:17:07.730+0800 INFO crawler/crawler.go:72 Loading Inputs: 1
2019-11-24T18:17:07.730+0800 INFO log/input.go:152 Configured paths: [/opt/data/my-app1-log/*.log]
2019-11-24T18:17:07.735+0800 INFO input/input.go:114 Starting input of type: log; ID: 6209541038125597693
2019-11-24T18:17:07.735+0800 INFO crawler/crawler.go:106 Loading and starting Inputs completed. Enabled inputs: 1
2019-11-24T18:17:07.735+0800 INFO cfgfile/reload.go:171 Config reloader started
2019-11-24T18:17:07.735+0800 INFO cfgfile/reload.go:226 Loading of config files completed.
2019-11-24T18:17:07.736+0800 INFO log/harvester.go:251 Harvester started for file: /opt/data/my-app1-log/app.log
2019-11-24T18:17:10.729+0800 INFO add_cloud_metadata/add_cloud_metadata.go:87 add_cloud_metadata: hosting provider type not detected.
2019-11-24T18:17:11.729+0800 INFO pipeline/output.go:95 Connecting to backoff(elasticsearch(http://localhost:9200))
2019-11-24T18:17:11.825+0800 INFO elasticsearch/client.go:743 Attempting to connect to Elasticsearch version 7.4.1
采集的日志接着被发送到了 Elasticsearch 中,以下是ElasticSearch的部分日志:
[2019-11-24T18:17:12,359][INFO ][o.e.c.m.MetaDataCreateIndexService] [devServer1] [filebeat-7.4.1-2019.11.24-000001] creating index, cause [api], templates [filebeat-7.4.1], shards [1]/[1], mappings [_doc]
数据展示及搜索
经过前面的操作以后,现在文件中的日志已经被发送到了 Elasticsearch 中。现在我们就可以检索我们的日志了。
创建索引模式
打开Kibana页面,点击菜单 Management >> Index Patterns,然后点击页面上的Create index pattern,在Index pattern输入框中输入filebeat-*关键字,当提示Success! Your index pattern matches 1 index.时,我们点击Next step:
然后在Time Filter field name下拉列表中选择@timestamp
作为时间过滤字段,最后点击Create index pattern按钮,稍等几秒,完成索引模式创建
查询日志
索引模式创建完成后,现在终于可以查询日志了。点击左侧菜单Discover,进入查询页面,当前页面默认查询过去15分钟的日志,如果你的页面显示没有查到任何东西,请调整一下时间段,比如将时间改为查找今天,然后就能查到内容了:
你也可以试着在输入框输入日志中出现的某个关键字进行搜索,搜索结果会高亮显示。
小结
到此,我们的日志查询平台就算告一段落了。可以看到,我们将日志从应用节点采集汇总到了 Elasticsearch 中,然后可以通过 Kibana 进行各种检索,这比登录到服务器上使用tail,cat,grep等命令方便多了,重要的是,Elastic Stack可以将你的多个应用的多个节点产生的日志聚合到一块,方便检索和分析。
ES 生产配置
概述
当集群中有多个 Elasticsearch 节点时,存储的文档会分布在整个集群中,并且可以从任何一个节点立即访问。Elasticsearch 的索引是由一个或多个物理分片组成的逻辑分组,每个分片实际上又是一个自包含的索引。通过将文档分布在多个分片组成的索引中,分片分布在集群中的多个节点上,Elasticsearch 可以确保一定的冗余,这样在增加节点时,既可以避免硬件故障,又可以提升查询容量,自动做到负载均衡。
简单框架:
Elasticsearch集群在生产环境部署时,为保证最低的可用性,需要至少3个可以胜任主节点的节点(有投票选举权的节点),主节点候选者过半不可用则集群服务不可用,所以3主节点候选者的集群最多能容忍一台主节点候选者不可用。
如果你只有2台服务器可用,建议要么只用1台,要么,将其中一台指定为主节点候选者,另一台指定为数据节点(并且关闭主节点)。
性能考量
前面提到,索引是多个分片的逻辑组合,分片又分为主分片和副本分片。副本分片是主分片的一个拷贝,索引中的每个文档都隶属于一个主分片。副本可以提升集群的容错能力以及读请求能力。索引创建之初,主分片就是固定的了,但是其副本会随着集群环境变化而动态变化,这些变化对用户是透明的。
性能受多种因素的影响,例如过多的小分片会增加维护索引的开销;而减少分片数增大分片大小则会影响在集群中迁移(负载均衡)的速度。同样,查询多个小的分片在单个分片看来性能不错,但太多的查询也会增加额外的开销,所以我们更倾向于让分片稍大一点。
以下两点作为一个初步参考:
- 平均分片大小应保持在几个GB到几十个GB之间;如果是基于时间的数据,可能在20GB到40GB之间。
- 避免大分片问题。节点可容纳的分片数与堆大小是成正比的。通常,每GB堆中分片数量应保持在20个以内。
另外,集群节点应保持在同一个网络内,避免分布在跨数据中心的节点集群上,这样在负载均衡时效率会大打折扣。
同时为了满足高可用,Elasticsearch采用跨集群复制(CCR,Cross-cluster replication)的解决方案。
CCR可以自动将索引从主群集同步到可以用作热备份的远程备用群集。如果主机群宕掉,备用集群可以完全接管。另外CCR还可以用于提供区域读服务,例如客户可以从就近的集群中获取数据。
跨集群复制是主-被动模式的,这意味着主机群可以处理读和写,而备集群只能用于读。
系统配置
Elasticsearch 默认运行在开发模式下,所以对系统资源要求比较宽泛,满足不了的条件会以警告形式打印在日志中,但仍然可以运行。一旦修改了网略类的设置,比如network.host
,Elasticsearch 会认为是运行在生产环境,并且会提高对系统资源的要求,如果不满足,则不再是警告,而是直接抛出异常并退出,因为这样的配置不能保证Elasticsearch正常运行,在未来可能会会导致数据丢失。
一般情况下,Elasticsearch 需要运行在单独的服务器上,并使用全部可用系统资源。所以需要对系统默认参数进行调整,以充分利用系统资源。
在准备将Elasticsearch迁移到生产环境时,以下配置必须确认:
- 增加可用文件描述符
- 确保有足够的线程
- 关闭交换分区
- 确保有足够的虚拟内存
- JVM DNS缓存设置
- 临时目录未用
noexec
挂载
增大文件描述符数量
官方建议该值不低于65536
检查:
ulimit -H -n
open files (-n) 4096
修改系统配置文件:
sudo vi /etc/security/limits.d/20-nproc.conf
添加以下内容并保存退出:
# max number of open file descriptors
devops - nofile 65535
其中devops
是指当前运行Elasticsearch的用户,根据实际情况修改。
另外,针对老版本的Ubuntu Server可能存在修改后不生效的情况,如果发现不生效,检查以下内容:
对于使用init.d
启动的进程, 默认会忽略limits.conf
文件。
编辑/etc/pam.d/su
,并取消注释以下行:
# session required pam_limits.so
Ubuntu Server18.04 默认已经开启
最后退出当前会话,重新登录检查:
ulimit -H -n
如果是使用RPM或Debian包安装:
一般会在系统配置文件中指定:
RPM:/etc/sysconfig/elasticsearch
Debian:/etc/default/elasticsearch
对于使用
systemd
的系统,必须通过systemd
设置:
默认在/usr/lib/systemd/system/elasticsearch.service
。
可以增加以下文件进行覆盖:/etc/systemd/system/elasticsearch.service.d/override.conf
[Service] LimitMEMLOCK=infinity
最后输入
sudo systemctl daemon-reload
重新加载。
当 Elasticsearch 启动后,还可以使用如下命令检查确认:
curl -k --user elastic:iechie1Z \
-X GET "https://192.168.0.11:9200/_nodes/stats/process?filter_path=**.max_file_descriptors&pretty"
增大线程数
官方建议该值设置不低于4096
。
检查:
ulimit -u
不够的话使用命令临时修改:
sudo ulimit -u 4096
或者修改配置文件永久修改:
sudo vi /etc/security/limits.d/20-nproc.conf
添加以下内容并保存退出:
# max number of processes
devops - nproc 4096
最后退出当前会话,重新登录检查:
ulimit -u
关闭内存交换
交换分区由于是在磁盘上的文件,所以相比物理内存性能很差。为了保证 Elasticsearch 节点稳定,应尽力避免出现内存交换。
有三种方法可以关闭内存交换。官方建议是完全禁用交换,如果不能满足,或者可以考虑最小化交换或者内存锁定。
以下方案选择一种即可:
-
完全关闭交换分区
如果当前服务器只用于运行 Elasticsearch 服务,则完全可以关闭交换分区,因为内存主要有JVM来管理。临时关闭:
sudo swapoff -a
Bash
Copy
这种方式不需要重启Elasticsearch服务。
永久关闭:
编辑/etc/fstab
,注释掉所有包含swap
关键字的行,保存重启服务器。 -
配置最小化交换
检查:sysctl -a |grep vm.swappiness
确保将该值设置为
1
,以尽可能的减少交换,正常情况下应该不会出现,只在紧急情况下可能会发生交换。临时修改:
使用以下命令临时修改:sudo sysctl -w vm.swappiness=1
永久修改:
或者修改配置文件已达到永久修改的目的:vi /etc/sysctl.d/vm.conf
增加以下内容:
vm.swappiness=1
重启服务器生效。
-
启用内存锁定
最后一种选择是使用mlockall,尝试将进程地址空间锁定在内存中,阻止 Elasticsearch 的内存被换出。
编译ES配置文件config/elasticsearch.yml
,增加以下内容:bootstrap.memory_lock: true
需要注意的是,如果mlockall尝试分配的内存超出可用空间,则可能导致JVM或Shell会话退出。
Elasticsearch 启动后可以使用以下命令检查是否生效:
curl -k --user elastic:iechie1Z \ -X GET "https://192.168.0.11:9200/_nodes?filter_path=**.mlockall&pretty"
如果
mlockall
显示为true
,则表示已经生效,否则可能是因为权限问题失败,继续修改配置文件:sudo vi /etc/security/limits.d/20-nproc.conf
添加以下内容并保存退出:
# max locked-in-memory address space (KB) devops - memlock unlimited
最后退出当前会话,重新登录检查:
ulimit -l
更多其他原因和其他安装方式的修改可以参考[常见问题汇总(待续)]中的方案。
增大虚拟内存
Elasticsearch默认使用mmapfs
目录存储索引。操作系统对mmap计数的默认限制可能太低,这可能会导致内存不足异常。
检查:
sudo sysctl -a |grep vm.max_map_count
不够的话使用以下命令临时修改:
sudo sysctl -w vm.max_map_count=262144
该操作无需重启操作系统;
或者修改配置文件已达到永久修改的目的:
vi /etc/sysctl.d/vm.conf
增加以下内容:
vm.max_map_count=262144
注意该操作需要重启后生效。
RPM 和 Debian安装包会自动配置该操作,无需再配置。
DNS 缓存设置
ES启用了安全管理器,在有安全管理器的情况下,JVM默认将无限缓存正向主机名解析,10s反向解析。ES修改了该行为,将正向解析缓存时间设置为60s,反向解析为10s。这些值一般应适用于大多数环境,如果有问题,可以在jvm.options
文件中修改es.networkaddress.cache.ttl
和es.networkaddress.cache.negative.ttl
的值。
需要注意的是,如果设置了以上两个值(默认),则在Java security policy中的networkaddress.cache.ttl=<timeout>
和networkaddress.cache.negative.ttl=<timeout>
的值会被ES忽略。
对于tar.gz或.zip配置文件在config/jvm.options;RPM和Debian安装的在/etc/elasticsearch/jvm.options。
确保临时目录未用noexec
挂载
Elasticsearch使用JNA库来执行一些平台相关的本机代码。在Linux上,在运行时从JNA归档中提取支持该库的本机代码。默认情况下,此代码被提取到Elasticsearch临时目录,该目录默认为/tmp
的子目录。该目录可以使用JVM参数-Djna.tmpdir=<path>
来设置。如果/tmp
目录是用noexec
挂载的,则JVM无法使用该目录,要么重新挂载,要么更换目录。
可以使用以下命令检查确认:
mount |grep /tmp
如果没返回任何东西或者,在返回的结果中,/tmp
一行后边不存在noexec
字样就是没问题。
Elasticsearch配置
有三个主要配置文件,默认位于主目录的config
目录下:
- elasticsearch.yml ES配置
- jvm.options JVM配置
- log4j2.properties 日志配置
修改默认配置文件存放路径
使用环境变量ES_PATH_CONF
来修改默认配置文件存放路径:
ES_PATH_CONF="/opt/data/elasticsearch/config"
该变量可以配置到系统环境变量中,或者在命令行中指定,或者直接保存到bin/elasticsearch-env
文件中:
编辑该文件,找到以下片段(位于文件末尾处):
if [ -z "$ES_PATH_CONF" ]; then ES_PATH_CONF="$ES_HOME"/config; fi
在其前面添加一行配置,将其修改为:
# custom the config path.
ES_PATH_CONF="/opt/data/elasticsearch/config"
if [ -z "$ES_PATH_CONF" ]; then ES_PATH_CONF="$ES_HOME"/config; fi
保存退出。
接着将配置文件全部复制到目标位置:
cp config/* /opt/data/elasticsearch/config/
注意,后续关于配置文件的修改都在新的位置进行,默认路径下的已经无效。
修改临时工作目录
默认情况下,ES需要在系统临时目录(一般是/tmp
)中创建私有临时目录。但系统临时目录下的文件可能会被周期性地清除,这可能导致运行中的ES出现问题。如果使用的是.tar.gz
安装,为避出现免该问题,应使用环境变量ES_TMPDIR
指定一个其他安全且可访问的位置,我们直接配置到bin/elasticsearch-env
中:
找到上边添加ES_PATH_CONF
变量的地方,加在它前面即可。
# for java.io.tmpdir.
ES_TMPDIR="/opt/data/elasticsearch/tmp"
Bash
Copy
该参数也可以放到命令行参数中。
修改数据和日志目录:
cd /opt/data/elasticsearch/config
修改elasticsearch.yml
:
path.data: /opt/data/elasticsearch/data
path.logs: /opt/data/elasticsearch/logs
注意冒号后的空格。
修改堆内存大小
编辑jvm.options
,修改堆内存大小,应始终保持这两个值是相等的:
-Xms8g
-Xmx8g
其他选择默认就好。
启用集群监控
默认情况下,监控是打开状态,但是数据收集是关闭的,需要同时打开数据收集:
修改elasticsearch.yml
:
# Enable data collection.
xpack.monitoring.collection.enabled: true
该配置启动后也可以在Kibana中开启,集群设置优先于
elasticsearch.yml
文件中的设置。
修改集群名称cluster.name
集群名称,多个集群时,集群间需要保持唯一;单个集群时,各个节点需要保持相同,修改elasticsearch.yml
:
cluster.name: AppLogCluster
修改节点名称node.name
节点名称,默认显示机器名。修改elasticsearch.yml
:
三个节点分别配置为node-M1
,node-M2
,node-M3
,例如:
node.name: node-M1
修改节点其他信息
修改elasticsearch.yml
,添加:
node.master: true
node.voting_only: false
node.data: true
node.ingest: true
node.ml: false
xpack.ml.enabled: false
cluster.remote.connect: false
网络配置network.host
网络地址。默认绑定到本地回环:127.0.0.1
和 [::1]
:
修改elasticsearch.yml
,添加:
三台IP按前面的约定分别指定:
node-M1:
network.host: 192.168.0.11
node-M2:
network.host: 192.168.0.12
node-M3:
network.host: 192.168.0.13
伪集群模式可以在同一个节点上运行多个实例。
注意一旦更改该配置,Elasticsearch 将从开发模式切换到生产模式,并在启动时做必要的系统配置检查。
发现和集群组成配置
以下配置用于节点之间相互发现和主节点选举。集群主节点候选者至少3个。过半不可用。建议主节点候选者数量固定,通过从节点方式扩容集群。
discovery.seed_hosts
默认情况下,Elasticsearch 会扫描9300
-9305
之间的端口,以发现本机运行的其他实例,这样可以自动形成一个集群。
当指定其他不同节点上的实例时,必须使用该选项配置。另外,该选项必须包含所有主节点候选者的IP,并且每个节点都要保持一致。格式为host:port
,端口不指定则保持默认。对于IPv6格式的IP,必须放到中括号中[]
。
修改elasticsearch.yml
,添加:
discovery.seed_hosts:
- 192.168.0.11
- 192.168.0.12
- 192.168.0.13
cluster.initial_master_nodes
初始化主节点候选者列表。我们选择将该配置放在命令行参数中,以避免后续重启时还要再修改配置文件:
注意现在先不要执行:
./bin/elasticsearch -d -Ecluster.initial_master_nodes=node-M1,node-M2,node-M3
需要注意的是,这里指定的节点名字必须与node.name
中的保持一致。该参数只能在第一次启动的时候加入,后续重启和新加入的节点不可以再使用。
ES 权威指南之生命周期管理入门(ILM)
索引简介
对于日志或指标(metric)类时序性强的ES索引,因为数据量大,并且写入和查询大多都是近期时间内的数据。我们可以采用hot-warm-cold架构将索引数据切分成hot/warm/cold的索引。hot索引
负责最新数据的读写,可使用内存存储;warm索引
负责较旧数据的读取,可使用内存或SSD存储;cold索引
很少被读取,可使用大容量磁盘存储。随着时间的推移,数据不断从hot索引->warm索引->cold索引迁移。针对不同阶段的索引我们还可以调整索引的主分片数,副本数,单分片的segment数等等,更好的利用机器资源。
这一切ES都帮我们实现了。ES从6.7版本推出了索引生命周期管理(Index Lifecycle Management ,简称ILM)机制,能帮我们自动管理一个索引策略(Policy)
下索引集群的生命周期。索引策略将一个索引的生命周期定义为四个阶段:
- Hot:索引可写入,也可查询。
- Warm:索引不可写入,但可查询。
- Cold:索引不可写入,但很少被查询,查询的慢点也可接受。
- Delete:索引可被安全的删除。
索引策略控制这一个索引的生命从Hot -> Warm -> Cold -> Delete 阶段,每个阶段都可以配置不同的转化行为(Action)
。下面我们看下几个常用的Action:
- Rollover 当写入索引达到了一定的大小,文档数量或创建时间时,Rollover可创建一个新的写入索引,将旧的写入索引的别名去掉,并把别名赋给新的写入索引。所以便可以通过切换
别名
控制写入的索引是谁。它可用于Hot
阶段。 - Shrink 减少一个索引的主分片数,可用于
Warm
阶段。需要注意的是当shink完成后索引名会由原来的<origin-index-name>
变为shrink-<origin-index-name>
. - Force merge 可触发一个索引分片的segment merge,同时释放掉被删除文档的占用空间。用于
Warm
阶段。 - Allocate 可指定一个索引的副本数,用于
warm, cold
阶段。
好了现在我们知道一个索引策略是由配置不同的阶段和每个阶段对应的Action组成,那怎么设置一个索引的索引策略呢?把冰箱装进大象分为三部:
设置索引策略
1.创建一个索引策略
PUT _ilm/policy/my_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
//rollover前距离索引的创建时间最大为7天
"max_age": "7d",
//rollover前索引的最大大小不超过50G
"max_size": "50G",
//rollover前索引的最大文档数不超过1个(测试用)
"max_docs": 1,
}
}
},
"warm": {
//rollover之后进入warm阶段的时间不小于30天
"min_age": "30d",
"actions": {
"forcemerge": {
//强制分片merge到segment为1
"max_num_segments": 1
},
"shrink": {
//收缩分片数为1
"number_of_shards": 1
},
"allocate": {
//副本数为2
"number_of_replicas": 2
}
}
},
"cold": {
//rollover之后进入cold阶段的时间不小于60天
"min_age": "60d",
"actions": {
"allocate": {
"require": {
//分配到cold 节点,ES可根据机器资源配置不同类型的节点
"type": "cold"
}
}
}
},
"delete": {
//rollover之后进入cold阶段的时间不小于60天
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}
2.创建一个索引模版,指定使用的索引策略
PUT _template/my_template
{
//模版匹配的索引名以"myindex-"开头
"index_patterns": ["myindex-*"],
"settings": {
//索引分片数为2
"number_of_shards":2 ,
//索引副本数为1
"number_of_replicas": 1,
//索引使用的索引策略为my_policy
"index.lifecycle.name": "full_policy",
//索引rollover后切换的索引别名为 myindex
"index.lifecycle.rollover_alias": "myindex"
}
}
3.创建一个符合上述索引模版的索引
PUT index-000001
{
"aliases": {
"myindex":{ //别名为 myindex
//允许索引被写入数据
"is_write_index": true
}
}
}
当发生rollover时,老索引的别名myindex将被去掉,新创建的索引别名为myidex,同时索引名自动在索引名上自增,变为myindex-0002。此外对应的配置信息我已注释上了,大家慢慢看吧。
小贴士
:部署ES集群节点的版本要统一,不然ILM可能出现意想不到的错误。
这里为啥要用索引模版来关联索引和索引策略呢?因为如果在创建索引时不通过模版指定索引策略,当发生rollover时,新的索引并不会继承原来索引的索引策略。
小伙伴将尝试了之后发现不对啊,我插入里两条数据并没有自动rollover啊。不慌,ES检测索引的索引策略是否该生效的时间默认为10min,可通过修改以下配置:
PUT _cluster/settings
{
"transient": {
"indices.lifecycle.poll_interval": "3s"
}
}
3秒检测一下是否可执行索引策略,应该够了。
Logstash使用ILM
问题来了,当我们使用ELK搭建索引日志系统时,咋让Logstash和ES的ILM无缝连接呢?Logstash的Elasticsearch output plugin
插件自从9.3.1版本之后就支持ILM了,我们只需要在Logstash的配置文件中简单配置下就可以全部托管给ES ILM了。
output {
elasticsearch {
//发生rollover时的写入索引的别名
ilm_rollover_alias => "myindex"
//将会附在ilm_rollover_alias的值后面共同构成索引名,myindex-00001
ilm_pattern => "00001"
//使用的索引策略
ilm_policy => "my_policy"
//使用的索引模版名称
template_name => "my_template"
}
}
如果我们一直愉快的使用一个索引策略,当然很好。但是总有意外发生。。索引策略执行失败了怎么办,中途想改变索引策略换车怎么办?这都是问题。
索引策略执行失败
首先我们先看一下失败的原因是什么,可以用API查看一下:
GET /myindex/_ilm/explain
返回信息中step_info
就是失败原因,假设是索引策略设置的有问题,比如说Shrink的主分片数设置的比模版的都大,我们只需要更新索引策略,解决问题。然后在重试让ILM继续执行下一步就好。
POST /myindex/_ilm/retry
索引策略的更新
我们可使用以下API更新索引策略
PUT _ilm/policy/my_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "25GB"
}
}
},
"delete": {
"min_age": "30d",
"actions": {
"delete": {}
}
}
}
}
}
查看索引策略发现,每次更新索引策略的版本都会增加。
对于还没有开始创建的索引,更新索引策略显然能够生效。对于已经存在的策略生效的索引,当前阶段是不会按照最新版本的策略执行的,必须等到变为下一个阶段了,才会按照最新版本的策略执行。
如果想切换索引使用的索引策略,可以使用API进行修改:
PUT myindex/_settings
{
"lifecycle.name": "my_other_policy"
}
此外,在老版本使用ILM机制时,可能还涉及到将原来的索引纳入索引策略管理中,将原来ES的curator索引滚动方案升级到ILM等问题。本文主要结合官方文档介绍了ILM的开箱使用,Logstash使用ILM,索引策略执行失败和索引策略的更新的使用。更多问题还请阅读官方文档,获得更好的体验。
ES 的CRUD基本操作
常见curl操作
==============================================================================================================================
查找别名
curl -X GET "{you_es_address}:9200/_aliases
{"logging-2021.06.22":{"aliases":{}},"logging-2021.06.23":{"aliases":{}}}
查找索引
curl -X GET "{you_es_address}:9200/_cat/indices?v"
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
green open logging-2021.06.22 v_IEgGJfSKOKeUq6EyGryg 1 1 505128 0 672.7mb 336.2mb
green open logging-2021.06.23 4X4qXOJKTh2XwNw8wezyCA 1 1 268 0 63.3mb 31.6mb
==============================================================================================================================
创建索引
curl -X POST -H 'Content-Type:application/json' "{you_es_address}:9200/logging-2021.06.11"
curl -X PUT -H 'Content-Type:application/json' "{you_es_address}:9200/{index_name}" -d'{"aliases":{"{ALIAS_NAME}":{"is_write_index":true}}}'
例如:
curl -X PUT -H 'Content-Type:application/json' "{you_es_address}:9200/test-00001" -d'{"aliases":{"test":{"is_write_index":true}}}'
curl -X PUT -H 'Content-Type:application/json' "{you_es_address}:9200/test-2021.6.20" -d'{"aliases":{"test":{"is_write_index":true}}}'
curl -X PUT -H 'Content-Type:application/json' "{you_es_address}:9200/test-000001" -d'{"aliases":{"test":{"is_write_index":true}}}'
删除索引
curl -X DELETE "{you_es_address}:9200/{index_name}
例如:
curl -X DELETE "{you_es_address}:9200/test-00001
或
curl -X DELETE "{you_es_address}:9200/test-*
创建policy
curl -X PUT "$ES_URL/_ilm/policy/$POLICY_NAME" -H 'Content-Type:application/json' -d'{"policy":{"phases":{"hot":{"actions":{"rollover":{"max_age":"1d","max_size":"10G"}}},"delete":{"min_age":"${LOG_RETENTION_TIME}d","actions":{"delete":{}}}}}}'
例如:
curl -X PUT "{you_es_address}:9200/_ilm/policy/test_policy" -H 'Content-Type:application/json' -d'{"policy":{"phases":{"hot":{"actions":{"rollover":{"max_age":"1h","max_size":"10G","max_docs": 2}}},"delete":{"min_age":"2h","actions":{"delete":{}}}}}}'
curl -X PUT "{you_es_address}:9200/_ilm/policy/test_policy" -H 'Content-Type:application/json' -d'{"policy":{"phases":{"hot":{"actions":{"rollover":{"max_age":"1h","max_size":"10G","max_docs": 2}}},"delete":{"min_age":"2h","actions":{"delete":{}}}}}}'
删除policy
curl -X DELETE "{you_es_address}:9200/_ilm/policy/test_policy"
创建template
curl - XPUT "$ES_URL/_template/$TEMPLATE_NAME" -H 'Content-Type:application/json' -d'{"index_patterns":['\""$INDEX_PATTERN"\"'],"settings":{"number_of_shards":'$SHARD_COUNT',"number_of_replicas":'$REPLICA_COUNT',"index.lifecycle.name":"$POLICY_NAME","index.lifecycle.rollover_alias":"$ALIAS_NAME"}}'
例如:
curl -X PUT "{you_es_address}:9200/_template/test_template" -H 'Content-Type:application/json' -d'{"index_patterns":["test-*"],"settings":{"number_of_shards":1,"number_of_replicas":1,"index.lifecycle.name":"test_policy","index.lifecycle.rollover_alias":"test"}}'
curl -X PUT "{you_es_address}:9200/_template/test_template" -H 'Content-Type:application/json' -d'{"index_patterns":["test-*"],"settings":{"number_of_shards":1,"number_of_replicas":1,"index.lifecycle.name":"test_policy","index.lifecycle.rollover_alias":"test"}}'
删除template
curl -X DELETE "{you_es_address}:9200/_template/test_template"
插入数据
curl -X PUT -H 'Content-Type:application/json' "{you_es_address}:9200/{index_name}/{type_name}/{id}" -d'{"author":"王五","title":"菜谱","world_count":"5000","publish_date":"2002-10-01"}'
例如:
curl -X PUT -H 'Content-Type:application/json' "{you_es_address}:9200/test-00001/info/1" -d'{"author":"王五","title":"菜谱","world_count":"5000","publish_date":"2002-10-01"}'
curl -X PUT -H 'Content-Type:application/json' "{you_es_address}:9200/test-2021.6.20/info/1" -d'{"author":"王五","title":"菜谱","world_count":"5000","publish_date":"2002-10-01"}'
curl -X PUT -H 'Content-Type:application/json' "{you_es_address}:9200/test-000001/test_type/1" -d'{"author":"王五","title":"菜谱","world_count":"5000","publish_date":"2002-10-01"}'
修改自动rollover时间
curl -X PUT -H 'Content-Type:application/json' "{you_es_address}:9200/_cluster/settings" -d'{"transient":{"indices.lifecycle.poll_interval":"3s"}}'
k8s 部署es配置日志保存方法
在es的chart中加上lifecycle
lifecycle:
postStart:
exec:
command:
- bash
- -c
- |
#!/bin/bash
# Add a template to use ILM
TEMPLATE_NAME=test_template
POLICY_NAME=test_policy
ALIAS_NAME=test-logging
INDEX_PATTERN="test-logging-*"
SHARD_COUNT=1
REPLICA_COUNT=1
ES_URL=http://localhost:9200
# Check es api service alive
while [[ "$(curl -s -o /dev/null -w '%{http_code}\n' $ES_URL)" != "200" ]]; do sleep 1; done
# Create Policy
curl -XPUT "$ES_URL/_ilm/policy/$POLICY_NAME" -H 'Content-Type:application/json'\
-d'{"policy":{"phases":{"hot":{"actions":{"rollover":{"max_age":"1d","max_size":"10G",}}},\
"delete":{"min_age":"${LOG_RETENTION_TIME}d","actions":{"delete":{}}}}}}'
# Create Template
curl -XPUT "$ES_URL/_template/$TEMPLATE_NAME" -H 'Content-Type:application/json'\
-d'{"index_patterns":['\""$INDEX_PATTERN"\"'],"settings":{"number_of_shards":'$SHARD_COUNT',"number_of_replicas":'$REPLICA_COUNT',\
"index.lifecycle.name":"$POLICY_NAME","index.lifecycle.rollover_alias":"$ALIAS_NAME"}}'
# Create First Index
curl -XPUT "$ES_URL/emla-logging-00001" -H 'Content-Type:application/json'\
-d'{"aliases":{"$ALIAS_NAME":{"is_write_index":true}}}'
ES API Interface解析
简述
ES监控的最主要作用是用于监控ES的服务是否正常运行,以及在出现问题时为提供解决问题提供依据。目前ES监控主要针对三个级别分别是:集群级别、节点级别和索引级别。
集群级别的监控主要是针对整个ES集群来说,包括集群的健康状况、集群的状态等。
节点级别的监控主要是针对每个ES实例的监控,其中包括每个实例的查询索引指标和物理资源使用指标。
索引级别的监控主要是针对每个索引来说,主要包括每个索引的性能指标,由于是针对每个索引的监控,因此一般含有多个索引的ES集群其索引级别的监控数据是非常多的。
针对这三类指标,首先集群级别的指标其性能指标较少,但是其每个指标都非常重要,可以查看集群级别指标获取ES集群的运行状态。其次,节点级别的指标更多的用于问题的排查,当发现集群出现问题时更可能多的时候会直接定位到具体的ES实例,通过查看单台实例的资源使用情况或者其他指标进行问题排查。最后,索引级别的监控的应用场景主要是为应用提供监控,例如某个应用使用到的索引其查询速度变慢就可以通过索引级别的监控判断是否是由于索引创建时一些不合理设置引起的。
查看集群总的状态
集群的监控包括二个方面:集群的健康状况和集群的运行状况
1、集群的健康状态,通过api获取:GET _cluster/health?pretty
GET _cluster/health?pretty
{
"cluster_name" : "lqbyz-es",
"status" : "green",
"timed_out" : false,
"number_of_nodes" : 8,
"number_of_data_nodes" : 4,
"active_primary_shards" : 120,
"active_shards" : 240,
"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
}
关键指标说明:
status:集群状态,分为green、yellow和red。
number_of_nodes/number_of_data_nodes:集群的节点数和数据节点数。
active_primary_shards:集群中所有活跃的主分片数。
active_shards:集群中所有活跃的分片数。
relocating_shards:当前节点迁往其他节点的分片数量,通常为0,当有节点加入或者退出时该值会增加。
initializing_shards:正在初始化的分片。
unassigned_shards:未分配的分片数,通常为0,当有某个节点的副本分片丢失该值就会增加。
number_of_pending_tasks:是指主节点创建索引并分配shards等任务,如果该指标数值一直未减小代表集群存在不稳定因素
active_shards_percent_as_number:集群分片健康度,活跃分片数占总分片数比例。
number_of_pending_tasks:pending task只能由主节点来进行处理,这些任务包括创建索引并将shards分配给节点
2、集群的状况信息
集群状态信息主要包含整个集群的一些统计信息,例如文档数、分片数、资源使用情况等。集群状态信息可以由以下api获取:GET _cluster/stats?pretty
GET _cluster/stats?pretty
{
"_nodes" : {
"total" : 8,
"successful" : 8,
"failed" : 0
},
"cluster_name" : "lqbyz-es",
"cluster_uuid" : "nRAbFt8eS9m0VYO6YKEo4w",
"timestamp" : 1601217640020,
"status" : "green",
"indices" : {
"count" : 33,
"shards" : {
"total" : 240,
"primaries" : 120,
"replication" : 1.0,
"index" : {
"shards" : {
"min" : 2,
"max" : 44,
"avg" : 7.2727272727272725
},
"primaries" : {
"min" : 1,
"max" : 22,
"avg" : 3.6363636363636362
},
"replication" : {
"min" : 1.0,
"max" : 1.0,
"avg" : 1.0
}
}
},
"docs" : {
"count" : 12509216,
"deleted" : 791784
},
"store" : {
"size_in_bytes" : 11514579278
},
"fielddata" : {
"memory_size_in_bytes" : 13464,
"evictions" : 0
},
"query_cache" : {
"memory_size_in_bytes" : 92728,
"total_count" : 19768667,
"hit_count" : 9227143,
"miss_count" : 10541524,
"cache_size" : 10,
"cache_count" : 1509487,
"evictions" : 1509477
},
"completion" : {
"size_in_bytes" : 0
},
"segments" : {
"count" : 986,
"memory_in_bytes" : 48584598,
"terms_memory_in_bytes" : 40039845,
"stored_fields_memory_in_bytes" : 4349408,
"term_vectors_memory_in_bytes" : 0,
"norms_memory_in_bytes" : 1975936,
"points_memory_in_bytes" : 1458889,
"doc_values_memory_in_bytes" : 760520,
"index_writer_memory_in_bytes" : 115607304,
"version_map_memory_in_bytes" : 5522714,
"fixed_bit_set_memory_in_bytes" : 744936,
"max_unsafe_auto_id_timestamp" : 1601164805126,
"file_sizes" : { }
}
},
"nodes" : {
"count" : {
"total" : 8,
"data" : 4,
"coordinating_only" : 1,
"master" : 3,
"ingest" : 1
},
"versions" : [
"7.2.1"
],
"os" : {
"available_processors" : 32,
"allocated_processors" : 32,
"names" : [
{
"name" : "Linux",
"count" : 8
}
],
"pretty_names" : [
{
"pretty_name" : "CentOS Linux 7 (Core)",
"count" : 8
}
],
"mem" : {
"total_in_bytes" : 64806584320,
"free_in_bytes" : 1384660992,
"used_in_bytes" : 63421923328,
"free_percent" : 2,
"used_percent" : 98
}
},
"process" : {
"cpu" : {
"percent" : 0
},
"open_file_descriptors" : {
"min" : 393,
"max" : 1069,
"avg" : 608
}
},
"jvm" : {
"max_uptime_in_millis" : 7668445988,
"versions" : [
{
"version" : "12.0.1",
"vm_name" : "OpenJDK 64-Bit Server VM",
"vm_version" : "12.0.1+12",
"vm_vendor" : "Oracle Corporation",
"bundled_jdk" : true,
"using_bundled_jdk" : true,
"count" : 8
}
],
"mem" : {
"heap_used_in_bytes" : 8168965296,
"heap_max_in_bytes" : 34080817152
},
"threads" : 447
},
"fs" : {
"total_in_bytes" : 429287014400,
"free_in_bytes" : 371347718144,
"available_in_bytes" : 371347718144
},
"plugins" : [ ],
"network_types" : {
"transport_types" : {
"security4" : 8
},
"http_types" : {
"security4" : 8
}
},
"discovery_types" : {
"zen" : 8
},
"packaging_types" : [
{
"flavor" : "default",
"type" : "rpm",
"count" : 8
}
]
}
}
关键指标说明:
indices.count:索引总数。
indices.shards.total:分片总数。
indices.shards.primaries:主分片数量。
docs.count:文档总数。
store.size_in_bytes:数据总存储容量。
segments.count:段总数。
nodes.count.total:总节点数。
nodes.count.data:数据节点数。
nodes. process. cpu.percent:节点CPU使用率。
fs.total_in_bytes:文件系统使用总容量。
fs.free_in_bytes:文件系统剩余总容量。
查看节点
节点监控主要针对各个节点,有很多指标对于保证ES集群的稳定运行非常重要。下面对节点监控指标进行介绍。节点指标可以通过以下api获取:GET /_nodes/stats?pretty
GET /_nodes/stats?pretty
{
"_nodes" : {
"total" : 8,
"successful" : 8,
"failed" : 0
},
"cluster_name" : "lqbyz-es",
"nodes" : {
}
}
关键指标说明:
name:节点名。
roles:节点角色。
indices.docs.count:索引文档数。
segments.count:段总数。
jvm.heap_used_percent:内存使用百分比。
thread_pool.{bulk, index, get, search}.{active, queue, rejected}:线程池的一些信息,包括bulk、index、get和search线程池,主要指标有active(激活)线程数,线程queue(队列)数和rejected(拒绝)线程数量。
以下一些指标是一个累加值,当节点重启之后会清零。
indices.indexing.index_total:索引文档数。
indices.indexing.index_time_in_millis:索引总耗时。
indices.get.total:get请求数。
indices.get.time_in_millis:get请求总耗时。
indices.search.query_total:search总请求数。
indices.search.query_time_in_millis:search请求总耗时。indices.search.fetch_total:fetch操作总数量。
indices.search.fetch_time_in_millis:fetch请求总耗时。
jvm.gc.collectors.young.collection_count:年轻代垃圾回收次数。
jvm.gc.collectors.young.collection_time_in_millis:年轻代垃圾回收总耗时。
jvm.gc.collectors.old.collection_count:老年代垃圾回收次数。
jvm.gc.collectors.old.collection_time_in_millis:老年代垃圾回收总耗时。
备注:需要计算的指标分为两类,分别为请求速率指标和请求处理延迟指标。
1、index_per_min:每分钟索引请求数量。计算公式如下:
索引请求率=(index_total两次采集差值)/(系统时间差值(ms))×60000 (公式1)
2、indexAverge_per_min:索引请求处理延迟。计算公式如下:
索引延迟=(index_time_in_millis两次采集差值)/(index_total两次采集差值) (公式2)
get_per_min:每分钟get请求数量,计算公式如(公式1),更改相应参数。
getAverage_per_min:get请求处理延迟,计算公式如(公式2) ,更改相应参数。
merge_per_min:每分钟merge请求数量,计算公式如(公式1),更改相应参数。
mergeAverage_per_min:merge请求处理延迟,计算公式如(公式2) ,更改相应参数。
searchQuery_per_min:每分钟query请求数量,计算公式如(公式1),更改相应参数。
searchQueryAverage_per_min:query请求延迟,计算公式如(公式2) ,更改相应参数。
searchFetch_per_min:每分钟fetch请求数量,计算公式如(公式1),更改相应参数。
searchFetchAverage_per_min:fetch请求延迟,计算公式如(公式2) ,更改相应参数。
youngGc_per_min:每分钟young gc数量,计算公式如(公式1),更改相应参数。
youngGcAverage_per_min:young gc请求延迟,计算公式如(公式2) ,更改相应参数。
oldGc_per_min:每分钟old gc数量,计算公式如(公式1),更改相应参数。
oldGcAverage_per_min:old gc请求延迟,计算公式如(公式2) ,更改相应参数。
查看索引
索引监控指标注意针对单个索引,不过也可以通过"_all"对集群种所有索引进行监控,节点指标可以通过以下api获取:GET /_stats?pretty
GET /_stats?pretty
{
"_shards" : {
"total" : 240,
"successful" : 240,
"failed" : 0
},
"_all" : {
"primaries" : {
"docs" : {
"count" : 12509216,
"deleted" : 791784
},
"store" : {
"size_in_bytes" : 5757248526
},
"indexing" : {
"index_total" : 30588566,
"index_time_in_millis" : 5143570,
"index_current" : 0,
"index_failed" : 0,
"delete_total" : 12,
"delete_time_in_millis" : 45,
"delete_current" : 0,
"noop_update_total" : 0,
"is_throttled" : false,
"throttle_time_in_millis" : 0
},
"get" : {
"total" : 3243159,
"time_in_millis" : 183757,
"exists_total" : 3242977,
"exists_time_in_millis" : 183751,
"missing_total" : 182,
"missing_time_in_millis" : 6,
"current" : 0
},
"search" : {
"open_contexts" : 0,
"query_total" : 5597307,
"query_time_in_millis" : 2410418,
"query_current" : 0,
"fetch_total" : 5596851,
"fetch_time_in_millis" : 174601,
"fetch_current" : 0,
"scroll_total" : 473136,
"scroll_time_in_millis" : 622565,
"scroll_current" : 0,
"suggest_total" : 0,
"suggest_time_in_millis" : 0,
"suggest_current" : 0
},
"merges" : {
"current" : 0,
"current_docs" : 0,
"current_size_in_bytes" : 0,
"total" : 373794,
"total_time_in_millis" : 21892509,
"total_docs" : 3054500557,
"total_size_in_bytes" : 256727351490,
"total_stopped_time_in_millis" : 0,
"total_throttled_time_in_millis" : 130284,
"total_auto_throttle_in_bytes" : 2465217352
},
"refresh" : {
"total" : 3568062,
"total_time_in_millis" : 42011140,
"external_total" : 3567653,
"external_total_time_in_millis" : 45420312,
"listeners" : 0
},
"flush" : {
"total" : 330,
"periodic" : 35,
"total_time_in_millis" : 303046
},
"warmer" : {
"current" : 0,
"total" : 3565888,
"total_time_in_millis" : 109062
},
"query_cache" : {
"memory_size_in_bytes" : 73942,
"total_count" : 9882484,
"hit_count" : 4610437,
"miss_count" : 5272047,
"cache_size" : 6,
"cache_count" : 754539,
"evictions" : 754533
},
"fielddata" : {
"memory_size_in_bytes" : 6708,
"evictions" : 0
},
"completion" : {
"size_in_bytes" : 0
},
"segments" : {
"count" : 490,
"memory_in_bytes" : 24181791,
"terms_memory_in_bytes" : 19931435,
"stored_fields_memory_in_bytes" : 2162808,
"term_vectors_memory_in_bytes" : 0,
"norms_memory_in_bytes" : 981376,
"points_memory_in_bytes" : 727948,
"doc_values_memory_in_bytes" : 378224,
"index_writer_memory_in_bytes" : 85756016,
"version_map_memory_in_bytes" : 4231529,
"fixed_bit_set_memory_in_bytes" : 367488,
"max_unsafe_auto_id_timestamp" : -1,
"file_sizes" : { }
},
"translog" : {
"operations" : 1137857,
"size_in_bytes" : 1020867543,
"uncommitted_operations" : 212059,
"uncommitted_size_in_bytes" : 191463796,
"earliest_last_modified_age" : 0
},
"request_cache" : {
"memory_size_in_bytes" : 27059,
"evictions" : 0,
"hit_count" : 1469699,
"miss_count" : 129
},
"recovery" : {
"current_as_source" : 0,
"current_as_target" : 0,
"throttle_time_in_millis" : 161
}
},
"total" : {
"docs" : {
"count" : 25020996,
"deleted" : 1662102
},
"store" : {
"size_in_bytes" : 11526213319
},
"indexing" : {
"index_total" : 61246128,
"index_time_in_millis" : 10347974,
"index_current" : 0,
"index_failed" : 0,
"delete_total" : 30,
"delete_time_in_millis" : 87,
"delete_current" : 0,
"noop_update_total" : 0,
"is_throttled" : false,
"throttle_time_in_millis" : 0
},
"get" : {
"total" : 3695973,
"time_in_millis" : 224666,
"exists_total" : 3695608,
"exists_time_in_millis" : 224652,
"missing_total" : 365,
"missing_time_in_millis" : 14,
"current" : 0
},
"search" : {
"open_contexts" : 0,
"query_total" : 11235854,
"query_time_in_millis" : 4807875,
"query_current" : 0,
"fetch_total" : 11234938,
"fetch_time_in_millis" : 354414,
"fetch_current" : 0,
"scroll_total" : 946306,
"scroll_time_in_millis" : 1219776,
"scroll_current" : 0,
"suggest_total" : 0,
"suggest_time_in_millis" : 0,
"suggest_current" : 0
},
"merges" : {
"current" : 0,
"current_docs" : 0,
"current_size_in_bytes" : 0,
"total" : 749454,
"total_time_in_millis" : 42373556,
"total_docs" : 6120472237,
"total_size_in_bytes" : 514338185876,
"total_stopped_time_in_millis" : 0,
"total_throttled_time_in_millis" : 258535,
"total_auto_throttle_in_bytes" : 4930765585
},
"refresh" : {
"total" : 7151040,
"total_time_in_millis" : 81040105,
"external_total" : 7150190,
"external_total_time_in_millis" : 87847532,
"listeners" : 0
},
"flush" : {
"total" : 693,
"periodic" : 70,
"total_time_in_millis" : 588990
},
"warmer" : {
"current" : 0,
"total" : 7146843,
"total_time_in_millis" : 220830
},
"query_cache" : {
"memory_size_in_bytes" : 92728,
"total_count" : 19774666,
"hit_count" : 9229714,
"miss_count" : 10544952,
"cache_size" : 10,
"cache_count" : 1509487,
"evictions" : 1509477
},
"fielddata" : {
"memory_size_in_bytes" : 13464,
"evictions" : 0
},
"completion" : {
"size_in_bytes" : 0
},
"segments" : {
"count" : 986,
"memory_in_bytes" : 48584598,
"terms_memory_in_bytes" : 40039845,
"stored_fields_memory_in_bytes" : 4349408,
"term_vectors_memory_in_bytes" : 0,
"norms_memory_in_bytes" : 1975936,
"points_memory_in_bytes" : 1458889,
"doc_values_memory_in_bytes" : 760520,
"index_writer_memory_in_bytes" : 170601028,
"version_map_memory_in_bytes" : 8463058,
"fixed_bit_set_memory_in_bytes" : 744936,
"max_unsafe_auto_id_timestamp" : 1601164805126,
"file_sizes" : { }
},
"translog" : {
"operations" : 2275712,
"size_in_bytes" : 2041733134,
"uncommitted_operations" : 424117,
"uncommitted_size_in_bytes" : 382926616,
"earliest_last_modified_age" : 0
},
"request_cache" : {
"memory_size_in_bytes" : 47574,
"evictions" : 0,
"hit_count" : 2939404,
"miss_count" : 256
},
"recovery" : {
"current_as_source" : 0,
"current_as_target" : 0,
"throttle_time_in_millis" : 244
}
}
}
关键指标说明(indexname泛指索引名称):
indexname.primaries.docs.count:索引文档数量。
以下一些指标是一个累加值,当节点重启之后会清零。
indexname.primaries.indexing.index_total:索引文档数。
indexname.primaries.indexing.index_time_in_millis:索引总耗时。
indexname.primaries.get.total:get请求数。
indexname.primaries.get.time_in_millis:get请求总耗时。
indexname.primaries.search.query_total:search总请求数。
indexname.primaries.search.query_time_in_millis:search请求总耗时。indices.search.fetch_total:fetch操作总数量。
indexname.primaries.search.fetch_time_in_millis:fetch请求总耗时。
indexname.primaries.refresh.total:refresh请求总量。
indexname.primaries.refresh.total_time_in_millis:refresh请求总耗时。
indexname.primaries.flush.total:flush请求总量。
indexname.primaries.flush.total_time_in_millis:flush请求总耗时。
计算指标:
索引计算指标和节点监控的计算指标一样分为两类,分别为请求速率指标和请求处理延迟指标并且计算方式一样
备注
在节点监控和索引监控时可以获取到一些操作数据,例如index、search、get等。对于这些指标参数有一些需要注意的地方:
search.query
query_total:查询总数,指的每个分片上的查询次数,一般如果有五个分片的话进行一次查询query_total = 5。注意在统计指标的时候应该统计的是total分片的,不应该只统计主分片的值,因为查询请求不一定全部分发到主分片。
get
total:get请求数,一般get都需要指定文档id。同样get也应该统计total分片的值,如果只统计主分片会造成数据变少,因为有时候的get请求不是从主分片取的数据。
indexing
index_total和index_time_in_millis指标是根据索引的文档数来记录,同样需要统计total分片的数据,total分片包括副本的索引请求,一般1个副本的话index_total会乘以2。
search.fetch
一个查询分为两个阶段,一是query另一个是fetch,fetch的情况与get相似,fetch.total指fetch请求数,fetch有可能从主分片拉取数据,也有可能从副本拉取数据。如果只统计主分片的数据会丢失数据。
g-client 使用
// $ go run _examples/main.go
package main
import (
"context"
"encoding/json"
"log"
"strconv"
"strings"
"sync"
"github.com/elastic/go-elasticsearch"
"github.com/elastic/go-elasticsearch/esapi"
)
func main() {
log.SetFlags(0)
var (
r map[string]interface{}
wg sync.WaitGroup
)
// Initialize a client with the default settings.
//
// An `ELASTICSEARCH_URL` environment variable will be used when exported.
//
es, err := elasticsearch.NewDefaultClient()
if err != nil {
log.Fatalf("Error creating the client: %s", err)
}
// 1. Get cluster info
//
res, err := es.Info()
if err != nil {
log.Fatalf("Error getting response: %s", err)
}
// Deserialize the response into a map.
if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
log.Fatalf("Error parsing the response body: %s", err)
}
// Print version number.
log.Printf("~~~~~~~> Elasticsearch %s", r["version"].(map[string]interface{})["number"])
// 2. Index documents concurrently
//
for i, title := range []string{"Test One", "Test Two"} {
wg.Add(1)
go func(i int, title string) {
defer wg.Done()
// Set up the request object directly.
req := esapi.IndexRequest{
Index: "test",
DocumentID: strconv.Itoa(i + 1),
Body: strings.NewReader(`{"title" : "` + title + `"}`),
Refresh: "true",
}
// Perform the request with the client.
res, err := req.Do(context.Background(), es)
if err != nil {
log.Fatalf("Error getting response: %s", err)
}
defer res.Body.Close()
if res.IsError() {
log.Printf("[%s] Error indexing document ID=%d", res.Status(), i+1)
} else {
// Deserialize the response into a map.
var r map[string]interface{}
if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
log.Printf("Error parsing the response body: %s", err)
} else {
// Print the response status and indexed document version.
log.Printf("[%s] %s; version=%d", res.Status(), r["result"], int(r["_version"].(float64)))
}
}
}(i, title)
}
wg.Wait()
log.Println(strings.Repeat("-", 37))
// 3. Search for the indexed documents
//
// Use the helper methods of the client.
res, err = es.Search(
es.Search.WithContext(context.Background()),
es.Search.WithIndex("test"),
es.Search.WithBody(strings.NewReader(`{"query" : { "match" : { "title" : "test" } }}`)),
es.Search.WithTrackTotalHits(true),
es.Search.WithPretty(),
)
if err != nil {
log.Fatalf("ERROR: %s", err)
}
defer res.Body.Close()
if res.IsError() {
var e map[string]interface{}
if err := json.NewDecoder(res.Body).Decode(&e); err != nil {
log.Fatalf("error parsing the response body: %s", err)
} else {
// Print the response status and error information.
log.Fatalf("[%s] %s: %s",
res.Status(),
e["error"].(map[string]interface{})["type"],
e["error"].(map[string]interface{})["reason"],
)
}
}
if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
log.Fatalf("Error parsing the response body: %s", err)
}
// Print the response status, number of results, and request duration.
log.Printf(
"[%s] %d hits; took: %dms",
res.Status(),
int(r["hits"].(map[string]interface{})["total"].(map[string]interface{})["value"].(float64)),
int(r["took"].(float64)),
)
// Print the ID and document source for each hit.
for _, hit := range r["hits"].(map[string]interface{})["hits"].([]interface{}) {
log.Printf(" * ID=%s, %s", hit.(map[string]interface{})["_id"], hit.(map[string]interface{})["_source"])
}
log.Println(strings.Repeat("=", 37))
}
// ~~~~~~~> Elasticsearch 7.0.0-SNAPSHOT
// [200 OK] updated; version=1
// [200 OK] updated; version=1
// -------------------------------------
// [200 OK] 2 hits; took: 7ms
// * ID=1, map[title:Test One]
// * ID=2, map[title:Test Two]
// =====================================
附录
一些零碎的知识记录
1) “我应该有多少个分片?”
答: 每个节点的分片数量保持在低于每1GB堆内存对应集群的分片在20-25之间。
2) “我的分片应该有多大”?
答:分片大小为50GB通常被界定为适用于各种用例的限制。
数据量(PVC_SIZE) 可配置
保存时间 (RETENTION_TIME) 可配置
索引大小 = 分片数量 * 分片大小
分片数量 = 数据量(PVC_SIZE) / 30G
"rollover": {
//rollover前距离索引的创建时间最大为1天
"max_age": "1d",
//rollover前索引的最大大小不超过10G
"max_size": "10G",
}
"delete": {
//rollover之后进入cold阶段的时间不小于60天
"min_age": "${RETENTION_TIME}",
"actions": {
"delete": {}
}
"settings": {
//索引分片数为2
"number_of_shards":2,
//索引副本数为1
"number_of_replicas": 1,
//索引使用的索引策略为my_policy
"index.lifecycle.name": "my_policy",
//索引rollover后切换的索引别名为test-alias
"index.lifecycle.rollover_alias": "myindex"
}
ES默认为一个索引创建5个主分片, 并分别为其创建一个副本分片. 也就是说每个索引都由5个主分片成本, 而每个主分片都相应的有一个copy.
不知道你是否有基于日期的索引需求, 并且对索引数据的搜索场景非常少. 也许这些索引量将达到成百上千, 但每个索引的数据量只有1GB甚至更小. 对于这种类似场景, 我建议你只需要为索引分配1个分片.
如果使用ES的默认配置(5个分片), 并且使用Logstash按天生成索引, 那么6个月下来, 你拥有的分片数将达到890个. 再多的话, 你的集群将难以工作--除非你提供了更多(例如15个或更多)的节点.
想一下, 大部分的Logstash用户并不会频繁的进行搜索, 甚至每分钟都不会有一次查询. 所以这种场景, 推荐更为经济使用的设置. 在这种场景下, 搜索性能并不是第一要素, 所以并不需要很多副本. 维护单个副本用于数据冗余已经足够. 不过数据被不断载入到内存的比例相应也会变高.
如果你的索引只需要一个分片, 那么使用Logstash的配置可以在3节点的集群中维持运行6个月. 当然你至少需要使用4GB的内存, 不过建议使用8GB, 因为在多数据云平台中使用8GB内存会有明显的网速以及更少的资源共享.
ES一旦创建好索引后,就无法调整分片的设置,而在ES中,一个分片实际上对应一个lucene 索引,而lucene索引的读写会占用很多的系统资源,因此,分片数不能设置过大;所以,在创建索引时,合理配置分片数是非常重要的。一般来说,我们遵循一些原则:
6.1 控制每个分片占用的硬盘容量不超过ES的最大JVM的堆空间设置(一般设置不超过32G,参加上文的JVM设置原则),因此,如果索引的总容量在500G左右,那分片大小在16个左右即可;当然,最好同时考虑原则6.2。
6.2 考虑一下node数量,一般一个节点有时候就是一台物理机,如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,即使保持了1个以上的副本,同样有可能会导致数据丢失,集群无法恢复。所以, 一般都设置分片数不超过节点数的3倍。
如果你真的担心数据的快速增长, 我们建议你多关心这条限制: ElasticSearch推荐的最大JVM堆空间是30~32G, 所以把你的分片最大容量限制为30GB, 然后再对分片数量做合理估算. 例如, 你认为你的数据能达到200GB, 我们推荐你最多分配7到8个分片.
不过, 你最好还是能描述出每个节点上只放一个索引分片的必要性. 在开始阶段, 一个好的方案是根据你的节点数量按照1.5~3倍的原则来创建分片. 例如,如果你有3个节点, 则推荐你创建的分片数最多不超过9(3x3)个.
es 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?
1. es 生产集群我们部署了 5 台机器,每台机器是 6 核 64G 的,集群总内存是 320G。
2. 我们 es 集群的日增量数据大概是 2000 万条,每天日增量数据大概是 500MB,每月增量数据大概是 6 亿,15G。目前系统已经运行了几个月,现在 es 集群里数据总量大概是 100G 左右。
3. 目前线上有 5 个索引(这个结合你们自己业务来,看看自己有哪些数据可以放 es 的),每个索引的数据量大概是 20G,所以这个数据量之内,我们每个索引分配的是 8 个 shard,比默认的 5 个 shard 多了 3 个shard。
对于您的写重索引情况,只有一个节点,索引和分片的最佳数量为1!但是对于搜索情况(不按ID进行访问),每个节点的最佳分片数是可用的CPU数。这样,可以在多个线程中进行搜索,从而获得更好的搜索性能。
但是分片有什么好处?
可用性:通过将分片复制到其他节点,即使不再能够访问某些节点,您仍然可以使用!
性能:将主分片分发到不同的节点,也将分配工作负载。
因此,如果您的方案写的很繁琐,请使每个索引的分片数量保持较低。如果需要更好的搜索性能,请增加分片的数量,但要牢记“物理”。如果需要可靠性,请考虑节点/副本的数量。
由于 ES 是一个准实时系统,很多操作都不能实时生效
Lifecycle 的 rollover 之所以不用每次手动执行 rollover 操作是因为 ES 会隔一段时间判断一次索引是否满足 rollover 的条件
ES检测 ILM 策略的时间默认为10min
修改 Lifecycle 配置:
PUT _cluster/settings
{
"transient": {
"indices.lifecycle.poll_interval": "3s"
}
}
{"transient":{"indices.lifecycle.poll_interval":"3s"}}
参考: