一、数据准备


# 1、设置主分片数量、副本等,如果有特殊mapping,需要提前设置好
curl -k -u elastic:Passw0rd -H "Content-Type: application/json" -XPUT "http://xxx.xx.xxx.172:9200/group_test_main" -d  '{"settings":{"number_of_shards":3,"number_of_replicas":1},"mappings":{"properties":{"prod_code":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}}}}}'

curl -u elastic:Passw0rd -XGET xxx.xx.xxx.172:9200/group_test_main?pretty # 查看指定索引结构信息

# 2、创建hive-es外部表,为后续数据更新写入做准备
drop table tmp.group_test_main01;
CREATE TABLE tmp.group_test_main01(
client_id string COMMENT '客户编号',
prod_code string comment '客户可以购买产品集'
)ROW FORMAT SERDE 'org.elasticsearch.hadoop.hive.EsSerDe'
 STORED BY 'org.elasticsearch.hadoop.hive.EsStorageHandler'
 TBLPROPERTIES('es.index.read.missing.as.empty'='true',
'es.field.read.empty.as.null' = 'true',
'es.mapping.id'='client_id',
'es.mapping.routing'='client_id', --路由值,控制数据分片位置 高并发、大数据量读写 慎用,可能出现数据倾斜,热点分片
'es.write.operation'='upsert',
'es.net.http.auth.user'='elastic',
'es.net.http.auth.pass'='Passw0rd',
'es.nodes'='xxx.xx.xxx.172:9200,xxx.xx.xxx.173:9200,xxx.xx.xxx.174:9200',
'es.index.auto.create'='true',
'es.nodes.wan.only'='true',
'es.read.metadata'='true',
'es.refresh' = 'false',  -- 关闭写入后主动刷新
'es.resource'='group_test_main/_doc',
'es.nodes.wan.only' = 'true',
'es.index.read.missing.as.empty'='yes',
'es.net.ssl.protocol'='ssl');

# 3、插入数据
insert overwrite table tmp.group_test_main01
select '11564745' as client_id
,'004807,004913,005725,005994,007262,017653,019982,020224,020225,020855,020856' as prod_code;

# 4、创建hive-es外部表,为后续数据更新写入做准备
DROP TABLE  tmp.group_test_main02;
CREATE TABLE tmp.group_test_main02(
client_id string COMMENT '客户编号',
wid_valid_flag_20241231 int COMMENT '20241231是否宽口径有效户 1是',
wid_valid_flag int COMMENT '是否宽口径有效户 1是'
) ROW FORMAT SERDE 'org.elasticsearch.hadoop.hive.EsSerDe'
STORED BY 'org.elasticsearch.hadoop.hive.EsStorageHandler'
TBLPROPERTIES('es.index.read.missing.as.empty'='true',
'es.field.read.empty.as.null' = 'true',
'es.mapping.id'='client_id',
'es.mapping.routing'='client_id', --路由值,控制数据分片位置 高并发、大数据量读写 慎用,可能出现数据倾斜,热点分片
'es.write.operation'='upsert',
'es.net.http.auth.user'='elastic',
'es.net.http.auth.pass'='Passw0rd',
'es.nodes'='xxx.xx.xxx.172:9200,xxx.xx.xxx.173:9200,xxx.xx.xxx.174:9200',
'es.index.auto.create'='true',
'es.nodes.wan.only'='true',
'es.read.metadata'='true',
'es.resource'='group_test_main/_doc',
'es.nodes.wan.only' = 'true',
'es.index.read.missing.as.empty'='yes',
'es.refresh' = 'false',  -- 关闭写入后主动刷新
-- 'es.batch.size.bytes' = '2097152', -- 单批大小(默认1MB)
-- 'es.refresh' = 'false',  -- 关闭写入后主动刷新
-- 'es.batch.size.bytes' = '2mb', -- 单批大小(默认1MB es大小写敏感)
-- 'es.batch.size.entries' = '2000', -- 单批条目(默认1000,提升吞吐量)
-- 高版本ES适配(若集群>7.x)
-- 'es.nodes.discovery'='false',              -- 关闭自动发现,强制用配置节点
-- 'es.net.ssl.enabled'='true',               -- 若启用SSL,需同步开启(你的原配置有ssl协议)
-- 'es.net.ssl.protocol'='TLSv1.2'            -- 明确SSL协议版本
'es.net.ssl.protocol'='ssl');

