7752 字
39 分钟
ELK系列(二):核心概念、索引与文档操作、Mapping 与 IK 分词器详解

一、引言与背景#

很多运维同学跟着教程把 ElasticSearch 部署起来后,就卡在「然后呢」——面对 Kibana Dev Tools 里一堆 JSON 请求无从下手;单节点集群健康状态常年 yellow,以为是故障到处重装;想改索引分片数发现根本改不了;写入中文数据后搜索完全搜不到。这些问题的根源都是只学会了「装」,没理解 ES 的核心机制与 API 使用方式。

本文目标读者

已完成 ElasticSearch 单机/集群部署(即本系列第一篇内容)的运维工程师,尚未系统使用过 ES REST API。

读完本文你将获得以下实战能力:

  • 理解集群/节点/索引/分片/副本等核心术语与分片路由机制,能独立判断集群健康状态
  • 熟练使用 RESTful API 完成索引管理与文档 CRUD、批量操作
  • 看得懂、改得动 Mapping,能配合开发完成字段类型定义
  • 掌握 IK 中文分词器的安装、两种分词模式与自定义词典配置

二、核心实战模块区#

本章是全文的主战场,我们将按照「概念 → 语法 → 索引 → 文档 → Mapping → 分词器」的递进顺序逐一击破。每个模块都遵循「前置知识 → 核心术语 → 实战演示」的结构,所有命令均可直接复制到你的环境中验证,确保你知其然更知其所以然。

ES 的基础概念#

在敲任何一条命令之前,必须先把 ES 的「世界观」建立起来。本模块将用运维最熟悉的 MySQL 体系做类比,帮你一次性理清 ES 的术语体系,并通过实战吃透分片机制与集群健康状态的判断方法。

本模块前置知识#

  • 已完成 ES 部署并能正常访问 9200 端口(第一篇内容)
  • 会使用 curl 或 Kibana Dev Tools 发起 HTTP 请求

本模块核心术语#

下表是 ES 术语与 MySQL 体系的类比对照,这是理解后续所有操作的基石,建议对照多读两遍:

ES 术语类比 MySQL说明
集群(Cluster)一组 MySQL 主从一组 ES 节点协同工作、统一对外服务
节点(Node)一个 MySQL 实例集群里的一台 ES 实例
索引(Index)库/表写入数据的逻辑单元,一类相似文档的集合
分片(Shard)分库分表中的「分表」索引水平切分的数据块,本质是一个 Lucene 索引
副本(Replica)只读从库主分片的拷贝,提供冗余与读分流
文档(Document)表里的「一行」实际存储的一条 JSON 数据
字段(Field)表里的「列」文档里的 key
映射(Mapping)Schema 定义字段的类型、是否可检索、分词器等结构定义
分词器(Analyzer)无对应物把文本切成「词项(term)」的组件,全文检索的基础
DSLSQLES 专有的 JSON 格式查询语言
节点角色:集群里的分工#

一台 ES 节点可以身兼数职,理解角色划分是理解集群架构的前提:

角色职责类比
主节点(Master)管理集群元数据:创建/删除索引、分片分配决策项目经理,不干活只调度
数据节点(Data)真正存储分片、执行 CRUD 与搜索一线工人,资源消耗大户
协调节点(Coordinating)接收客户端请求、路由转发、汇总结果前台接待,每个节点默认兼任

小规模集群通常让节点身兼全部角色;生产大集群建议角色分离,避免主节点被数据压力拖垮导致集群脑裂。

倒排索引:全文检索为什么快#

传统数据库是「文档 → 词」的正排结构,查「哪些文档包含某词」必须全表扫描;ES 底层 Lucene 反过来建立「词 → 文档列表」的倒排索引(Inverted Index)。写入时分词器把文本切成词项,每个词项记录它出现在哪些文档中;搜索时查词项表即可毫秒级定位候选文档。

graph LR
    subgraph 正排["正排思路(MySQL)"]
        D1["文档1"] -->|"包含"| W1["弹性 / 云 / 主机"]
    end
    subgraph 倒排["倒排思路(Lucene)"]
        T1["词:弹性"] -->|"出现在"| L1["文档1, 文档7, 文档23"]
    end

这就是「分词质量直接决定搜索质量」的底层原因——词切错了,倒排表里就没有正确的入口。

