云计算百科
云计算领域专业知识百科平台

Elasticsearch核心概念详解

Elasticsearch核心概念详解

学习目标

  • 深入理解索引、文档、映射的概念和底层原理
  • 掌握所有数据类型和字段属性的使用场景
  • 理解分片和副本的工作机制和最佳实践
  • 能够设计高性能、可扩展的索引结构
  • 掌握索引生命周期管理和优化策略
  • 理解倒排索引的实现原理

知识结构思维导图

#mermaid-svg-kPtIGTqdXuYWJbhm{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kPtIGTqdXuYWJbhm .error-icon{fill:#552222;}#mermaid-svg-kPtIGTqdXuYWJbhm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kPtIGTqdXuYWJbhm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kPtIGTqdXuYWJbhm .marker.cross{stroke:#333333;}#mermaid-svg-kPtIGTqdXuYWJbhm svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kPtIGTqdXuYWJbhm p{margin:0;}#mermaid-svg-kPtIGTqdXuYWJbhm .edge{stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .section–1 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section–1 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section–1 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section–1 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section–1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section–1 text{fill:#ffffff;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon–1{font-size:40px;color:#ffffff;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge–1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth–1{stroke-width:17;}#mermaid-svg-kPtIGTqdXuYWJbhm .section–1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-0 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-0 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-0 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-0 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-0 text{fill:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-0{font-size:40px;color:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-0{stroke-width:14;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-1 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-1 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-1 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-1 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-1 text{fill:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-1{font-size:40px;color:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-1{stroke-width:11;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-2 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-2 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-2 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-2 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-2 text{fill:#ffffff;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-2{stroke-width:8;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-3 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-3 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-3 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-3 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-3 text{fill:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-3{font-size:40px;color:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-3{stroke-width:5;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-4 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-4 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-4 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-4 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-4 text{fill:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-4{font-size:40px;color:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-4{stroke-width:2;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-5 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-5 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-5 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-5 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-5 text{fill:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-5{font-size:40px;color:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-5{stroke-width:-1;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-6 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-6 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-6 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-6 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-6 text{fill:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-6{font-size:40px;color:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-6{stroke-width:-4;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-7 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-7 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-7 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-7 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-7 text{fill:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-7{font-size:40px;color:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-7{stroke-width:-7;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-8 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-8 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-8 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-8 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-8 text{fill:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-8{font-size:40px;color:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-8{stroke-width:-10;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-9 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-9 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-9 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-9 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-9 text{fill:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-9{font-size:40px;color:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-9{stroke-width:-13;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-10 rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-10 path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-10 circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-10 polygon,#mermaid-svg-kPtIGTqdXuYWJbhm .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-10 text{fill:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .node-icon-10{font-size:40px;color:black;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .edge-depth-10{stroke-width:-16;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled circle,#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:lightgray;}#mermaid-svg-kPtIGTqdXuYWJbhm .disabled text{fill:#efefef;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-root rect,#mermaid-svg-kPtIGTqdXuYWJbhm .section-root path,#mermaid-svg-kPtIGTqdXuYWJbhm .section-root circle,#mermaid-svg-kPtIGTqdXuYWJbhm .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-kPtIGTqdXuYWJbhm .section-root text{fill:#ffffff;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-root span{color:#ffffff;}#mermaid-svg-kPtIGTqdXuYWJbhm .section-2 span{color:#ffffff;}#mermaid-svg-kPtIGTqdXuYWJbhm .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-kPtIGTqdXuYWJbhm .edge{fill:none;}#mermaid-svg-kPtIGTqdXuYWJbhm .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-kPtIGTqdXuYWJbhm :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

Elasticsearch核心概念

索引Index

索引设置

分片配置

副本配置

刷新间隔

索引别名

索引模板

索引生命周期

文档Document

文档结构

文档ID

文档版本

文档路由

映射Mapping

动态映射

静态映射

字段类型

字段属性

数据类型

文本类型

text

keyword

数值类型

long/integer

double/float

日期类型

布尔类型

对象类型

object

nested

特殊类型

geo_point

ip

completion

分片与副本

主分片

副本分片

分片分配

分片路由

倒排索引

Term Dictionary

Posting List

Term Index

一、索引(Index)深入理解

1.1 什么是索引?

索引是文档的容器,类似于 MySQL 中的数据库。但与传统数据库不同,Elasticsearch 的索引是基于倒排索引实现的。

概念对比(ES 8.x):

MySQL 5.x/6.x:
database.table.row
shop.products.row1

Elasticsearch 6.x(旧版本):
index.type.document
shop.products.doc1
❌ 已废弃

Elasticsearch 7.x(过渡版本):
index._doc.document
shop._doc.doc1
⚠️ Type 标记为废弃

Elasticsearch 8.x(当前版本):
index.document
shop.doc1
✓ Type 完全移除

为什么 ES 8.x 完全移除了 Type?

