-------------------------------------------------------
作者:尚雷
Oracle、PostgreSQL ACE
如喜欢文章内容,可关注我公众号:
-------------------------------------------------------
这篇文章给大家描述如果遇到生产Elasticsearch因有unassigned_shard的索引导致集群状态为red,引发应用无法正常写入Elasticsearch,MQ有挤压该如何处理。
集群状态集群状态为何会异常
我们知道Elasticsearch集群健康状态分为三种,分别是:
-
GREEN
-
YELLOW
-
RED
GREEN: 代表集群当前处于健康状态,说明其所有分片及副本都是可用的。此时,Elasticsearch集群的所有主分片和副本分片都已正常分配,集群可以正常对外提供服务。
YELLOW: 该种状态表示主分片可用,但副本分片不可用,该种情况表明Elasticsearch集群中所有主分片都已分配,但至少有一个副本分片未正常分配,该种状态下是不会有数据丢失的,也是可以对外提供搜索的,这种情况也经常会发生,但通常会持续时间比较短,通常会自动恢复,可以这么比喻YELLOW代表集群处于亚健康状态,如果调养好还能恢复正常。
RED: 如果集群状态为RED,表示Elasticsearch已处于不健康状态,集群此时可能难以正常对外提供服务,存在不可用的主分片,虽然查询时可以查询到部分数据,但已经影响到正常的索引读写,此时需要得到及时处理。当Elasticsearch状态为RED,意味部分索引缺失数据,无法获取到全部数据。
如何查询集群状态
可通过如下命令查询当前集群状态。
命令方式
GET /_cluster/health?pretty
-- 若状态为正常,则查询到的信息类似如下:
{
"cluster_name" : "es-cluster",
"status" : "green",
"timed_out" : false,
"number_of_nodes" : 10,
"number_of_data_nodes" : 10,
"active_primary_shards" : 13814,
"active_shards" : 15874,
"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
}
当集群状态为RED,可通过命令进一步分析有哪些索引异常,查询的方式有多种,可通过命令或者图形方式进行查询。
命令查询
GET /_cat/indices?v
-- 查询结果类似如下
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
green open xxxx_xxx_r_2024-06-1w Et76c4q0So6h7lB0DoeNmw 1 0 420324 28 221.5mb 221.5mb
green open xxxx_app_log_2020-01-31 JnhjrFviQSClnRy6HbTKbA 3 0 1 0 37.7kb 3
-- health若为GREEN表示索引分片正常
图形查询通过kibana可通过管理界面进行查询,打开kibana管理界面,依次打开Stack Management-->索引管理,可查看当前各索引分片状态,如下所示,此时展示部分索引分片状态异常,集群状态为RED。
图形方式
通过kibana或其它第三方监控工具,可查看当前Elasticsearch集群状态,如下所示通过kibana查询到的集群状态。
上图表示此时集群处于健康状态,若集群不正常,会提示运行状况为YELLOW或RED。
处理办法如何处理索引异常状态
1为何要处理异常索引
当索引分片异常,导致该索引状态为RED,此时整个Elasticsearch集群状态为RED,难以正常对外提供完整的查询。在我所负责的Elasticsearch生产集群环境中,当集群处于该种状态,MQ无法正常写入ES中这些状态异常的索引,造成大量消息队列挤压,业务人员无法正常查询到业务数据。
2如何处理异常状态索引
初期,当收到Elasticsearch告警提示集群状态异常,信息提示:Cluster status is RED!!!!,通常为了快速处理问题,通常会通过kibana或者命令查询到哪些索引状态异常,然后通过关闭/打开索引快速进行处理。后期,发现这种方式也比较慢,特别是当遇到业务繁忙,kibana打开又比较慢,通常会花较长时间去处理,所以我就通过编写脚本在Elasticsearch服务器上进行处理,初期编写的脚步如下:
#!/bin/bash
ES_HOST="xxx.xxx.xxx.xxx"
ES_PORT="9200"
ES_USER="elastic"
ES_PASS="xxxx"
# 查询所有状态为red的索引
echo "查询所有状态为red的索引..."
RED_INDICES=$(curl -s -u $ES_USER:$ES_PASS "http://$ES_HOST:$ES_PORT/_cat/indices?health=red&h=index" | tr '\n' ' ')
if [ -z "$RED_INDICES" ]; then
echo "没有发现状态为red的索引。"
else
echo "下列索引状态为red:"
echo "$RED_INDICES" | tr ' ' '\n'
# 关闭所有red状态的索引
for index in $RED_INDICES; do
echo "正在关闭索引: $index"
curl -X POST -s -u $ES_USER:$ES_PASS "http://$ES_HOST:$ES_PORT/$index/_close" -H "Content-Type: application/json"
done
# 等待一段时间后重新打开索引
echo "等待一段时间后重新打开索引..."
sleep 30 # 这里的30只是示例,具体时间需要根据实际情况调整
for index in $RED_INDICES; do
echo "正在打开索引: $index"
curl -X POST -s -u $ES_USER:$ES_PASS "http://$ES_HOST:$ES_PORT/$index/_open" -H "Content-Type: application/json"
done
# 再次检查仍然为red状态的索引
echo "检查索引状态..."
sleep 30 # 给ES一些时间来更新状态
NEW_RED_INDICES=$(curl -s -u $ES_USER:$ES_PASS "http://$ES_HOST:$ES_PORT/_cat/indices?health=red&h=index" | tr '\n' ' ')
if [ -z "$NEW_RED_INDICES" ]; then
echo "没有索引处于red状态。"
else
echo "以下索引仍处于red状态:"
echo "$NEW_RED_INDICES" | tr ' ' '\n'
fi
fi
当再次收到Elasticsearch提示索引异常状态告警时,我会登录服务器手工执行该脚本去查询异常索引,然后将其关闭和打开。
但在运维过程后期发现一些问题,特别是当集群节点宕,此时集群各节点索引reblance未平衡结束,此时会有大量索引状态为RED,该状态下执行这个脚本,会导致需要遍历查询大量异常状态索引,然后逐个对其关闭和打开,会非常耗时。
另外也遇到过当出现多个索引状态异常,在关闭/打开这些索引时,由于Kibana的刷新频率,在分页较多的情况下,可能这些异常索引不会在首页展示,会在其它分页中,导致应用无法写入这些关闭的索引。
后期,当我们优化并升级了Elasticsearch的JDK,Elasticsearch出现节点宕的频率已经比较低,而且恢复速度相对提升了很高,为了更好的处理异常索引,我对上述脚本进行了优化,添加了判断条件,当异常索引数量较多,比如大于4个,此时会先不执行该脚本,待异常索引数量少于4个,会对其进行处理,为了降低手工处理时间,我将该脚本添加到crontab定时任务中去执行,每隔两分钟会执行该脚本,通过近期观察,运行效果良好,修改后的脚步如下:
#!/bin/bash
ES_HOST="xxx.xxx.xxx.xxx"
ES_PORT="9200"
ES_USER="elastic"
ES_PASS="xxxx"
LOG_FILE="/home/esuser/scripts/es_indices_check.log"
# 添加日志时间戳
echo "执行时间: $(date)" >> $LOG_FILE
# 查询所有状态为red的索引
echo "查询所有状态为red的索引..." >> $LOG_FILE
RED_INDICES=$(curl -s -u $ES_USER:$ES_PASS "http://$ES_HOST:$ES_PORT/_cat/indices?health=red&h=index" | tr '\n' ' ')
if [ -z "$RED_INDICES" ]; then
echo "没有发现状态为red的索引。" >> $LOG_FILE
else
# 统计red状态索引的数量
RED_INDICES_COUNT=$(echo "$RED_INDICES" | wc -w)
if [ "$RED_INDICES_COUNT" -gt 4 ]; then
echo "状态为red的索引数量超过4个,请手工处理。" >> $LOG_FILE
echo "下列索引状态为red:" >> $LOG_FILE
echo "$RED_INDICES" | tr ' ' '\n' >> $LOG_FILE
else
echo "下列索引状态为red:" >> $LOG_FILE
echo "$RED_INDICES" | tr ' ' '\n' >> $LOG_FILE
# 关闭所有red状态的索引
for index in $RED_INDICES; do
echo "正在关闭索引: $index" >> $LOG_FILE
curl -X POST -s -u $ES_USER:$ES_PASS "http://$ES_HOST:$ES_PORT/$index/_close" -H "Content-Type: application/json"
done
# 等待一段时间后重新打开索引
echo "等待一段时间后重新打开索引..." >> $LOG_FILE
sleep 30 # 这里的30只是示例,具体时间需要根据实际情况调整
for index in $RED_INDICES; do
echo "正在打开索引: $index" >> $LOG_FILE
curl -X POST -s -u $ES_USER:$ES_PASS "http://$ES_HOST:$ES_PORT/$index/_open" -H "Content-Type: application/json"
done
# 再次检查仍然为red状态的索引
echo "检查索引状态..." >> $LOG_FILE
sleep 30 # 给ES一些时间来更新状态
NEW_RED_INDICES=$(curl -s -u $ES_USER:$ES_PASS "http://$ES_HOST:$ES_PORT/_cat/indices?health=red&h=index" | tr '\n' ' ')
if [ -z "$NEW_RED_INDICES" ]; then
echo "没有索引处于red状态。" >> $LOG_FILE
else
echo "以下索引仍处于red状态:" >> $LOG_FILE
echo "$NEW_RED_INDICES" | tr ' ' '\n' >> $LOG_FILE
fi
fi
fi
--- 然后将该脚本放入定时任务中执行
*/2 * * * * /home/esuser/scripts/check_red.sh
执行结果会写入/home/esuser/scripts/es_indices_check.log日志。同时我又编写了另一个脚本,会定期对该日志文件进行清理防止文件过大,清理脚本如下:
#!/bin/bash
LOG_FILE="/home/esuser/scripts/es_indices_check.log"
# 清理日志文件
> $LOG_FILE
echo "日志文件已清理于: $(date)" >> $LOG_FILE
--- 我也将该脚本放入定时任务中执行,如下所示:
0 0 */2 * * /home/esuser/scripts/clean_log.sh
以上是一个处理示例
希望你也能提供更好的处理方法
(如果有更好方法希望告诉我)