主分片 vs 副本分片#

ES 的数据可靠性靠「主分片 + 副本分片」的组合拳实现,二者职责泾渭分明:

  • 主分片(Primary Shard):可读可写(rw),所有写请求先落主分片,再同步到副本
  • 副本分片(Replica Shard):只读(ro),承担数据冗余与查询请求分流
关键约束

同一主分片与其副本永不分配在同一节点——否则该节点宕机时主副本同时丢失,冗余就失去了意义。这正是下文「单节点永远 yellow」现象的根本原因。

分片在双节点集群中的典型分配方式如下:

graph TD
    subgraph 节点A
        P0["主分片 P0 (rw)"]
        R1["副本 R1 (ro)"]
    end
    subgraph 节点B
        P1["主分片 P1 (rw)"]
        R0["副本 R0 (ro)"]
    end
    P0 -. 数据同步 .-> R0
    P1 -. 数据同步 .-> R1

注意交叉分配:节点A 存放 P0 与 R1,节点B 存放 P1 与 R0,任一节点宕机,数据依然完整可用。

集群健康颜色与排查命令#

集群健康状态只有三种颜色,但风险等级天差地别:

状态含义风险等级
green主分片与副本全部分配✅ 正常
yellow主分片全部可用,但有副本未分配⚠️ 警告:数据可读写但无冗余
red存在主分片未分配🚨 事故级:部分数据不可读写

排查三板斧,按顺序执行:

集群健康排查命令
curl -X GET "localhost:9200/_cluster/health?pretty"
curl -X GET "localhost:9200/_cat/shards?v"
curl -X GET "localhost:9200/_cluster/allocation/explain?pretty"
  • health 返回的 status 字段直接显示当前颜色
  • _cat/shards 中状态为 UNASSIGNED 的行就是「无处安家」的分片
  • explain 会返回分片未分配的详细原因(如磁盘水位超标、节点数不足)
经典「假故障」复现

单节点集群创建默认带 1 副本的索引后,健康状态永远是 yellow——因为副本分片不能和主分片住同一台机器,而集群里根本没有第二台机器。这不是故障,是特性。

分片路由公式:为什么主分片数创建后不可改#

每条文档写入时,ES 用下面的公式决定它落到哪个分片:

shard=hash(文档ID) mod 主分片数shard = hash(\text{文档ID}) \bmod \text{主分片数}

推导一下你就明白为什么改不了:假设原来 5 个主分片,ID 为 7 的文档落在 7 % 5 = 2 号分片;如果把主分片数改成 3,取模结果变成 7 % 3 = 1,历史数据的定位全部错乱,等于整个索引报废。

补救路线

确实需要调整分片规模时,可用 _split(扩容)/ _shrink(缩容)/ _reindex(重建)三种方案,各有前置条件与代价,将在本系列后续文章展开。


RESTful 与 JSON 语法#

ES 的所有交互都是「HTTP 方法 + JSON 请求体」,因此掌握 RESTful 风格与 JSON 语法规范是后续一切操作的前置技能。本模块内容不长,但每条规则都是实战中高频踩坑点。

本模块前置知识#

  • 了解 HTTP 常见方法(GET/POST)
  • 完成上一模块,知道 ES 通过 9200 端口提供 HTTP 服务

本模块核心术语#

  • RESTful:一种 API 设计风格——URL 只表示「资源」(名词),动作用 HTTP 方法(动词)表达
  • JSON:ES 请求体与响应体的统一数据格式

RESTful 风格:URL 是名词,HTTP 方法是动词#

反面教材是把动词塞进 URL,例如 /student/get_1001、/student/del_1001——每个人起名习惯不同,团队协作时就是灾难。RESTful 的正解是让同一个资源用同一 URL,用 HTTP 方法区分动作:

操作反面示例RESTful 正解
查询/student/get_1001GET /student/1001
新增/student/add_1001POST /student/1001
修改/student/update_1001PUT /student/1001
删除/student/del_1001DELETE /student/1001

结论:ES 就是典型的 RESTful 程序——索引/文档是资源,HTTP 方法是动作。

JSON 语法硬性规则#

JSON 的类型系统非常简单:四种基础类型(字符串、数字、布尔 true/false、null)+ 两种复合类型(数组 [...]、对象 {...}),复合类型可任意嵌套。