历史背景:
ES 早期版本模仿 MySQL 的设计
Index = Database
Type = Table
Document = Row

实际问题:
1. 映射冲突
同一索引中,不同 Type 的相同字段名必须有相同的映射
products/phone: { "name": "text" }
products/tablet: { "name": "keyword" }
❌ 冲突!无法共存

2. 稀疏存储
不同 Type 有不同的字段
products/phone: { "name", "price", "network" }
products/tablet: { "name", "price", "screenSize" }
❌ 每个文档都要存储所有字段的映射,浪费空间

3. 性能问题
Lucene 没有 Type 的概念
查询 products/phone 时,仍需要扫描所有文档
❌ 无法真正隔离数据

4. 复杂性
增加了 API 的复杂性
增加了用户的学习成本
❌ 收益不大

ES 8.x 解决方案:
✓ 完全移除 Type 概念
✓ 一个索引只存储一种类型的文档
✓ 如果需要多种类型,创建多个索引
✓ 或使用字段区分:{ "type": "phone" }

ES 8.x 的正确用法:

# ✓ 正确:为不同类型创建不同索引
PUT /products_phone/_doc/1
{
"name": "华为手机",
"price": 3999
}

PUT /products_tablet/_doc/2
{
"name": "iPad",
"price": 6799
}

# ✓ 正确:使用字段区分类型
PUT /products/_doc/1
{
"type": "phone",
"name": "华为手机",
"price": 3999
}

PUT /products/_doc/2
{
"type": "tablet",
"name": "iPad",
"price": 6799
}

# ❌ 错误:ES 8.x 不再支持自定义 Type
PUT /products/phone/1
{
"name": "华为手机"
}
# 返回错误:Rejecting mapping update to [products] as the final mapping would have more than 1 type

迁移指南(从旧版本升级到 ES 8.x):

# 旧版本(ES 6.x)
PUT /shop/products/1
{
"name": "华为手机"
}

PUT /shop/orders/1
{
"orderId": "001"
}

# 新版本(ES 8.x)- 方案 1:分离索引(推荐)
PUT /products/_doc/1
{
"name": "华为手机"
}

PUT /orders/_doc/1
{
"orderId": "001"
}

# 新版本(ES 8.x)- 方案 2:使用字段区分
PUT /shop/_doc/product_1
{
"docType": "product",
"name": "华为手机"
}

PUT /shop/_doc/order_1
{
"docType": "order",
"orderId": "001"
}

1.2 索引的创建

1.2.1 自动创建(不推荐生产环境)

POST /products/_doc/1
{
"name": "华为手机",
"price": 3999
}
// 如果 products 索引不存在,会自动创建

自动创建的问题:

❌ 无法控制分片数量
❌ 无法控制字段类型
❌ 可能导致类型推断错误
❌ 生产环境可能导致问题

示例:
"123" → 推断为 text
123 → 推断为 long
导致后续插入失败!

禁用自动创建:

PUT /_cluster/settings
{
"persistent": {
"action.auto_create_index": "false"
}
}

// 或者只允许特定模式
PUT /_cluster/settings
{
"persistent": {
"action.auto_create_index": "+logs-*,-*"
}
}

1.2.2 手动创建(推荐)

基本创建:

PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}

完整创建(包含映射):

PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "1s",
"max_result_window": 10000
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word"
},
"price": {
"type": "double"
}
}
}
}

1.3 索引设置详解

1.3.1 静态设置(创建后不可修改)

PUT /products
{
"settings": {
// ==================== 分片配置 ====================
"number_of_shards": 3, // 主分片数(不可修改)

// ==================== 编解码器 ====================
"codec": "best_compression", // 压缩算法(default/best_compression)

// ==================== 路由分片数 ====================
"routing_partition_size": 1 // 路由分区大小
}
}

为什么主分片数不可修改?

文档路由公式:
shard = hash(routing) % number_of_primary_shards

修改后路由会错乱,文档找不到!

示例:
原来 3 个分片:
doc1 → hash(doc1) % 3 = 1 → 存储在分片 1

修改为 5 个分片:
doc1 → hash(doc1) % 5 = 3 → 查询分片 3(找不到!)

如何修改分片数?

// 方法 1:Reindex(推荐)
POST /_reindex
{
"source": {
"index": "products"
},
"dest": {
"index": "products_new"
}
}

// 方法 2:Split API(分裂分片)
POST /products/_split/products_new
{
"settings": {
"index.number_of_shards": 6 // 必须是原来的倍数
}
}

// 方法 3:Shrink API(合并分片)
POST /products/_shrink/products_new
{
"settings": {
"index.number_of_shards": 1 // 必须是原来的因数
}
}

1.3.2 动态设置(可随时修改)