# 5、插入数据
insert overwrite table tmp.group_test_main02
select '11564745' as client_id
,0 as wid_valid_flag_20241231
,'1' as wid_valid_flag;


curl -k -u elastic:Passw0rd -XPOST http://xxx.xx.xxx.172:9200/group_test_main/_search?pretty -H 'Content-Type: application/json' -d'{"query":{"match_all":{}}}' #查看所有数据

# 6、删除备份索引 如果备份索引有特殊mapping需提前创建,否则_reindex的时候自动推导mapping,导致使用出问题
curl -u elastic:Passw0rd -XDELETE xxx.xx.xxx.172:9200/group_test_test

curl -k -u elastic:Passw0rd -H "Content-Type: application/json" -XPUT "http://xxx.xx.xxx.172:9200/group_test_test" -d  '{"settings":{"number_of_shards":3,"number_of_replicas":1},"mappings":{"properties":{"prod_code":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}}}}}'

# 7、复制或者备份索引
curl -s -k -u elastic:Passw0rd -XPOST "xxx.xx.xxx.172:9200/_reindex" -H "Content-Type: application/json" -d'{"source": {"index":"group_test_main"},"dest": {"index": "group_test_test"}}'

curl -u elastic:Passw0rd -XGET xxx.xx.xxx.172:9200/group_test_test?pretty # 查看指定索引结构信息


curl -k -u elastic:Passw0rd -XPOST http://xxx.xx.xxx.172:9200/group_test_main/_search?pretty -H 'Content-Type: application/json' -d'{"query":{"match_all":{}}}' #查看所有数据
curl -k -u elastic:Passw0rd -XPOST http://xxx.xx.xxx.172:9200/group_test_test/_search?pretty -H 'Content-Type: application/json' -d'{"query":{"match_all":{}}}' #查看所有数据

es.mapping.routing='client_id' #用client_id字段的值作为路由值,控制数据分片位置。让同一个客户的数据永远落在同一个分片

强制让"同一个客户的所有数据",都写入Elasticsearch集群的同一个分片(Shard)里。
1.作用
Elasticsearch 集群有多台机器、多个分片
默认情况下,数据会随机分散到不同分片
加了 routing=customer_id 后:
同一个 customer_id 的数据,永远只往同一个分片写!
customer_id = 1001 → 固定去 分片1
customer_id = 1002 → 固定去 分片3
customer_id = 1001 → 还去 分片1

查询速度提升10~100倍:不加routing查询要扫所有分片,加routing只需要查一个分片数据量越大,速度差距越恐怖
避免数据错乱:同一个客户的资产、标签、权限数据,全部聚在一个分片,不会分散,聚合、统计、关联都不会出错
2.工作原理
ES会做一件事:
hash(customer_id) % 总分片数 = 目标分片
同一个 ID → 永远同一个结果 → 永远同一个分片
3.使用
使用父子索引(Join 类型) ✅必须加,不加直接报错,无法使用
按用户ID/客户ID聚合查询 ✅必须加,不加聚合结果不准,查询慢10~100倍
高并发、大数据量写入 ✅必须加,不加写入慢,集群不稳定

维度	         不指定routing(默认)	           指定routing(如?routing=user_123)
数据分布	     均匀散落在3个分片(靠_id哈希)	   所有相同routing值的文档强制进入同一个分片
查询范围	     默认查全部3个分片	               只查1个分片(由routing值计算得出)
查询性能	     较慢(需合并3个分片结果)      	   快2–10倍(单分片本地执行)
关联数据存储	 父子文档可能在不同分片→join失败   可确保父子/用户订单等强关联数据同分片
数据倾斜风险	 极低(均匀分布)	               高:若user_123数据量极大→热点分片
查询一致性要求 无需关心routing	               必须索引和查询用相同routing否则查不到