但必须牢记三条铁律,违反任何一条都会直接报解析错误:

  1. 字符串必须使用双引号,单引号非法
  2. 最后一个元素后不允许尾随逗号
  3. 不允许注释(// 和 /* */ 都不行)

课堂练习#

用 JSON 描述你自己(注意对象嵌套数组的写法),并通过 curl 提交给 ES 验证合法性:

practice_json.sh
curl -X PUT "localhost:9200/student/_doc/1" -H 'Content-Type: application/json' -d'
{
"name": "张三",
"age": 28,
"is_ops": true,
"skills": ["Linux", "Docker", "K8s"],
"address": {
"city": "北京",
"district": "海淀区"
}
}'

若 JSON 合法,预期返回正常的写入响应("result":"created");若格式有误,ES 会直接抛出 mapper_parsing_exception 并指出出错位置。


索引管理#

概念理解了,接下来进入真正的操作环节。索引是 ES 数据管理的「大门」,本模块覆盖索引的创建、查看、修改、删除、开关闭合与别名机制——这些是日常运维中最高频的管理动作,每一条命令都请在你的环境上实际敲一遍。

本模块前置知识#

  • 掌握 RESTful 风格与 JSON 语法
  • 理解索引、分片、副本的概念

本模块核心术语#

  • 别名(Alias):指向一个或多个索引的「虚拟名字」,可实现索引平滑切换
  • 打开/关闭(open/close):关闭的索引几乎不占集群资源,也不可读写,适合冷数据归档

索引的增删改查#

创建索引时直接指定分片与副本数:

create_index.sh
curl -X PUT "localhost:9200/student" -H 'Content-Type: application/json' -d'
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}'

预期输出:

终端输出
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "student"
}

查看索引有两种视图,按需取用:

  • GET /_cat/indices?v:列表视图,一眼看到 docs.count、store.size 等关键指标
  • GET /student:详情视图,返回 settings 与 mappings 完整定义

修改副本数——副本数是少数支持动态调整的索引级配置:

update_replicas.sh
curl -X PUT "localhost:9200/student/_settings" -H 'Content-Type: application/json' -d'
{
"number_of_replicas": 0
}'
再次强调

主分片数只能在创建时指定,事后不可修改。生产环境规划容量时宁多勿少。

删除索引:

delete_index.sh
curl -X DELETE "localhost:9200/student"

预期输出 {"acknowledged":true}。

索引的打开与关闭#

冷数据归档的利器,一行命令完成状态切换:

open_close.sh
curl -X POST "localhost:9200/student/_close"
curl -X POST "localhost:9200/student/_open"

关闭后执行 GET /_cat/indices?v,可观察到该索引状态变为 close,此时它几乎不再占用堆内存与文件句柄,但数据完整保留在磁盘上。

别名实战#

创建别名只需一个 _aliases 动作:

create_alias.sh
curl -X POST "localhost:9200/_aliases" -H 'Content-Type: application/json' -d'
{
"actions": [
{ "add": { "index": "student", "alias": "student_v1_read" } }
]
}'

别名的经典场景是重建索引后的无感切换:业务代码始终读写别名,运维在后台建好新索引并完成数据迁移后,原子切换别名指向,业务零改动、零停机。

graph LR
    App["业务应用"] -->|"始终读写别名 student_read"| V1["student_v1(旧索引)"]
    App -. "别名原子切换" .-> V2["student_v2(重建后新索引)"]
    style V2 stroke:#3fb950,stroke-width:2px

索引关键排查指标#

_cat/indices?v 返回的每一列都值得读懂:

列名含义
health / status索引健康色 / 开闭状态
pri / rep主分片数 / 副本分片数
docs.count文档总数(含已删未合并的文档见 docs.deleted)
store.size该索引占用磁盘总量

其他常用 settings 速查:refresh_interval(写入后多久可被搜索到,默认 1s,批量导入时可临时调大提速)、max_result_window(深度分页上限,默认 10000)。


文档的基础操作#

索引是容器,文档才是数据本体。本模块聚焦文档的写入、查询、更新、删除四大基本动作,以及生产环境使用频率极高的 _mget 与 _bulk 批量 API——其中 _bulk 是重点中的重点,日志导入、数据初始化全靠它。