PUT /products/_settings
{
// ==================== 副本配置 ====================
"number_of_replicas": 2, // 副本数(可动态修改)

// ==================== 刷新配置 ====================
"refresh_interval": "30s", // 刷新间隔(默认 1s)
// "refresh_interval": "-1", // 禁用自动刷新(批量导入时)

// ==================== 查询配置 ====================
"max_result_window": 10000, // 最大返回结果数(from + size)
"max_inner_result_window": 100, // 内部聚合最大结果数
"max_rescore_window": 10000, // 重新评分窗口大小

// ==================== 分析配置 ====================
"max_ngram_diff": 1, // N-gram 最大差值
"max_shingle_diff": 3, // Shingle 最大差值

// ==================== 索引配置 ====================
"max_docvalue_fields_search": 100, // 最大 doc_values 字段数
"max_script_fields": 32, // 最大脚本字段数

// ==================== 路由配置 ====================
"routing.allocation.enable": "all", // 分片分配策略
// "all": 允许所有分片分配
// "primaries": 只允许主分片分配
// "new_primaries": 只允许新索引的主分片分配
// "none": 不允许分片分配

// ==================== 慢查询日志 ====================
"search.slowlog.threshold.query.warn": "10s",
"search.slowlog.threshold.query.info": "5s",
"search.slowlog.threshold.query.debug": "2s",
"search.slowlog.threshold.query.trace": "500ms",

"search.slowlog.threshold.fetch.warn": "1s",
"search.slowlog.threshold.fetch.info": "800ms",
"search.slowlog.threshold.fetch.debug": "500ms",
"search.slowlog.threshold.fetch.trace": "200ms",

"indexing.slowlog.threshold.index.warn": "10s",
"indexing.slowlog.threshold.index.info": "5s",
"indexing.slowlog.threshold.index.debug": "2s",
"indexing.slowlog.threshold.index.trace": "500ms",

// ==================== 合并配置 ====================
"merge.scheduler.max_thread_count": 1 // 合并线程数
}

refresh_interval 详解:

什么是 refresh?
将内存中的文档写入磁盘段(segment),使其可被搜索

默认 1s:
✓ 近实时搜索(1秒内可见)
✗ 频繁刷新消耗资源

增加到 30s:
✓ 减少资源消耗
✗ 搜索延迟增加

禁用(-1):
✓ 批量导入时性能最佳
✗ 数据不可搜索
⚠️ 导入完成后必须手动刷新

实战场景:批量导入优化

// 1. 导入前:禁用刷新和副本
PUT /products/_settings
{
"refresh_interval": "-1",
"number_of_replicas": 0
}

// 2. 批量导入数据
POST /_bulk
{ "index": { "_index": "products", "_id": "1" }}
{ "name": "产品1", "price": 100 }
{ "index": { "_index": "products", "_id": "2" }}
{ "name": "产品2", "price": 200 }
// … 大量数据

// 3. 导入后:手动刷新
POST /products/_refresh

// 4. 恢复设置
PUT /products/_settings
{
"refresh_interval": "1s",
"number_of_replicas": 1
}

1.4 索引别名(Alias)

1.4.1 为什么需要别名?

场景:索引需要重建
products_v1 → 应用使用

需要修改映射

创建 products_v2

问题:如何无缝切换?

解决:使用别名!

1.4.2 创建别名

基本别名:

// 创建别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "products_v1",
"alias": "products"
}
}
]
}

// 应用使用别名
GET /products/_search
// 实际查询 products_v1

切换别名(零停机):

POST /_aliases
{
"actions": [
{
"remove": {
"index": "products_v1",
"alias": "products"
}
},
{
"add": {
"index": "products_v2",
"alias": "products"
}
}
]
}
// 原子操作,瞬间切换

1.4.3 过滤别名

// 创建过滤别名(只显示上架商品)
POST /_aliases
{
"actions": [
{
"add": {
"index": "products",
"alias": "products_on_sale",
"filter": {
"term": {
"onSale": true
}
}
}
}
]
}

// 查询别名(自动应用过滤)
GET /products_on_sale/_search
// 只返回 onSale=true 的商品

1.4.4 路由别名

// 创建路由别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "products",
"alias": "products_category_1",
"routing": "1"
}
}
]
}

// 写入时自动路由到指定分片
POST /products_category_1/_doc/1
{
"name": "商品1",
"category": "1"
}

1.4.5 写入别名

// 创建写入别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "products_v2",
"alias": "products",
"is_write_index": true // 标记为写入索引
}
},
{
"add": {
"index": "products_v1",
"alias": "products"
}
}
]
}

// 写入别名(写入 products_v2)
POST /products/_doc/1
{
"name": "新商品"
}

// 查询别名(查询两个索引)
GET /products/_search

1.5 索引状态管理

1.5.1 查看索引信息

// 查看所有索引
GET /_cat/indices?v

// 查看索引设置
GET /products/_settings

// 查看索引映射
GET /products/_mapping

// 查看索引统计
GET /products/_stats