二、例行跑批


1、索引备份

#!/bin/bash

# 开启主索引备份机制
curl -s -k -u elastic:Passw0rd -XPUT 'xxx.xx.xxx.172:9200/group_tag_all_boci_main/_settings' -H 'Content-Type: application/json' -d '{"number_of_replicas": 1,"refresh_interval":"1s"}'

# 备份索引数据
#关闭备份索引日志备份
# curl -s -k -u elastic:Passw0rd -XPUT 'xxx.xx.xxx.172:9200/group_tag_all_boci_back/_settings' -H 'Content-Type: application/json' -d '{"number_of_replicas": 0,"refresh_interval":-1}'

# 删除备份索引
curl -u elastic:Passw0rd -XDELETE xxx.xx.xxx.172:9200/group_tag_all_boci_back

#创建索引
curl -k -u elastic:Passw0rd -H "Content-Type: application/json" -XPUT "http://xxx.xx.xxx.172:9200/group_tag_all_boci_back" -d  '{"settings":{"number_of_shards":3,"number_of_replicas":0},"mappings":{"properties":{"client_acted_task":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}},
"stock_hldp_list":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}},
"client_exchanged_goods":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}},
"prod_code":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}},
"discount_coupon_invl_after_1":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}},
"discount_coupon_invl_after_2_3":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}},
"discount_coupon_invl_after_4_7":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}},
"zzcp":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}},
"client_joined_active":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":10240}}}}}}'


#备份模型数据备份
curl -s -k -u elastic:Passw0rd -XPOST "xxx.xx.xxx.172:9200/_reindex" -H "Content-Type: application/json" -d'{"source": {"index":"group_tag_all_boci_main"},"dest": {"index": "group_tag_all_boci_back"}}'

2、索引数据清理

#!/bin/bash

# 将索引切换为备份索引
curl -u elastic:Passw0rd -XPOST 'xxx.xx.xxx.172:9200/_aliases' -H 'Content-Type: application/json' -d '{"actions":[{"remove":{"index": "group_tag_all_boci_main","alias":"group_tag_all_boci"}},{"add": {"index": "group_tag_all_boci_back","alias":"group_tag_all_boci"}}]}'


#开启备份索引日志备份
curl -s -k -u elastic:Passw0rd -XPUT 'xxx.xx.xxx.172:9200/group_tag_all_boci_back/_settings' -H 'Content-Type: application/json' -d '{"number_of_replicas": 1,"refresh_interval":"1s"}'


# 关闭主索引日志备份
curl -s -k -u elastic:Passw0rd -XPUT 'xxx.xx.xxx.172:9200/group_tag_all_boci_main/_settings' -H 'Content-Type: application/json' -d '{"number_of_replicas": 0,"refresh_interval":-1}'


#删除主索引数据

curl -u elastic:Passw0rd -XPOST 'xxx.xx.xxx.172:9200/group_tag_all_boci_main/_delete_by_query?pretty' -H 'Content-Type: application/json' -d '{"query":{"match_all":{}}}'

# 仅清理包含已删除文档的段,不强制合并所有段,减少I/O和CPU消耗
curl -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_tag_all_boci_main/_forcemerge?only_expunge_deletes=true"

注意!!!!

执行的_delete_by_query是逻辑删除,不会立即释放存储,也不会让docs.count直接归0(需等段合并),会导致main索引还有数据。
具体原因和验证/处理步骤如下:
2.1 逻辑删除的本质
_delete_by_query只是给文档打上删除标记,文档仍存在于Lucene段中, docs.count不会立刻清零,只有当段合并(_forcemerge)执行时,才会真正清理这些标记的文档,释放空间。
2.2 验证是否删除成功
执行查询语句,确认main索引是否还有可查询数据:

bash
curl -u elastic:Passw0rd -XGET 'xxx.xx.xxx.172:9200/group_tag_all_boci_main/_search?size=0' -H 'Content-Type: application/json'