本模块前置知识#

  • 已掌握索引的创建与查看
  • 熟悉 JSON 对象写法

本模块核心术语#

术语说明
文档 ID(_id)文档在索引内的唯一标识,参与分片路由哈希计算
_source文档原始 JSON 内容,查询时原样返回
_bulk批量操作 API,一次请求完成多条增删改
_mget批量查询 API,一次请求取回多条文档

单条文档写入(POST 与 PUT 的区别)#

方式请求示例适用场景
指定 IDPUT /student/_doc/1001ID 有业务意义(如学号、订单号)
自动生成 IDPOST /student/_doc日志等无自然主键的数据

指定 ID 写入示例:

put_doc.sh
curl -X PUT "localhost:9200/student/_doc/1001" -H 'Content-Type: application/json' -d'
{
"name": "张三",
"age": 20,
"tag": ["篮球", "编程"]
}'

预期输出包含 "result":"created"。使用 POST 自动生成 ID 时,响应中会返回一串系统生成的 _id。

写入响应的每个字段都是排查线索:

字段含义
_index / _id文档落点与标识
_version文档级版本号,每次变更 +1
result本次动作结果:created / updated / deleted / noop
_seq_no / _primary_term全局序号与主分片任期,乐观锁与恢复的底层依据

特别地,局部更新若提交的内容与原文档完全一致,result 返回 noop(无操作),_version 不递增。

_bulk 部分失败的典型响应片段如下,排障时逐条遍历定位:

bulk_partial_failure.json
{
"errors": true,
"items": [
{ "index": { "_id": "1003", "status": 201 } },
{ "update": { "_id": "1001", "status": 200 } },
{ "delete": { "_id": "1002", "status": 404,
"error": { "reason": "document missing" } } }
]
}
幂等性说明

对同一 ID 重复 PUT 是覆盖式更新,_version 字段随之递增;而重复 POST 每次都会生成一条新文档,注意区分。

单条文档查询与删除#

get_delete_doc.sh
curl -X GET "localhost:9200/student/_doc/1001"
curl -X DELETE "localhost:9200/student/_doc/1001"
  • 查询成功:返回体含 _index、_id、_version、_source 等字段
  • 查询不存在的 ID:返回 "found":false(对比记忆)
  • 删除成功:"result":"deleted"

文档更新:全量覆盖 vs 局部更新#

  • 全量更新:再次 PUT 同 ID 的完整 JSON,旧内容被整体替换
  • 局部更新:使用 _update 端点,只改指定字段,其余字段保持不变
partial_update.sh
curl -X POST "localhost:9200/student/_doc/1001/_update" -H 'Content-Type: application/json' -d'
{
"doc": { "age": 22 }
}'
并发控制

ES 通过 _seq_no / _primary_term 实现乐观锁:更新时携带查询时读到的这两个值,若期间文档被他人修改,则本次更新失败。运维阶段了解即可。

批量查询 _mget#

一次请求取回多条文档,避免 N 次网络往返:

mget.sh
curl -X GET "localhost:9200/student/_mget" -H 'Content-Type: application/json' -d'
{
"ids": ["1001", "1002"]
}'

预期输出 docs 数组,按请求顺序逐条返回;不存在的 ID 对应条目标记 found:false,不影响其他文档返回。

批量操作 _bulk(重点中的重点)#

_bulk 的格式铁律:每个动作由「元数据行 + 数据行」两行组成,且每行必须以换行符结尾(包括最后一行)。一次请求可混合增、改、删:

bulk_request.json
POST /_bulk
{"index":{"_index":"student","_id":"1003"}}
{"name":"李四","age":20}
{"update":{"_index":"student","_id":"1001"}}
{"doc":{"age":22}}
{"delete":{"_index":"student","_id":"1002"}}

上述请求一次完成三件事:新增 1003、修改 1001 的年龄、删除 1002。预期输出解读要点:

  • items 数组逐条回显每个动作的执行结果,顺序与请求一致
  • 顶层 errors 字段为 false 表示全部成功
  • 部分失败特性:单条失败不影响其他条目执行,失败条目的 error.reason 给出具体原因
高频踩坑

_bulk 报 illegal_argument_exception 十有八九是换行问题。通过文件提交时务必使用 --data-binary @bulk.json 而非 -d,避免 curl 吃掉换行符。