// 查看索引分片
GET /_cat/shards/products?v

1.5.2 打开/关闭索引

// 关闭索引(释放内存,但数据保留)
POST /products/_close

// 打开索引
POST /products/_open

// 关闭索引的好处:
// ✓ 释放内存
// ✓ 数据保留在磁盘
// ✓ 可以快速重新打开
// ✗ 无法读写

1.5.3 删除索引

// 删除单个索引
DELETE /products

// 删除多个索引
DELETE /products,orders

// 使用通配符删除
DELETE /logs*

// 删除所有索引(危险!)
DELETE /_all
// 建议禁用:action.destructive_requires_name: true

1.6 索引生命周期管理(ILM)

1.6.1 什么是 ILM?

场景:日志索引
logs-2024-01-01 → 热数据(频繁查询)
logs-2024-01-02 → 热数据
logs-2024-01-03 → 热数据
logs-2023-12-01 → 温数据(偶尔查询)
logs-2023-11-01 → 冷数据(很少查询)
logs-2023-10-01 → 可以删除

问题:如何自动管理这些索引?
解决:使用 ILM!

1.6.2 ILM 阶段

Hot(热)→ Warm(温)→ Cold(冷)→ Delete(删除)
↓ ↓ ↓ ↓
频繁读写 偶尔查询 很少查询 自动删除
高性能 中性能 低性能 释放空间
SSD SSD/HDD HDD –

1.6.3 创建 ILM 策略

PUT /_ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50GB", // 索引大小超过 50GB
"max_age": "1d", // 或索引创建超过 1 天
"max_docs": 10000000 // 或文档数超过 1000 万
},
"set_priority": {
"priority": 100 // 恢复优先级
}
}
},
"warm": {
"min_age": "7d", // 7 天后进入温阶段
"actions": {
"shrink": {
"number_of_shards": 1 // 合并为 1 个分片
},
"forcemerge": {
"max_num_segments": 1 // 强制合并段
},
"set_priority": {
"priority": 50
}
}
},
"cold": {
"min_age": "30d", // 30 天后进入冷阶段
"actions": {
"freeze": {}, // 冻结索引
"set_priority": {
"priority": 0
}
}
},
"delete": {
"min_age": "90d", // 90 天后删除
"actions": {
"delete": {}
}
}
}
}
}

1.6.4 应用 ILM 策略

// 创建索引模板并应用 ILM
PUT /_index_template/logs_template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"index.lifecycle.name": "logs_policy", // 应用 ILM 策略
"index.lifecycle.rollover_alias": "logs" // 滚动别名
}
}
}

// 创建初始索引
PUT /logs000001
{
"aliases": {
"logs": {
"is_write_index": true
}
}
}

// 写入数据(使用别名)
POST /logs/_doc
{
"message": "日志消息",
"timestamp": "2024-01-17T10:00:00"
}

// ILM 会自动:
// 1. 当索引满足条件时,创建 logs-000002
// 2. 将写入别名切换到新索引
// 3. 按照策略管理旧索引

1.7 索引模板详解

1.7.1 组件模板(Component Template)

// 创建通用设置模板
PUT /_component_template/common_settings
{
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}
}

// 创建通用映射模板
PUT /_component_template/common_mappings
{
"template": {
"mappings": {
"properties": {
"timestamp": {
"type": "date"
},
"message": {
"type": "text"
}
}
}
}
}

1.7.2 索引模板(Index Template)

// 创建索引模板(组合组件模板)
PUT /_index_template/logs_template
{
"index_patterns": ["logs-*"],
"composed_of": [
"common_settings",
"common_mappings"
],
"priority": 100,
"template": {
"settings": {
"index.lifecycle.name": "logs_policy"
},
"mappings": {
"properties": {
"level": {
"type": "keyword"
}
}
}
}
}

1.7.3 模板优先级

// 优先级高的模板会覆盖优先级低的模板
PUT /_index_template/logs_template_general
{
"index_patterns": ["logs-*"],
"priority": 100,
"template": {
"settings": {
"number_of_shards": 1
}
}
}

PUT /_index_template/logs_template_specific
{
"index_patterns": ["logs-app-*"],
"priority": 200, // 优先级更高
"template": {
"settings": {
"number_of_shards": 3 // 会覆盖上面的设置
}
}
}

// 创建 logs-app-001
// 使用 logs_template_specific(优先级 200)
// number_of_shards = 3

二、文档(Document)深入理解

2.1 文档结构详解

{
// ==================== 元数据字段 ====================
"_index": "products", // 所属索引
"_id": "1", // 文档ID(唯一标识)
"_version": 3, // 版本号(每次更新+1)
"_seq_no": 5, // 序列号(全局唯一,递增)
"_primary_term": 1, // 主分片任期号
"_score": 1.2345, // 相关性评分(搜索时)
"_routing": "user123", // 路由值(可选)

// ==================== 原始数据 ====================
"_source": {
"name": "华为手机",
"price": 3999,
"category": "电子产品",
"tags": ["5G", "拍照"],
"createTime": "2024-01-17T10:00:00"
}
}