如果返回hits.total.value=0 ,说明数据已被逻辑删除,只是段未合并。
2.3 彻底清理main索引数据

#对main索引执行强制段合并,清理已标记删除的文档,同时合并段为1个,后续索引的元数据占用会更少,最大化降低存储和查询开销
bash
curl -u elastic:Passw0rd -XPOST 'xxx.xx.xxx.172:9200/group_tag_all_boci_main/_forcemerge?only_expunge_deletes=true&max_num_segments=1'
# 仅清理包含已删除文档的段,不强制合并所有段,减少I/O和CPU消耗
curl -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_tag_all_boci_main/_forcemerge?only_expunge_deletes=true"
# 注意:_forcemerge是重量级操作,建议在低峰期执行。

3、索引切换

#!/bin/bash

#等待Hive写入ES完全结束,避免合并和写入冲突
sleep 600s

# 仅清理包含已删除文档的段,不强制合并所有段,仅清理删除占比≥10%的段,减少I/O和CPU消耗,释放磁盘空间,不合并正常段(轻量)
curl -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_tag_all_boci_main/_forcemerge?only_expunge_deletes=true"

# 开启主索引备份及自动刷新
curl -s -k -u elastic:Passw0rd -XPUT 'xxx.xx.xxx.172:9200/group_tag_all_boci_main/_settings' -H 'Content-Type: application/json' -d '{"number_of_replicas": 1,"refresh_interval":"1s"}'

#等待副本分片同步完成,避免别名切换时副本未就绪
sleep 300s

# 将索引切换为主索引
curl -u elastic:Passw0rd -XPOST 'xxx.xx.xxx.172:9200/_aliases' -H 'Content-Type: application/json' -d '{"actions":[{"remove":{"index": "group_tag_all_boci_back","alias":"group_tag_all_boci"}},{"add": {"index": "group_tag_all_boci_main","alias":"group_tag_all_boci"}}]}'

#关闭备份索引日志备份
curl -s -k -u elastic:Passw0rd -XPUT 'xxx.xx.xxx.172:9200/group_tag_all_boci_back/_settings' -H 'Content-Type: application/json' -d '{"number_of_replicas": 0,"refresh_interval":-1}'

三、验证数据


验证es源索引和目标索引(通过_reindex或快照恢复得到的)数据是否完全一致
方法一:使用_shards API(检查索引健康和分布)
这是一个辅助检查,用于确保索引的物理分片是健康的,这是数据一致性的基础。

curl -s -k -u elastic:Passw0rd "http://xxx.xx.xxx.172:9200/_cat/shards/group_test*?v"
结果
index           shard prirep state   docs store ip             node
group_test_test 2     r      STARTED    0  226b xxx.xx.xxx.174 Elasticsearch-230502205157_ydhy-dmp3
group_test_test 2     p      STARTED    0  226b xxx.xx.xxx.172 Elasticsearch-230502205157_ydhy-dmp1
group_test_test 1     p      STARTED    0  226b xxx.xx.xxx.173 Elasticsearch-230502205157_ydhy-dmp2
group_test_test 1     r      STARTED    0  226b xxx.xx.xxx.172 Elasticsearch-230502205157_ydhy-dmp1
group_test_test 0     p      STARTED    1 5.6kb xxx.xx.xxx.174 Elasticsearch-230502205157_ydhy-dmp3
group_test_test 0     r      STARTED    1 5.6kb xxx.xx.xxx.173 Elasticsearch-230502205157_ydhy-dmp2
group_test_main 2     r      STARTED    0  226b xxx.xx.xxx.174 Elasticsearch-230502205157_ydhy-dmp3
group_test_main 2     p      STARTED    0  226b xxx.xx.xxx.172 Elasticsearch-230502205157_ydhy-dmp1
group_test_main 1     p      STARTED    0  226b xxx.xx.xxx.173 Elasticsearch-230502205157_ydhy-dmp2
group_test_main 1     r      STARTED    0  226b xxx.xx.xxx.172 Elasticsearch-230502205157_ydhy-dmp1
group_test_main 0     p      STARTED    1 5.6kb xxx.xx.xxx.174 Elasticsearch-230502205157_ydhy-dmp3
group_test_main 0     r      STARTED    1 5.6kb xxx.xx.xxx.173 Elasticsearch-230502205157_ydhy-dmp2
结论:
state必须是STARTED。如果是INITIALIZING,RELOCATING或UNASSIGNED,说明索引还未准备就绪。
主分片(prirep=p)和副本分片(prirep=r)的docs数量应该完全相同。