索引管理#

概念理解了,接下来进入真正的操作环节。索引是 ES 数据管理的「大门」,本模块覆盖索引的创建、查看、修改、删除、开关闭合与别名机制——这些是日常运维中最高频的管理动作,每一条命令都请在你的环境上实际敲一遍。

本模块前置知识#

  • 掌握 RESTful 风格与 JSON 语法
  • 理解索引、分片、副本的概念

本模块核心术语#

  • 别名(Alias):指向一个或多个索引的「虚拟名字」,可实现索引平滑切换
  • 打开/关闭(open/close):关闭的索引几乎不占集群资源,也不可读写,适合冷数据归档

索引的增删改查#

创建索引时直接指定分片与副本数:

create_index.sh
curl -X PUT "localhost:9200/student" -H 'Content-Type: application/json' -d'
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}'

预期输出:

终端输出
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "student"
}

查看索引有两种视图,按需取用:

  • GET /_cat/indices?v:列表视图,一眼看到 docs.count、store.size 等关键指标
  • GET /student:详情视图,返回 settings 与 mappings 完整定义

修改副本数——副本数是少数支持动态调整的索引级配置:

update_replicas.sh
curl -X PUT "localhost:9200/student/_settings" -H 'Content-Type: application/json' -d'
{
"number_of_replicas": 0
}'
再次强调

主分片数只能在创建时指定,事后不可修改。生产环境规划容量时宁多勿少。

删除索引:

delete_index.sh
curl -X DELETE "localhost:9200/student"

预期输出 {"acknowledged":true}。

索引的打开与关闭#

冷数据归档的利器,一行命令完成状态切换:

open_close.sh
curl -X POST "localhost:9200/student/_close"
curl -X POST "localhost:9200/student/_open"

关闭后执行 GET /_cat/indices?v,可观察到该索引状态变为 close,此时它几乎不再占用堆内存与文件句柄,但数据完整保留在磁盘上。

别名实战#

创建别名只需一个 _aliases 动作:

create_alias.sh
curl -X POST "localhost:9200/_aliases" -H 'Content-Type: application/json' -d'
{
"actions": [
{ "add": { "index": "student", "alias": "student_v1_read" } }
]
}'

别名的经典场景是重建索引后的无感切换:业务代码始终读写别名,运维在后台建好新索引并完成数据迁移后,原子切换别名指向,业务零改动、零停机。

graph LR
    App["业务应用"] -->|"始终读写别名 student_read"| V1["student_v1(旧索引)"]
    App -. "别名原子切换" .-> V2["student_v2(重建后新索引)"]
    style V2 stroke:#3fb950,stroke-width:2px

文档的基础操作#

索引是容器,文档才是数据本体。本模块聚焦文档的写入、查询、更新、删除四大基本动作,以及生产环境使用频率极高的 _mget 与 _bulk 批量 API——其中 _bulk 是重点中的重点,日志导入、数据初始化全靠它。

本模块前置知识#

  • 已掌握索引的创建与查看
  • 熟悉 JSON 对象写法

本模块核心术语#

术语说明
文档 ID(_id)文档在索引内的唯一标识,参与分片路由哈希计算
_source文档原始 JSON 内容,查询时原样返回
_bulk批量操作 API,一次请求完成多条增删改
_mget批量查询 API,一次请求取回多条文档

单条文档写入(POST 与 PUT 的区别)#

方式请求示例适用场景
指定 IDPUT /student/_doc/1001ID 有业务意义(如学号、订单号)
自动生成 IDPOST /student/_doc日志等无自然主键的数据

指定 ID 写入示例:

put_doc.sh
curl -X PUT "localhost:9200/student/_doc/1001" -H 'Content-Type: application/json' -d'
{
"name": "张三",
"age": 20,
"tag": ["篮球", "编程"]
}'

预期输出包含 "result":"created"。使用 POST 自动生成 ID 时,响应中会返回一串系统生成的 _id。

幂等性说明

对同一 ID 重复 PUT 是覆盖式更新,_version 字段随之递增;而重复 POST 每次都会生成一条新文档,注意区分。

单条文档查询与删除#