元数据字段说明:

┌──────────────────────────────────────────────────────────────┐
│ 字段 │ 说明 │
├──────────────────────────────────────────────────────────────┤
│ _index │ 文档所属的索引名称 │
│ _id │ 文档的唯一标识符 │
│ _version │ 文档版本号,用于乐观锁 │
│ _seq_no │ 序列号,用于并发控制 │
│ _primary_term │ 主分片任期号,用于并发控制 │
│ _score │ 相关性评分,搜索时计算 │
│ _routing │ 路由值,决定文档存储在哪个分片 │
│ _source │ 原始 JSON 文档 │
└──────────────────────────────────────────────────────────────┘

2.2 文档 ID 策略

2.2.1 自动生成 ID

// 自动生成 ID(使用 POST)
POST /products/_doc
{
"name": "小米手机",
"price": 2999
}

// 返回
{
"_index": "products",
"_id": "AWxyz123ABC", // 自动生成的 UUID
"_version": 1,
"result": "created"
}

自动生成 ID 的特点:

✓ 保证全局唯一
✓ 无需维护 ID 生成器
✓ 性能好(无需检查 ID 冲突)
✗ ID 无业务含义
✗ 无法通过 ID 直接查询

2.2.2 手动指定 ID

// 手动指定 ID(使用 PUT)
PUT /products/_doc/1
{
"name": "华为手机",
"price": 3999
}

// 返回
{
"_index": "products",
"_id": "1",
"_version": 1,
"result": "created"
}

手动指定 ID 的特点:

✓ ID 有业务含义
✓ 可以通过 ID 直接查询
✓ 方便与数据库主键对应
✗ 需要保证 ID 唯一性
✗ 可能存在 ID 冲突

2.2.3 使用业务 ID 作为文档 ID(推荐)

// Java 代码示例
@Service
public class ProductService {

@Autowired
private RestHighLevelClient esClient;

/**
* 同步商品到 ES
* 使用数据库主键作为 ES 文档 ID
*/

public void syncProductToES(Product product) throws IOException {
IndexRequest request = new IndexRequest("products")
.id(String.valueOf(product.getId())) // 使用数据库主键
.source(
"productId", product.getId(),
"name", product.getName(),
"price", product.getPrice(),
"category", product.getCategory()
);

esClient.index(request, RequestOptions.DEFAULT);
}

/**
* 根据数据库主键查询 ES 文档
*/

public Product getProductFromES(Long productId) throws IOException {
GetRequest request = new GetRequest("products", String.valueOf(productId));
GetResponse response = esClient.get(request, RequestOptions.DEFAULT);

if (response.isExists()) {
Map<String, Object> source = response.getSourceAsMap();
// 转换为 Product 对象
return convertToProduct(source);
}
return null;
}
}

最佳实践:

推荐:使用数据库主键作为 ES 文档 ID
✓ 方便数据同步
✓ 方便根据 ID 查询
✓ 方便更新和删除
✓ 保持数据一致性

示例:
MySQL: id=10001, name="华为手机"
ES: _id="10001", name="华为手机"

2.3 文档版本控制

2.3.1 内部版本控制

// 第一次创建文档
PUT /products/_doc/1
{
"name": "华为手机",
"price": 3999
}
// 返回:_version: 1

// 第一次更新
PUT /products/_doc/1
{
"name": "华为手机",
"price": 3899
}
// 返回:_version: 2

// 第二次更新
PUT /products/_doc/1
{
"name": "华为手机",
"price": 3799
}
// 返回:_version: 3

版本号的作用:

1. 乐观锁:防止并发更新冲突
2. 变更追踪:记录文档修改次数
3. 调试:帮助定位问题

2.3.2 乐观锁(基于版本号)

// 场景:两个用户同时修改商品价格

// 用户 A 读取文档(version=1)
GET /products/_doc/1
// 返回:price=3999, _version=1

// 用户 B 读取文档(version=1)
GET /products/_doc/1
// 返回:price=3999, _version=1

// 用户 A 更新(指定版本号)
PUT /products/_doc/1?if_seq_no=0&if_primary_term=1
{
"name": "华为手机",
"price": 3899
}
// 成功,version=2

// 用户 B 更新(使用旧版本号)
PUT /products/_doc/1?if_seq_no=0&if_primary_term=1
{
"name": "华为手机",
"price": 3799
}
// 失败!返回 409 冲突错误

Java 代码示例:

@Service
public class ProductService {

@Autowired
private RestHighLevelClient esClient;

/**
* 使用乐观锁更新商品价格
*/

public boolean updateProductPrice(Long productId, Double newPrice) {
try {
// 1. 先查询获取当前版本
GetRequest getRequest = new GetRequest("products", String.valueOf(productId));
GetResponse getResponse = esClient.get(getRequest, RequestOptions.DEFAULT);

if (!getResponse.isExists()) {
return false;
}

long seqNo = getResponse.getSeqNo();
long primaryTerm = getResponse.getPrimaryTerm();

// 2. 使用版本号更新
UpdateRequest updateRequest = new UpdateRequest("products", String.valueOf(productId))
.setIfSeqNo(seqNo)
.setIfPrimaryTerm(primaryTerm)
.doc("price", newPrice);

UpdateResponse updateResponse = esClient.update(updateRequest, RequestOptions.DEFAULT);
return updateResponse.getResult() == DocWriteResponse.Result.UPDATED;

} catch (ElasticsearchException e) {
if (e.status() == RestStatus.CONFLICT) {
// 版本冲突,可以重试
log.warn("版本冲突,商品ID: {}", productId);
return false;
}
throw new RuntimeException("更新失败", e);
} catch (IOException e) {
throw new RuntimeException("更新失败", e);
}
}
}

2.3.3 外部版本控制

// 使用外部版本号(如数据库的 version 字段)
PUT /products/_doc/1?version=5&version_type=external
{
"name": "华为手机",
"price": 3999
}

// 规则:
// 外部版本号必须 > 当前版本号
// 否则返回 409 冲突错误

使用场景:

场景:MySQL 数据同步到 ES

MySQL:
id=1, name="华为手机", price=3999, version=5

ES:
_id=1, name="华为手机", price=3999, _version=5

同步时使用外部版本号:
✓ 保证数据一致性
✓ 防止旧数据覆盖新数据

2.4 文档路由

2.4.1 默认路由

文档路由公式:
shard = hash(_id) % number_of_primary_shards

示例(3 个主分片):
doc1 → hash("1") % 3 = 1 → 存储在分片 1
doc2 → hash("2") % 3 = 2 → 存储在分片 2
doc3 → hash("3") % 3 = 0 → 存储在分片 0

路由流程:

客户端

协调节点(任意节点)

计算路由:shard = hash(_id) % number_of_primary_shards

转发到目标分片所在节点

主分片写入

副本分片同步

返回结果

2.4.2 自定义路由

// 使用自定义路由值
PUT /products/_doc/1?routing=user123
{
"userId": "user123",
"name": "华为手机",
"price": 3999
}

// 路由公式变为:
// shard = hash("user123") % number_of_primary_shards

// 查询时也要指定相同的路由值
GET /products/_doc/1?routing=user123

自定义路由的好处:

场景:用户订单系统

不使用自定义路由:
user123 的订单分散在所有分片
查询时需要查询所有分片
性能差

使用自定义路由(routing=userId):
user123 的所有订单在同一个分片
查询时只需查询一个分片
性能好

Java 代码示例:

@Service
public class OrderService {

@Autowired
private RestHighLevelClient esClient;

/**
* 创建订单(使用用户ID作为路由)
*/

public void createOrder(Order order) throws IOException {
IndexRequest request = new IndexRequest("orders")
.id(String.valueOf(order.getId()))
.routing(order.getUserId()) // 使用用户ID作为路由
.source(
"orderId", order.getId(),
"userId", order.getUserId(),
"productName", order.getProductName(),
"amount", order.getAmount()
);

esClient.index(request, RequestOptions.DEFAULT);
}

/**
* 查询用户的所有订单(只查询一个分片)
*/

public List<Order> getUserOrders(String userId) throws IOException {
SearchRequest request = new SearchRequest("orders")
.routing(userId) // 指定路由,只查询一个分片
.source(new SearchSourceBuilder()
.query(QueryBuilders.termQuery("userId", userId))
);

SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);

// 转换结果
return Arrays.stream(response.getHits().getHits())
.map(hit -> convertToOrder(hit.getSourceAsMap()))
.collect(Collectors.toList());
}
}

2.4.3 路由的注意事项

⚠️ 注意事项:

1. 写入和查询必须使用相同的路由值
PUT /orders/_doc/1?routing=user123
GET /orders/_doc/1?routing=user123 // 必须指定

2. 路由值分布要均匀
❌ 不好:routing=category(类别少,分布不均)
✓ 好:routing=userId(用户多,分布均匀)

3. 路由会影响分片分布
如果路由值分布不均,会导致某些分片数据过多

4. 删除文档也要指定路由
DELETE /orders/_doc/1?routing=user123

2.5 文档操作

2.5.1 创建文档

方法 1:PUT(指定 ID)

PUT /products/_doc/1
{
"name": "华为手机",
"price": 3999
}

方法 2:POST(自动生成 ID)

POST /products/_doc
{
"name": "小米手机",
"price": 2999
}

方法 3:PUT + _create(仅创建,ID 存在则失败)

PUT /products/_create/1
{
"name": "华为手机",
"price": 3999
}
// 如果 ID=1 已存在,返回 409 冲突错误