方法二:检查文档总数(最快、最基础)
使用_cat/countAPI分别获取源索引和目标索引的文档总数。

#检查源索引group_test_main的文档数
curl -s -k -u elastic:Passw0rd "http://xxx.xx.xxx.172:9200/_cat/count/group_test_main?v"

#检查目标索引group_test_test的文档数
curl -s -k -u elastic:Passw0rd "http://xxx.xx.xxx.172:9200/_cat/count/group_test_test?v"
结果
epoch      timestamp count
1767676882 05:21:22  1
epoch      timestamp count
1767676882 05:21:22  1

方法三:使用_search的track_total_hits(更精确的计数)
_cat/count是一个轻量级的统计,但在某些极端情况下(如索引正在被大量更新),它的值可能不是绝对精确的。使用_search并设置track_total_hits:true可以得到更精确的总数。
对两个索引分别执行一个不返回任何文档的搜索,并获取hits.total.value.

#精确计算源索引的文档数
curl -s -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_test_main/_search?filter_path=hits.total" -H "Content-Type: application/json" -d'{"size": 0,"track_total_hits": true}'

#精确计算目标索引的文档数
curl -s -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_test_test/_search?filter_path=hits.total" -H "Content-Type: application/json" -d'{"size": 0,"track_total_hits": true}'
结果
{"hits":{"total":{"value":1,"relation":"eq"}}}{"hits":{"total":{"value":1,"relation":"eq"}}}
relation:"eq"表示value是精确的文档总数。

方法四:抽样检查文档内容(验证数据完整性)
在确认总数一致后,需要随机抽取一些文档,对比它们在源索引和目标索引中的内容是否完全相同。