get_delete_doc.sh
curl -X GET "localhost:9200/student/_doc/1001"
curl -X DELETE "localhost:9200/student/_doc/1001"
  • 查询成功:返回体含 _index、_id、_version、_source 等字段
  • 查询不存在的 ID:返回 "found":false(对比记忆)
  • 删除成功:"result":"deleted"

文档更新:全量覆盖 vs 局部更新#

  • 全量更新:再次 PUT 同 ID 的完整 JSON,旧内容被整体替换
  • 局部更新:使用 _update 端点,只改指定字段,其余字段保持不变
partial_update.sh
curl -X POST "localhost:9200/student/_doc/1001/_update" -H 'Content-Type: application/json' -d'
{
"doc": { "age": 22 }
}'
并发控制

ES 通过 _seq_no / _primary_term 实现乐观锁:更新时携带查询时读到的这两个值,若期间文档被他人修改,则本次更新失败。运维阶段了解即可。

批量查询 _mget#

一次请求取回多条文档,避免 N 次网络往返:

mget.sh
curl -X GET "localhost:9200/student/_mget" -H 'Content-Type: application/json' -d'
{
"ids": ["1001", "1002"]
}'

预期输出 docs 数组,按请求顺序逐条返回;不存在的 ID 对应条目标记 found:false,不影响其他文档返回。

批量操作 _bulk(重点中的重点)#

_bulk 的格式铁律:每个动作由「元数据行 + 数据行」两行组成,且每行必须以换行符结尾(包括最后一行)。一次请求可混合增、改、删:

bulk_request.json
POST /_bulk
{"index":{"_index":"student","_id":"1003"}}
{"name":"李四","age":20}
{"update":{"_index":"student","_id":"1001"}}
{"doc":{"age":22}}
{"delete":{"_index":"student","_id":"1002"}}

上述请求一次完成三件事:新增 1003、修改 1001 的年龄、删除 1002。预期输出解读要点:

  • items 数组逐条回显每个动作的执行结果,顺序与请求一致
  • 顶层 errors 字段为 false 表示全部成功
  • 部分失败特性:单条失败不影响其他条目执行,失败条目的 error.reason 给出具体原因
高频踩坑

_bulk 报 illegal_argument_exception 十有八九是换行问题。通过文件提交时务必使用 --data-binary @bulk.json 而非 -d,避免 curl 吃掉换行符。


自定义数据类型 Mapping#

前面写入文档时我们从未定义过任何字段类型,ES 却照单全收——这背后是动态映射机制在兜底。本模块带你看懂 ES 自动推断出的 Mapping 长什么样,学会在创建索引时显式声明字段类型,并明确运维在 Mapping 管理中的职责边界。

本模块前置知识#

  • 已完成文档写入操作
  • 理解 Mapping 是字段的「表结构定义」

本模块核心术语#

类型特点典型场景
text全文分词类型,会被分词器切词文章内容、评论等需要全文搜索的字段
keyword精确值类型,不分词标签、状态、枚举值,排序与聚合
date日期专用类型,可指定 format生日、创建时间
ipIP 地址专用类型,带合法性校验访问日志中的客户端 IP
long / double整数 / 浮点数值类型年龄、分数、金额
  • 动态映射(Dynamic Mapping):未定义 Mapping 时,ES 按写入数据自动推断字段类型

查看与理解自动推断的 Mapping#

view_mapping.sh
curl -X GET "localhost:9200/student/_mapping?pretty"

对照之前写入的文档,你会发现一个有趣的现象:字符串字段被推断成了 text + keyword 的双子字段结构——

终端输出
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}

这份结构的含义:name 用于全文搜索,name.keyword 用于精确匹配与排序聚合——一份数据,两种用法。

显式定义 Mapping 实战#

动态映射方便但不可控,生产环境应在创建索引时显式声明:

create_index_with_mapping.sh
curl -X PUT "localhost:9200/student_v2" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"name": { "type": "text" },
"tag": { "type": "keyword" },
"birthday": { "type": "date", "format": "yyyy-MM-dd" },
"ip_addr": { "type": "ip" },
"score": { "type": "double" }
}
}
}'

类型校验的价值,写入一个非法 IP 立刻可见:

invalid_ip_test.sh
curl -X PUT "localhost:9200/student_v2/_doc/1" -H 'Content-Type: application/json' -d'
{
"name": "王五",
"ip_addr": "999.999.999.999"
}'