2.5.2 查询文档

根据 ID 查询:

GET /products/_doc/1

批量查询:

GET /products/_mget
{
"ids": ["1", "2", "3"]
}

条件查询:

GET /products/_search
{
"query": {
"match": {
"name": "华为"
}
}
}

2.5.3 更新文档

方法 1:全量更新(覆盖)

PUT /products/_doc/1
{
"name": "华为手机",
"price": 3899
}
// 整个文档被替换

方法 2:部分更新

POST /products/_update/1
{
"doc": {
"price": 3899
}
}
// 只更新 price 字段,其他字段保持不变

方法 3:脚本更新

POST /products/_update/1
{
"script": {
"source": "ctx._source.price -= params.discount",
"params": {
"discount": 100
}
}
}
// 价格减少 100

方法 4:upsert(不存在则创建)

POST /products/_update/1
{
"doc": {
"price": 3899
},
"doc_as_upsert": true
}
// 如果文档不存在,则创建

2.5.4 删除文档

根据 ID 删除:

DELETE /products/_doc/1

根据条件删除:

POST /products/_delete_by_query
{
"query": {
"range": {
"price": {
"lt": 1000
}
}
}
}
// 删除价格小于 1000 的所有商品

2.5.5 批量操作(Bulk API)

POST /_bulk
{"index": {"_index": "products", "_id": "1"}}
{"name": "华为手机", "price": 3999}
{"index": {"_index": "products", "_id": "2"}}
{"name": "小米手机", "price": 2999}
{"update": {"_index": "products", "_id": "1"}}
{"doc": {"price": 3899}}
{"delete": {"_index": "products", "_id": "3"}}

Bulk API 的特点:

✓ 批量操作,性能高
✓ 减少网络开销
✓ 单个操作失败不影响其他操作
⚠️ 每行必须以换行符结尾
⚠️ 最后一行也要换行符

Java 代码示例:

@Service
public class ProductService {

@Autowired
private RestHighLevelClient esClient;

/**
* 批量同步商品到 ES
*/

public void batchSyncProducts(List<Product> products) throws IOException {
BulkRequest bulkRequest = new BulkRequest();

for (Product product : products) {
IndexRequest indexRequest = new IndexRequest("products")
.id(String.valueOf(product.getId()))
.source(
"productId", product.getId(),
"name", product.getName(),
"price", product.getPrice(),
"category", product.getCategory()
);

bulkRequest.add(indexRequest);
}

BulkResponse bulkResponse = esClient.bulk(bulkRequest, RequestOptions.DEFAULT);

// 检查是否有失败的操作
if (bulkResponse.hasFailures()) {
for (BulkItemResponse item : bulkResponse.getItems()) {
if (item.isFailed()) {
log.error("批量操作失败: {}, 原因: {}",
item.getId(), item.getFailureMessage());
}
}
}
}
}

2.6 文档的 _source 字段

2.6.1 什么是 _source?

{
"_index": "products",
"_id": "1",
"_source": { // 原始 JSON 文档
"name": "华为手机",
"price": 3999,
"category": "电子产品"
}
}

_source 的作用:

1. 返回原始文档:查询时返回完整数据
2. 部分更新:更新时需要读取原始数据
3. Reindex:重建索引时需要原始数据
4. 高亮显示:搜索结果高亮需要原始文本

2.6.2 禁用 _source

PUT /products
{
"mappings": {
"_source": {
"enabled": false // 禁用 _source
},
"properties": {
"name": { "type": "text" },
"price": { "type": "double" }
}
}
}

禁用 _source 的影响:

✓ 节省存储空间(约 30-50%)
✗ 无法返回原始文档
✗ 无法部分更新
✗ 无法 Reindex
✗ 无法高亮显示

适用场景:
– 只需要搜索,不需要返回原始数据
– 数据量特别大,存储成本高
– 数据可以从其他地方获取

2.6.3 过滤 _source

PUT /products
{
"mappings": {
"_source": {
"includes": ["name", "price"], // 只存储这些字段
"excludes": ["description"] // 排除这些字段
}
}
}

查询时过滤 _source:

// 只返回指定字段
GET /products/_search
{
"_source": ["name", "price"],
"query": {
"match_all": {}
}
}

// 排除指定字段
GET /products/_search
{
"_source": {
"excludes": ["description"]
},
"query": {
"match_all": {}
}
}

三、映射(Mapping)

3.1 什么是映射?

映射定义了文档的结构和字段类型,类似于 MySQL 的表结构。

MySQL: CREATE TABLE products (
id INT,
name VARCHAR(100),
price DECIMAL(10,2)
);

ES: PUT /products
{
"mappings": {
"properties": {
"id": { "type": "long" },
"name": { "type": "text" },
"price": { "type": "double" }
}
}
}

3.2 动态映射 vs 静态映射