操作步骤:
1、从源索引随机获取一个文档的ID和内容。使用function_score查询可以实现随机抽样。
# 从源索引随机获取一个文档
curl -s -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_test_main/_search?size=1" -H "Content-Type: application/json" -d'{"query": {"function_score": {"query": { "match_all": {} },"random_score": {}}}}'
从返回结果中记下这个文档的_id 和_source。
{"took":117,"timed_out":false,"_shards":{"total":3,"successful":3,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":0.38136137,"hits":[{"_index":"group_test_main","_type":"_doc","_id":"11564745","_score":0.38136137,"_source":{"client_id":"11564745","prod_code":"004807,004913,005725,005994,007262,017653,019982,020224,020225,020855,020856","wid_valid_flag_20241231":1,"wid_valid_flag":1}}]}}
2、使用这个ID在目标索引中查询对应的文档。
#上一步获取的ID是"11564745"
curl -s -k -u elastic:Passw0rd -XGET "http://xxx.xx.xxx.172:9200/group_test_test/_doc/11564745"
{"_index":"group_test_test","_type":"_doc","_id":"11564745","_version":2,"_seq_no":1,"_primary_term":1,"found":true,"_source":{"client_id":"11564745","prod_code":"004807,004913,005725,005994,007262,017653,019982,020224,020225,020855,020856","wid_valid_flag_20241231":1,"wid_valid_flag":1}}
3、手动或使用脚本对比两个_source的内容。确保所有字段的值都完全一致。建议多抽取几个(如10-20个)不同的文档进行对比。

方法五:使用_validate API(高级,检查查询一致性)
这个方法可以验证对于一个给定的查询,源索引和目标索引的行为是否一致。它不会返回文档内容,而是检查查询的内部执行计划和结果计数是否相同。

# 验证一个简单的 match_all 查询
curl -s -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_test_main/_validate/query?rewrite=true" -H "Content-Type: application/json" -d'{"query": { "match_all": {} }}'

# 在目标索引上执行同样的查询
curl -s -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_test_test/_validate/query?rewrite=true" -H "Content-Type: application/json" -d'{"query": { "match_all": {} }}'
结果
{"_shards":{"total":1,"successful":1,"failed":0},"valid":true,"explanations":[{"index":"group_test_main","valid":true,"explanation":"*:*"}]}
{"_shards":{"total":1,"successful":1,"failed":0},"valid":true,"explanations":[{"index":"group_test_test","valid":true,"explanation":"*:*"}]}
结论:如果两个请求的返回结果完全相同,说明索引的结构(Mapping)和数据对于这个查询的理解是一致的。你可以将match_all替换为更复杂的业务查询,来进行更深度的验证。

总结与推荐流程
对于一次重要的数据迁移或备份恢复,推荐的检查流程是:
第一步:检查索引健康使用_cat/shards确保源和目标索引的所有分片都处于STARTED状态。
第二步:校验文档总数使用_search配合track_total_hits:true来精确比较两个索引的文档总数。这是最关键的一步。
第三步:抽样检查数据内容随机抽取多个文档,对比源和目标索引中同ID文档的_source内容,确保数据没有损坏或丢失。
(可选)第四步:深度业务查询验证如果有核心的业务查询,可以使用_validate/query或直接执行查询,对比返回的hits.total是否一致。
通过以上步骤,就可以确认两个索引的数据是否是一致的。

四、验证写入是否完成

1、查看 ES 的 write 线程池状态

curl -s -k -u elastic:Passw0rd http://xxx.xx.xxx.172:9200/_cat/thread_pool/write?v
node_name                            name  active queue rejected
Elasticsearch-230502205157_ydhy-dmp2 write      0     0        0
Elasticsearch-230502205157_ydhy-dmp3 write      1     0        0
Elasticsearch-230502205157_ydhy-dmp1 write      0     0        0

active/queue/rejected三个字段是判断更新状态的关键,对应ES写操作(update/index/bulk)的执行状态:
字段	        含义	                            更新完成的判定条件
active	    正在执行的更新请求数(活跃任务数)	=0(无正在执行的更新)
queue	    排队等待执行的更新请求数	        =0(无待执行的更新)
rejected	因资源不足被拒绝的更新请求数	        =0(无请求被拒,集群无压力)
更新完成的最终状态:所有节点的active=0+queue=0+rejected=0,且该状态持续稳定(不是瞬时值)。

通用验证方法(不止看日志,精准确认)
若要100%确认ES更新是否完成,除了看日志,还可通过ES的REST API直接查询集群/任务状态(推荐):
方法1:查询ES集群所有正在执行的任务

curl -s -k -u elastic:Passw0rd -XGET "http://xxx.xx.xxx.172:9200/_cat/tasks?v"

若返回结果为空,或无「write/update/index/bulk」相关任务→无活跃更新任务;
若有相关任务→任务执行中,更新未完成。
方法2:查询集群健康状态

curl -s -k -u elastic:Passw0rd -XGET "http://xxx.xx.xxx.172:9200/_cluster/health?pretty"
{
"cluster_name" : "Elasticsearch-230502205157",
"status" : "green",
"timed_out" : false,
"number_of_nodes" : 3,
"number_of_data_nodes" : 3,
"active_primary_shards" : 49,
"active_shards" : 93,
"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(集群健康);
pending_tasks:需为0(无待执行的集群任务,包括更新相关)。

2、查看分片是否都正常(最常用)

curl -u elastic:Passw0rd -XGET "http://xxx.xx.xxx.172:9200/_cat/shards/group_tag_all_boci_main?v"
# index                   shard prirep state      docs  store ip             node
group_tag_all_boci_main 1     p      STARTED 2505765    9gb xxx.xx.xxx.173 Elasticsearch-230502205157_ydhy-dmp2
group_tag_all_boci_main 1     r      STARTED 2505765  9.5gb xxx.xx.xxx.172 Elasticsearch-230502205157_ydhy-dmp1
group_tag_all_boci_main 2     r      STARTED 2505245  9.2gb xxx.xx.xxx.174 Elasticsearch-230502205157_ydhy-dmp3
group_tag_all_boci_main 2     p      STARTED 2505245  8.9gb xxx.xx.xxx.172 Elasticsearch-230502205157_ydhy-dmp1
group_tag_all_boci_main 0     p      STARTED 2506315 13.9gb xxx.xx.xxx.174 Elasticsearch-230502205157_ydhy-dmp3
group_tag_all_boci_main 0     r      STARTED 2506315 15.9gb xxx.xx.xxx.173 Elasticsearch-230502205157_ydhy-dmp2
#state列全部是STARTED => 分片就绪
#没有UNASSIGNED、INITIALIZING、RELOCATING
#主分片(pri)和副本(rep)的docs数量一致 =>数据同步完成
#→分片正常、数据同步完了。

3、查看索引状态/写入是否结束(看更新是否完成) index_current (=0:没有正在写入/更新的请求,>0:还在跑更新)

curl -u elastic:Passw0rd -XGET "http://xxx.xx.xxx.172:9200/group_tag_all_boci_main/_stats?pretty"

3.1.基础索引信息(确认索引存在)

{
  "_shards": {
    "total": 10,    //索引总分片数(主分片+副本分片)
    "successful": 10, //成功返回状态的分片数
    "failed": 0     //失败的分片数(0=所有分片正常)
  },
  "indices": {
    "group_tag_all_boci_main": {
      "uuid": "xxx",
      "status": "open", //索引状态:open=正常可用,close=关闭(无法读写)
      //核心状态字段↓

3.2. 文档统计(判断更新后文档数是否符合预期)

"docs": {
  "count": 100000,  //索引中当前的文档总数(更新后应与预期一致)
  "deleted": 500     //标记为删除的文档数(不影响更新判断,仅参考)
},

判断点:count数值是否等于「更新后预期的文档数」(比如更新了1000条,count应比更新前多1000)。

3.3.写入/更新操作统计(核心!判断是否有更新在执行)

"indexing": {
  "index_total": 1200,  // 累计写入/更新操作总数
  "index_time_in_millis": 500, // 累计写入耗时(毫秒)
  "index_current": 0,    // 当前正在执行的写入/更新操作数(关键!)
  "index_failed": 0,     // 写入/更新失败数(0=无失败)
  "delete_total": 0,
  "delete_time_in_millis": 0,
  "delete_current": 0
},

核心判断:index_current=0→无正在执行的更新操作;index_failed=0→所有更新都成功执行。

3.4.基于该命令的「更新完成」判断标准
结合返回结果,满足以下所有条件→索引group_tag_all_boci_main的更新已完成:
_shards.failed=0→所有分片正常响应;
indices.group_tag_all_boci_main.status=open→索引未关闭;
indexing.index_current=0→无正在执行的更新操作;
indexing.index_failed=0→无更新操作失败;
docs.count等于更新后预期的文档数;
所有分片state=STARTED,且副本分片文档数与主分片一致。

异常现象	          原因	            解决方法
index_current>0	  仍有更新操作在执行	等待一段时间后重新查询,直到变为0
index_failed>0	  部分更新失败	    查看ES集群日志(_cat/logs),排查失败原因(如字段类型不匹配)
_shards.failed>0  分片不可用	        执行_cat/shards/group_tag_all_boci_main查看分片状态,重启异常节点
副本分片count不一致 数据同步未完成	    等待分片同步,或执行POST/_flush/synced强制同步

五、单条验证

5.1查看client_owner_intensive_station字段不为空的数量

curl -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_tag_all_boci/_search?pretty" -H 'Content-Type: application/json' -d '{
  "size": 0,
  "query": {
    "bool": {
      "must": [
        { "exists": { "field": "client_owner_intensive_station" } }
      ]
    }
  }
}'

"size": 0 → 不返回具体数据,只统计数量
"exists": { "field": "client_owner_intensive_station" }→字段存在且不为null、不为空字符串
"hits" : {
  "total" : {
    "value" : 12345,   <--不为空数量
    "relation" : "eq"
  },
  ...
}

5.2强制ES扫描全部数据
显示10000但是实际有78854
ES默认最多只返回10000条统计,超过的会被截断
解决办法:加上track_total_hits: true

curl -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_tag_all_boci/_search?pretty" -H 'Content-Type: application/json' -d '{
  "size": 0,
  "track_total_hits": true,
  "query": {
    "bool": {
      "must": [
        { "exists": { "field": "client_owner_intensive_station" } }
      ]
    }
  }
}'
track_total_hits:true强制ES扫描全部数据,返回真实总数,而不是截断到10000
"total" : {
  "value" : 78854,    <-- 真实数量
  "relation" : "eq"
}

5.3总数/不为空/为空,精确显示真实数量

curl -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_tag_all_boci/_search?pretty" -H 'Content-Type: application/json' -d '{
  "size": 0,
  "track_total_hits": true,
  "aggs": {
    "field_status": {
      "filters": {
        "filters": {
          "不为空数量": {
            "exists": { "field": "client_owner_intensive_station" }
          },
          "为空数量": {
            "bool": {
              "must_not": [
                { "exists": { "field": "client_owner_intensive_station" } }
              ]
            }
          }
        }
      }
    }
  }
}'

结果查看位置
total.value = 索引总条数
aggregations.field_status.buckets.不为空数量.doc_count = 有值条数
aggregations.field_status.buckets.为空数量.doc_count = 无值条数

nvl(n.client_owner_intensive_station,‘’) as client_owner_intensive_station
如果是null→转成空字符串’',ES里的数据只有两种:
正常内容
空字符串 “”(包括纯空格)
最终统计:空字符串/非空字符串

curl -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_tag_all_boci/_search?pretty" -H 'Content-Type: application/json' -d '{
  "size": 0,
  "track_total_hits": true,
  "aggs": {
    "result": {
      "filters": {
        "filters": {
          "正常数据(非空)": {
            "bool": {
              "must": [
                {"exists": {"field": "client_owner_intensive_station"}},
                {"wildcard": {"client_owner_intensive_station": "*?*"}},
                {"bool": {"must_not": [{"wildcard": {"client_owner_intensive_station": "^\\s*$"}}]}}
              ]
            }
          },
          "空/空格数据": {
            "bool": {
              "should": [
                {"bool": {"must_not": {"exists": {"field": "client_owner_intensive_station"}}}},
                {"wildcard": {"client_owner_intensive_station": ""}},
                {"wildcard": {"client_owner_intensive_station": " "}},
                {"wildcard": {"client_owner_intensive_station": "  "}},
                {"wildcard": {"client_owner_intensive_station": "   "}}
              ]
            }
          }
        }
      }
    }
  }
}'

正常数据(非空)→有内容的
空/空格数据 → ''或null或空格

5.4统计wid_valid_flag = 1的文档数量

curl -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_tag_all_boci/_search?pretty" -H 'Content-Type: application/json' -d '{
  "size": 0,
  "track_total_hits": true,
  "query": {
    "term": {
      "wid_valid_flag": "1"
    }
  }
}'

如果想一次统计=1和!=1(更完整)

curl -k -u elastic:Passw0rd -XPOST "http://xxx.xx.xxx.172:9200/group_tag_all_boci/_search?pretty" -H 'Content-Type: application/json' -d '{
  "size": 0,
  "track_total_hits": true,
  "aggs": {
    "status": {
      "filters": {
        "filters": {
          "wid_valid_flag = 1": {
            "term": { "wid_valid_flag": "1" }
          },
          "wid_valid_flag != 1": {
            "bool": {
              "must_not": [
                { "term": { "wid_valid_flag": "1" } }
              ]
            }
          }
        }
      }
    }
  }
}'
总数:7734061
wid_valid_flag=1:XXX条
wid_valid_flag≠1:XXX条
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