ES 直接拒绝并抛出 mapper_parsing_exception,脏数据被挡在门外——这就是强类型 Mapping 的意义。

运维对 Mapping 的定位#

职责边界

Mapping 设计主要是开发的工作。运维需要做到的是:会查(_mapping)、会辅助排查(字段类型不符导致的写入报错),以及在变更评审时守住底线。

关键约束

已有字段的类型不可修改(例如把 text 改成 keyword),只能新建索引后 _reindex 迁移数据;新增字段则是允许的。了解即可,实操在进阶篇展开。


IK 分词器#

中文搜索体验的「最后一公里」在分词器。ES 自带的 standard 分词器对中文的支持近乎为零,而 IK 是国内事实上的中文分词标准。本模块完成 IK 的安装、两种分词模式的对比实测与自定义词典配置,让你的集群真正具备中文搜索能力。

本模块前置知识#

  • 理解分词器(Analyzer)是把文本切成词项的组件
  • 会在 Mapping 中为字段指定类型

本模块核心术语#

  • ik_max_word:最细粒度切分,穷尽各种可能的词组合
  • ik_smart:最粗粒度切分,只切一次不重复
  • 自定义词典(custom dic):扩展 IK 词库的文件/远程地址,让新词(如行业术语)能被正确切出

为什么要中文分词器#

先用默认的 standard 分词器试试中文:

standard_analyze.sh
curl -X GET "localhost:9200/_analyze" -H 'Content-Type: application/json' -d'
{
"analyzer": "standard",
"text": "中华人民共和国人民大会堂"
}'

预期输出把句子按单字切分:中、华、人、民……每字一个 token。这意味着搜「人民」只是逐字匹配,搜「华人」也能误命中——完全无法表达「词」的概念,搜索体验极差。这就是必须引入 IK 的根本原因。

IK 分词器安装#

铁律

IK 插件版本必须与 ES 版本严格一致,否则节点启动直接失败。这是新手炸掉集群的头号原因。