动态映射(自动推断):

POST /products/_doc/1
{
"name": "华为手机",
"price": 3999,
"onSale": true
}

// ES 自动推断类型:
// name → text
// price → long
// onSale → boolean

静态映射(手动定义):

PUT /products
{
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word"
},
"price": {
"type": "double"
},
"onSale": {
"type": "boolean"
}
}
}
}

⚠️ 映射一旦创建,字段类型不可修改!

四、数据类型

4.1 常用数据类型

类型说明示例
text 全文检索字段,会分词 商品名称、描述
keyword 精确匹配字段,不分词 状态、标签、ID
long 长整型 商品ID、数量
integer 整型 年龄、评分
double 双精度浮点 价格、评分
boolean 布尔值 是否上架
date 日期 创建时间
object 对象 嵌套数据
nested 嵌套对象 数组对象

4.2 text vs keyword

{
"mappings": {
"properties": {
"title": {
"type": "text", // 用于全文搜索
"fields": {
"keyword": {
"type": "keyword" // 用于精确匹配、排序、聚合
}
}
}
}
}
}

使用场景:

// 全文搜索(使用 text)
GET /products/_search
{
"query": {
"match": { "title": "华为手机" }
}
}

// 精确匹配(使用 keyword)
GET /products/_search
{
"query": {
"term": { "title.keyword": "华为 Mate 50" }
}
}

// 排序(使用 keyword)
GET /products/_search
{
"sort": [
{ "title.keyword": "asc" }
]
}

4.3 日期类型

{
"mappings": {
"properties": {
"createTime": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}

支持的格式:

// 字符串
{ "createTime": "2024-01-17 10:30:00" }

// 时间戳(毫秒)
{ "createTime": 1705460400000 }

4.4 对象类型

{
"mappings": {
"properties": {
"user": {
"properties": {
"name": { "type": "text" },
"age": { "type": "integer" }
}
}
}
}
}

// 文档示例
{
"user": {
"name": "张三",
"age": 25
}
}

4.5 nested 类型

为什么需要 nested?

// 使用 object(错误)
{
"comments": [
{ "user": "张三", "content": "很好" },
{ "user": "李四", "content": "不错" }
]
}

// ES 内部存储为:
{
"comments.user": ["张三", "李四"],
"comments.content": ["很好", "不错"]
}
// 问题:无法保持对象关联!

使用 nested(正确):

{
"mappings": {
"properties": {
"comments": {
"type": "nested",
"properties": {
"user": { "type": "keyword" },
"content": { "type": "text" }
}
}
}
}
}

五、分片与副本

5.1 分片的作用

单机存储限制:1TB

分片到3个节点

每个节点:333GB

支持水平扩展

5.2 分片策略

PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}

分片数量建议:

数据量 < 10GB:1个分片
数据量 10-100GB:3-5个分片
数据量 > 100GB:根据节点数和数据量计算

计算公式:

分片数 = 数据总量 / 单分片目标大小(20-50GB)

5.3 副本的作用

┌─────────────────────────────────────────┐
│ Node 1 Node 2 Node 3 │
├─────────────────────────────────────────┤
│ Shard 0 Shard 1 Shard 2 │ 主分片
│ Replica 1 Replica 2 Replica 0 │ 副本分片
└─────────────────────────────────────────┘

作用:
1. 高可用:节点故障时自动切换
2. 负载均衡:读请求分散到副本

六、索引模板

6.1 为什么需要模板?

场景:每天创建一个日志索引
logs-2024-01-01
logs-2024-01-02
logs-2024-01-03

问题:每次都要手动设置映射和配置?
解决:使用索引模板!

6.2 创建模板

PUT /_index_template/logs_template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"timestamp": { "type": "date" },
"level": { "type": "keyword" },
"message": { "type": "text" }
}
}
}
}

自动应用:

// 创建索引时自动应用模板
PUT /logs20240117
// 自动继承 logs_template 的配置

七、实战:设计商品索引

PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"ik_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word"
}
}
}
},
"mappings": {
"properties": {
"productId": {
"type": "long"
},
"name": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"category": {
"type": "keyword"
},
"price": {
"type": "double"
},
"stock": {
"type": "integer"
},
"onSale": {
"type": "boolean"
},
"description": {
"type": "text",
"analyzer": "ik_max_word"
},
"tags": {
"type": "keyword"
},
"createTime": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"images": {
"type": "keyword",
"index": false
}
}
}
}

学习检查清单

  • 理解索引、文档、映射的关系
  • 掌握常用数据类型的使用场景
  • 理解 text 和 keyword 的区别
  • 理解分片和副本的作用
  • 能够设计合理的索引结构
  • 了解索引模板的使用

下一步

下一节我们将学习 Elasticsearch 的基本操作和 RESTful API。

赞(0)
未经允许不得转载:网硕互联帮助中心 » Elasticsearch核心概念详解
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!