在线安装(离线环境可先下载 zip 包,再 install file:///path/to/zip):

install_ik.sh
bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.11.1
# 安装完成后必须重启节点
systemctl restart elasticsearch

验证插件是否加载成功:

verify_plugin.sh
curl -X GET "localhost:9200/_cat/plugins?v"

预期输出中出现 analysis-ik 即表示安装成功。

两种分词模式对比实战#

用同一句话分别测试两种模式:

ik_mode_compare.sh
curl -X POST "localhost:9200/_analyze" -H 'Content-Type: application/json' -d'
{"analyzer":"ik_max_word","text":"中华人民共和国人民大会堂"}'
curl -X POST "localhost:9200/_analyze" -H 'Content-Type: application/json' -d'
{"analyzer":"ik_smart","text":"中华人民共和国人民大会堂"}'
模式切分结果(token 列表)粒度
ik_max_word中华人民共和国 / 中华人民 / 中华 / 华人 / 人民共和国 / 人民 / 共和国 / 共和 / 人民大会 / 人大 / 大会堂 / 大会 / 会堂最细,穷尽组合
ik_smart中华人民共和国 / 人民大会堂最粗,只切一次
选型建议

索引侧用 ik_max_word 保证召回(能搜到),搜索侧用 ik_smart 保证精准(不误搜)。这是社区公认的最佳实践组合。

自定义词典配置#

新词与行业术语(如「弹性云主机」)默认不在 IK 词库中,需要手动扩展,三步走:

第一步,编辑 IK 配置文件(路径为 config/analysis-ik/config/IKAnalyzer.cfg.xml),声明扩展词典:

IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<entry key="ext_dict"></entry>
<entry key="ext_dict">custom/myword.dic</entry>
</properties>

第二步,在 config/analysis-ik/config/custom/ 目录下新建 myword.dic,每行一个词:

myword.dic
弹性云主机
云原生

第三步,重启节点后重新验证:

verify_custom_dic.sh
curl -X POST "localhost:9200/_analyze" -H 'Content-Type: application/json' -d'
{"analyzer":"ik_max_word","text":"使用弹性云主机部署云原生应用"}'

预期输出中「弹性云主机」「云原生」被整体切出,不再被拆碎。

进阶了解

本地词典每次修改都要重启节点。生产环境可配置远程词典(HTTP 地址),IK 会定时轮询拉取实现热更新,避免重启集群,具体方案留待进阶篇展开。


三、常见问题与排错指南#

纸上得来终觉浅,真正的功力都在踩坑里练出来。本章汇总 ES 日常使用中的高危操作红线与六类典型报错,每一条都附定位思路与解决方案,建议收藏当作案头速查手册。

高危操作与易错点总览#

生产环境红线清单
  • DELETE /索引名 不可逆,生产环境建议在 elasticsearch.yml 中配置 action.destructive_requires_name: true,禁止通配符与省略索引名的删除操作
  • 主分片数只能在创建索引时指定,事后不可修改,规划容量时宁多勿少
  • IK 分词器版本与 ES 版本不一致会导致节点启动直接失败
  • JSON 请求体使用单引号、尾随逗号会直接报解析错误,提交前先校验格式

典型报错根因与解决方案#

下表汇总六类高频问题,按「现象 → 定位 → 解决」的路径处理:

现象定位命令/线索根因与解决方案
集群一直 yellowGET /_cat/shards?v 查看 UNASSIGNED 分片单节点场景副本无处分配,将 number_of_replicas 改为 0 即可转 green
集群 redGET _cluster/allocation/explain?pretty主分片未分配,常见根因为磁盘水位超标、节点离线,按 explain 返回的原因逐项处理
mapper_parsing_exception报错信息中带行号与位置JSON 格式错误(单引号/尾逗号),逐字符检查请求体
_bulk 报 illegal_argument_exception检查请求体换行元数据行与数据行未正确换行,注意最后一行也需 \n;用 --data-binary 提交文件
_bulk 部分失败响应顶层 errors: true遍历 items 数组,定位失败条目的 error.reason 针对性修复后重试该条
写入 IP 字段报 parse 错误报错指向具体字段与值数据格式不合法,上游清洗数据,或临时改用 keyword 类型接收
排错通用心法

ES 的报错信息通常非常具体——先读 error.reason,再看 caused_by 链条,绝大多数问题在报错文本里就写明了答案,切忌不读报错直接重装。


四、总结与进阶建议#

至此,从概念到 API、从 Mapping 到中文分词的完整链路已经打通。本章用一张图、一张表帮你固化知识体系,并给出生产环境的实践建议与后续学习路线。

核心内容复盘#

一张图回顾 ES 的层次关系——自顶向下,逐层包含:

graph TD
    C["集群 Cluster"] --> N1["节点 Node A"]
    C --> N2["节点 Node B"]
    N1 --> I["索引 Index"]
    I --> S["分片 Shard(主/副本)"]
    S --> D["文档 Document"]
    D --> F["字段 Field + Mapping 定义"]
    F -. 分词处理 .-> A["分词器 Analyzer(IK)"]

API 速查表——本文全部核心命令一页收拢:

类别操作命令
集群健康检查GET /_cluster/health?pretty
集群分片分配诊断GET /_cluster/allocation/explain?pretty
索引创建 / 删除PUT /student / DELETE /student
索引查看 / 改副本GET /_cat/indices?v / PUT /student/_settings
索引关闭 / 打开 / 别名POST /student/_close、_open、POST /_aliases
文档增 / 查 / 删PUT /student/_doc/1001、GET、DELETE
文档局部更新POST /student/_doc/1001/_update
批量批量查 / 批量写GET /student/_mget / POST /_bulk
Mapping查看映射GET /student/_mapping
分词分词测试POST /_analyze(analyzer 选 ik_max_word / ik_smart)

生产实践建议与进阶学习方向#

生产环境三条实践建议:

  • 索引命名规范化:采用 业务名-环境-日期 格式(如 app-log-prod-2024.11),配合索引生命周期管理
  • 别名先行:业务代码永远不直接写死物理索引名,一律通过别名访问,为日后重建与迁移留好后路
  • 合理预估分片数:单个分片建议控制在 10GB~50GB 之间,结合数据增速反推主分片数量,一次规划到位

:::

ELK系列(二):核心概念、索引与文档操作、Mapping 与 IK 分词器详解
https://www.6ixblog.site/posts/elk-2/
作者
Licwic
发布于
2025-07-06
许可协议
CC BY-NC-SA 4.0