Elasticsearch 基础 #
Elasticsearch(简称 ES)是一款基于 Lucene 的分布式搜索引擎,广泛用于全文检索、结构化数据检索、分析等场景。下面详细介绍 ES 的基础语法,包括索引、文档、查询、聚合等核心内容。
1. 基本概念 #
- 索引(Index):类似于数据库的数据库(database),是文档的集合。
- 类型(Type):7.x 及以后已废弃,原本类似于表(table)。
- 文档(Document):类似于数据库中的一行数据,是 JSON 格式的数据单元。
- 字段(Field):文档中的一个属性。
- 映射(Mapping):类似于数据库的表结构,定义字段类型等。
2. 索引操作 #
创建索引 #
PUT /my_index
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"name": { "type": "text" },
"age": { "type": "integer" },
"created": { "type": "date" }
}
}
}
查看所有索引 #
GET /_cat/indices?v
删除索引 #
DELETE /my_index
3. 文档操作 #
新增文档 #
POST /my_index/_doc/
{
"name": "张三",
"age": 25,
"created": "2025-06-14"
}
指定 ID 新增/替换文档 #
PUT /my_index/_doc/1
{
"name": "李四",
"age": 30,
"created": "2025-06-14"
}
获取文档 #
GET /my_index/_doc/1
更新文档 #
POST /my_index/_update/1
{
"doc": {
"age": 31
}
}
删除文档 #
DELETE /my_index/_doc/1
4. 查询语法(Query DSL) #
基本查询 #
查询所有 #
GET /my_index/_search
{
"query": {
"match_all": {}
}
}
条件查询(match) #
GET /my_index/_search
{
"query": {
"match": {
"name": "张三"
}
}
}
精确查询(term) #
GET /my_index/_search
{
"query": {
"term": {
"age": 25
}
}
}
多条件查询(bool) #
GET /my_index/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "张三" } },
{ "term": { "age": 25 } }
],
"must_not": [
{ "term": { "name": "李四" } }
],
"should": [
{ "term": { "age": 30 } }
]
}
}
}
范围查询(range) #
GET /my_index/_search
{
"query": {
"range": {
"age": {
"gte": 20,
"lte": 30
}
}
}
}
前缀/通配符查询 #
GET /my_index/_search
{
"query": {
"prefix": {
"name": "张"
}
}
}
GET /my_index/_search
{
"query": {
"wildcard": {
"name": "张*"
}
}
}
多字段查询(multi_match) #
GET /my_index/_search
{
"query": {
"multi_match": {
"query": "张三",
"fields": ["name", "desc"]
}
}
}
5. 分页与排序 #
分页 #
GET /my_index/_search
{
"from": 0,
"size": 10
}
排序 #
GET /my_index/_search
{
"sort": [
{ "age": { "order": "desc" } }
]
}
6. 聚合(Aggregation) #
计数、分组 #
GET /my_index/_search
{
"size": 0,
"aggs": {
"age_count": {
"terms": {
"field": "age"
}
}
}
}
平均值、最大值、最小值 #
GET /my_index/_search
{
"size": 0,
"aggs": {
"avg_age": { "avg": { "field": "age" } },
"max_age": { "max": { "field": "age" } },
"min_age": { "min": { "field": "age" } }
}
}
7. 映射与数据类型 #
常用类型有:
- text(全文检索)
- keyword(精确匹配)
- integer、long、float、double(数字)
- date(日期)
- boolean(布尔)
- object、nested(对象、嵌套对象)
8. 其他常用语法 #
批量操作(Bulk) #
POST /my_index/_bulk
{ "index": { "_id": 1 } }
{ "name": "张三", "age": 25 }
{ "index": { "_id": 2 } }
{ "name": "李四", "age": 30 }
高亮 #
GET /my_index/_search
{
"query": { "match": { "name": "张三" } },
"highlight": {
"fields": {
"name": {}
}
}
}
9. 常用管理命令 #
- 查看集群健康:
GET /_cluster/health - 查看节点信息:
GET /_cat/nodes?v - 查看索引mapping:
GET /my_index/_mapping - 查看索引settings:
GET /my_index/_settings
以上为 Elasticsearch 的基础语法,涵盖了索引、文档、查询、聚合、分页、排序、映射等常用操作。实际开发中还可以结合脚本、模板、别名、分词等高级特性进行扩展。
找到具有 2 个许可证类型的类似代码
XIUANGH: PUT /my_index GET /_cat/indices?v DELETE /my_index POST /my_index/_doc/ 这些东西是什么意思?
VS Code: 这些是 Elasticsearch 的 RESTful API 操作命令:
- PUT /my_index:创建名为 my_index 的索引。
- GET /_cat/indices?v:以表格形式列出所有索引及其详细信息。
- DELETE /my_index:删除名为 my_index 的索引。
- POST /my_index/_doc/:向 my_index 索引中添加一个新文档。 如果你想在 Visual Studio Code 中操作 Elasticsearch,可以在扩展市场搜索 ’elasticsearch’ 相关扩展。 使用 搜索扩展市场 命令查找 Elasticsearch 相关扩展。
搜索扩展市场
XIUANGH: 我现在是以Elastic search的初学者,我对这个中间件完全不了解,请你按照初学者的视角来全部尽可能特别的详细的给我介绍他的全部。基本概念,安装方式,基本语法文档格式储存的数据类型,索引之类的任何初学者需要了解的信息,请你全部详细的告诉我。
GitHub Copilot:
Elasticsearch 完全入门指南 #
什么是 Elasticsearch? #
Elasticsearch(简称 ES)是一个基于 Apache Lucene 构建的分布式搜索和分析引擎。简单来说,它就像是一个超级强大的搜索工具,可以帮你在海量数据中快速找到你想要的信息。
为什么需要 Elasticsearch? #
想象一下这些场景:
- 你在淘宝搜索"手机",瞬间返回成千上万的商品
- 你在百度搜索资料,几毫秒内就能找到相关内容
- 公司需要在几亿条日志中找到特定的错误信息
传统的数据库(如 MySQL)在处理这种大量数据的全文搜索时会很慢,而 Elasticsearch 专门为此而生。
基本概念详解 #
1. 核心概念对比 #
为了更好理解,我们用关系型数据库来类比:
| 关系型数据库 | Elasticsearch | 说明 |
|---|---|---|
| Database(数据库) | Index(索引) | 数据的容器 |
| Table(表) | 7.x后已废弃 | |
| Row(行) | Document(文档) | 一条数据记录 |
| Column(列) | Field(字段) | 数据的属性 |
| Schema(表结构) | Mapping(映射) | 定义数据结构 |
2. 详细概念解释 #
Index(索引) #
索引就像一个数据库,是存储相关文档的地方。
例如:
- user_index(用户索引)
- product_index(商品索引)
- log_index(日志索引)
Document(文档) #
文档是JSON格式的数据,类似数据库中的一行:
{
"_id": "1",
"name": "张三",
"age": 25,
"city": "北京",
"hobbies": ["读书", "游泳"]
}
Field(字段) #
文档中的每个属性就是字段:
- name: "张三" (字符串字段)
- age: 25 (数字字段)
- hobbies: [] (数组字段)
Mapping(映射) #
定义每个字段的数据类型和索引方式:
{
"properties": {
"name": { "type": "text" }, // 全文搜索
"age": { "type": "integer" }, // 整数
"city": { "type": "keyword" } // 精确匹配
}
}
安装 Elasticsearch #
方法一:Docker 安装(推荐新手) #
1. 安装 Docker #
首先确保你的 Mac 上安装了 Docker Desktop。
2. 启动 Elasticsearch #
# 单节点模式启动
docker run -d \
--name elasticsearch \
-p 9200:9200 \
-p 9300:9300 \
-e "discovery.type=single-node" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
elasticsearch:8.11.0
3. 验证安装 #
打开浏览器访问:http://localhost:9200 如果看到类似以下内容说明安装成功:
{
"name": "...",
"cluster_name": "docker-cluster",
"version": {
"number": "8.11.0"
}
}
方法二:Homebrew 安装 #
# 安装 Elasticsearch
brew install elasticsearch
# 启动服务
brew services start elasticsearch
# 停止服务
brew services stop elasticsearch
方法三:官方下载 #
- 访问 https://www.elastic.co/downloads/elasticsearch
- 下载 macOS 版本
- 解压后运行:
cd elasticsearch-8.11.0
./bin/elasticsearch
基本操作工具 #
1. 命令行工具(curl) #
# 检查集群状态
curl -X GET "localhost:9200/"
# 查看所有索引
curl -X GET "localhost:9200/_cat/indices?v"
2. Kibana(可视化工具) #
# Docker 启动 Kibana
docker run -d \
--name kibana \
--link elasticsearch:elasticsearch \
-p 5601:5601 \
kibana:8.11.0
访问:http://localhost:5601
3. VS Code 插件 #
在 VS Code 中搜索并安装:
- “Elasticsearch for VSCode”
- “Rest Client”
数据类型详解 #
1. 基本数据类型 #
字符串类型 #
{
"name": {
"type": "text", // 用于全文搜索,会被分词
"analyzer": "standard" // 分词器
},
"status": {
"type": "keyword" // 精确匹配,不分词
}
}
数字类型 #
{
"age": { "type": "integer" }, // 整数:-2^31 到 2^31-1
"price": { "type": "float" }, // 单精度浮点数
"salary": { "type": "double" }, // 双精度浮点数
"count": { "type": "long" } // 长整数:-2^63 到 2^63-1
}
布尔类型 #
{
"is_active": { "type": "boolean" } // true/false
}
日期类型 #
{
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
2. 复杂数据类型 #
对象类型 #
{
"user": {
"type": "object",
"properties": {
"name": { "type": "text" },
"age": { "type": "integer" }
}
}
}
数组类型 #
{
"tags": ["elasticsearch", "search", "database"] // 字符串数组
}
嵌套类型 #
{
"users": {
"type": "nested",
"properties": {
"name": { "type": "text" },
"age": { "type": "integer" }
}
}
}
索引操作详解 #
1. 创建索引 #
基本创建 #
PUT /my_first_index
带设置和映射的创建 #
PUT /user_index
{
"settings": {
"number_of_shards": 1, // 分片数
"number_of_replicas": 1 // 副本数
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "standard"
},
"age": {
"type": "integer"
},
"email": {
"type": "keyword"
},
"created_at": {
"type": "date"
}
}
}
}
2. 查看索引 #
查看所有索引 #
GET /_cat/indices?v
查看特定索引信息 #
GET /user_index
查看索引映射 #
GET /user_index/_mapping
查看索引设置 #
GET /user_index/_settings
3. 修改索引 #
添加新字段映射 #
PUT /user_index/_mapping
{
"properties": {
"phone": {
"type": "keyword"
}
}
}
修改索引设置 #
PUT /user_index/_settings
{
"number_of_replicas": 2
}
4. 删除索引 #
DELETE /user_index
文档操作详解 #
1. 添加文档 #
自动生成ID #
POST /user_index/_doc
{
"name": "张三",
"age": 25,
"email": "zhangsan@example.com",
"created_at": "2025-06-14T10:30:00"
}
指定ID #
PUT /user_index/_doc/1
{
"name": "李四",
"age": 30,
"email": "lisi@example.com",
"created_at": "2025-06-14T11:00:00"
}
2. 获取文档 #
根据ID获取 #
GET /user_index/_doc/1
检查文档是否存在 #
HEAD /user_index/_doc/1
3. 更新文档 #
部分更新 #
POST /user_index/_update/1
{
"doc": {
"age": 31,
"phone": "13812345678"
}
}
使用脚本更新 #
POST /user_index/_update/1
{
"script": {
"source": "ctx._source.age += params.increment",
"params": {
"increment": 1
}
}
}
更新或插入(upsert) #
POST /user_index/_update/2
{
"doc": {
"name": "王五",
"age": 28
},
"upsert": {
"name": "王五",
"age": 28,
"email": "wangwu@example.com",
"created_at": "2025-06-14T12:00:00"
}
}
4. 删除文档 #
DELETE /user_index/_doc/1
5. 批量操作 #
POST /user_index/_bulk
{"index":{"_id":"1"}}
{"name":"张三","age":25,"email":"zhangsan@example.com"}
{"index":{"_id":"2"}}
{"name":"李四","age":30,"email":"lisi@example.com"}
{"update":{"_id":"1"}}
{"doc":{"age":26}}
{"delete":{"_id":"2"}}
查询语法(Query DSL) #
1. 基础查询 #
查询所有文档 #
GET /user_index/_search
{
"query": {
"match_all": {}
}
}
分页查询 #
GET /user_index/_search
{
"query": {
"match_all": {}
},
"from": 0, // 从第几条开始
"size": 10 // 返回多少条
}
指定返回字段 #
GET /user_index/_search
{
"query": {
"match_all": {}
},
"_source": ["name", "age"] // 只返回指定字段
}
2. 全文搜索 #
match 查询(分词匹配) #
GET /user_index/_search
{
"query": {
"match": {
"name": "张三"
}
}
}
multi_match 查询(多字段搜索) #
GET /user_index/_search
{
"query": {
"multi_match": {
"query": "张三",
"fields": ["name", "email"]
}
}
}
match_phrase 查询(短语匹配) #
GET /user_index/_search
{
"query": {
"match_phrase": {
"name": "张 三"
}
}
}
3. 精确查询 #
term 查询(精确匹配单个值) #
GET /user_index/_search
{
"query": {
"term": {
"age": 25
}
}
}
terms 查询(精确匹配多个值) #
GET /user_index/_search
{
"query": {
"terms": {
"age": [25, 30, 35]
}
}
}
range 查询(范围匹配) #
GET /user_index/_search
{
"query": {
"range": {
"age": {
"gte": 20, // 大于等于
"lte": 30 // 小于等于
}
}
}
}
4. 复合查询 #
bool 查询(布尔组合) #
GET /user_index/_search
{
"query": {
"bool": {
"must": [ // 必须匹配(AND)
{
"match": {
"name": "张三"
}
}
],
"must_not": [ // 必须不匹配(NOT)
{
"term": {
"age": 25
}
}
],
"should": [ // 应该匹配(OR,可选)
{
"term": {
"age": 30
}
}
],
"filter": [ // 过滤(不影响评分)
{
"range": {
"age": {
"gte": 18
}
}
}
]
}
}
}
5. 通配符和正则查询 #
wildcard 查询(通配符) #
GET /user_index/_search
{
"query": {
"wildcard": {
"name": "张*"
}
}
}
regexp 查询(正则表达式) #
GET /user_index/_search
{
"query": {
"regexp": {
"email": ".*@example\\.com"
}
}
}
6. 排序 #
GET /user_index/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": {
"order": "desc" // desc降序,asc升序
}
},
{
"created_at": {
"order": "desc"
}
}
]
}
7. 高亮显示 #
GET /user_index/_search
{
"query": {
"match": {
"name": "张三"
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": ["<em>"],
"post_tags": ["</em>"]
}
}
}
}
聚合分析(Aggregation) #
1. 指标聚合(Metric Aggregations) #
统计聚合 #
GET /user_index/_search
{
"size": 0, // 不返回文档,只要聚合结果
"aggs": {
"avg_age": {
"avg": {
"field": "age"
}
},
"max_age": {
"max": {
"field": "age"
}
},
"min_age": {
"min": {
"field": "age"
}
},
"sum_age": {
"sum": {
"field": "age"
}
}
}
}
统计信息聚合 #
GET /user_index/_search
{
"size": 0,
"aggs": {
"age_stats": {
"stats": {
"field": "age"
}
}
}
}
2. 桶聚合(Bucket Aggregations) #
terms 聚合(分组统计) #
GET /user_index/_search
{
"size": 0,
"aggs": {
"age_groups": {
"terms": {
"field": "age",
"size": 10
}
}
}
}
range 聚合(范围分组) #
GET /user_index/_search
{
"size": 0,
"aggs": {
"age_ranges": {
"range": {
"field": "age",
"ranges": [
{ "to": 25 },
{ "from": 25, "to": 35 },
{ "from": 35 }
]
}
}
}
}
date_histogram 聚合(时间分组) #
GET /user_index/_search
{
"size": 0,
"aggs": {
"monthly_users": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "month"
}
}
}
}
3. 嵌套聚合 #
GET /user_index/_search
{
"size": 0,
"aggs": {
"age_groups": {
"terms": {
"field": "age"
},
"aggs": {
"avg_score": {
"avg": {
"field": "score"
}
}
}
}
}
}
实际应用示例 #
示例1:电商商品搜索 #
1. 创建商品索引 #
PUT /products
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "standard"
},
"description": {
"type": "text"
},
"category": {
"type": "keyword"
},
"price": {
"type": "double"
},
"brand": {
"type": "keyword"
},
"tags": {
"type": "keyword"
},
"created_at": {
"type": "date"
}
}
}
}
2. 添加商品数据 #
POST /products/_bulk
{"index":{"_id":"1"}}
{"title":"iPhone 14 Pro","description":"苹果最新款手机","category":"手机","price":7999.0,"brand":"Apple","tags":["智能手机","iOS"],"created_at":"2025-06-14"}
{"index":{"_id":"2"}}
{"title":"Samsung Galaxy S23","description":"三星旗舰手机","category":"手机","price":6999.0,"brand":"Samsung","tags":["智能手机","Android"],"created_at":"2025-06-13"}
{"index":{"_id":"3"}}
{"title":"MacBook Pro","description":"苹果笔记本电脑","category":"电脑","price":12999.0,"brand":"Apple","tags":["笔记本","macOS"],"created_at":"2025-06-12"}
3. 搜索查询 #
# 搜索"苹果手机"
GET /products/_search
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "苹果 手机",
"fields": ["title^2", "description"] // title权重更高
}
}
],
"filter": [
{
"term": {
"category": "手机"
}
},
{
"range": {
"price": {
"gte": 5000,
"lte": 10000
}
}
}
]
}
},
"sort": [
{
"price": {
"order": "asc"
}
}
],
"highlight": {
"fields": {
"title": {},
"description": {}
}
}
}
4. 聚合分析 #
# 品牌和价格区间统计
GET /products/_search
{
"size": 0,
"aggs": {
"brands": {
"terms": {
"field": "brand"
}
},
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 5000 },
{ "from": 5000, "to": 10000 },
{ "from": 10000 }
]
}
}
}
}
示例2:日志分析 #
1. 创建日志索引 #
PUT /logs
{
"mappings": {
"properties": {
"timestamp": {
"type": "date"
},
"level": {
"type": "keyword"
},
"message": {
"type": "text"
},
"service": {
"type": "keyword"
},
"host": {
"type": "keyword"
},
"user_id": {
"type": "keyword"
}
}
}
}
2. 查询错误日志 #
GET /logs/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"level": "ERROR"
}
},
{
"range": {
"timestamp": {
"gte": "2025-06-14T00:00:00",
"lte": "2025-06-14T23:59:59"
}
}
}
]
}
},
"sort": [
{
"timestamp": {
"order": "desc"
}
}
]
}
常见问题和最佳实践 #
1. 性能优化 #
合理设计映射 #
# 不需要搜索的字段设置为 keyword
{
"id": { "type": "keyword", "index": false },
"status": { "type": "keyword" }
}
使用过滤器而非查询 #
# 使用 filter(更快,有缓存)
{
"query": {
"bool": {
"filter": [
{ "term": { "status": "active" } }
]
}
}
}
2. 分词器配置 #
中文分词器(IK) #
# 需要安装 IK 分词器插件
PUT /my_index
{
"settings": {
"analysis": {
"analyzer": {
"ik_max_word": {
"type": "ik_max_word"
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
3. 常用管理命令 #
# 查看集群健康状态
GET /_cluster/health
# 查看节点信息
GET /_cat/nodes?v
# 查看分片信息
GET /_cat/shards?v
# 查看索引统计
GET /my_index/_stats
# 强制合并分段
POST /my_index/_forcemerge
# 刷新索引
POST /my_index/_refresh
# 清空索引
POST /my_index/_delete_by_query
{
"query": {
"match_all": {}
}
}
常见错误和解决方案 #
1. 映射冲突 #
错误:mapper_parsing_exception
解决:检查字段类型定义,确保一致性
2. 分片不可用 #
错误:search_phase_execution_exception
解决:检查集群状态,确保所有分片正常
3. 内存不足 #
错误:circuit_breaking_exception
解决:增加JVM堆内存或优化查询
学习建议 #
- 先理解概念:索引、文档、映射、查询
- 动手练习:创建索引、添加数据、执行查询
- 学习顺序:基本操作 → 查询语法 → 聚合分析 → 性能优化
- 实践项目:构建一个简单的搜索应用
- 深入学习:集群管理、性能调优、安全配置
这份指南涵盖了 Elasticsearch 的核心知识点,建议你边学边练,逐步掌握这个强大的搜索引擎!
Elasticsearch 为什么搜索这么快?深度技术分析 #
核心原理概述 #
Elasticsearch 之所以搜索速度极快,主要基于以下几个核心技术:
- 倒排索引(Inverted Index)
- 分片和并行处理
- 内存优化和缓存机制
- 高效的数据结构
- 分词和预处理
让我们逐一深入分析:
1. 倒排索引 - 核心搜索引擎 #
什么是倒排索引? #
想象你要在一本书中找到所有包含"搜索"这个词的页面。传统方式是逐页翻阅(类似数据库的全表扫描),而倒排索引就像书后面的关键词索引表。
对比分析 #
传统数据库查询(正向索引) #
-- MySQL 全文搜索需要扫描整个表
SELECT * FROM articles WHERE content LIKE '%elasticsearch%';
时间复杂度:O(n) - 需要检查每一行数据
Elasticsearch 倒排索引 #
// 倒排索引结构示例
{
"elasticsearch": {
"doc_freq": 3, // 文档频率
"postings": [ // 包含该词的文档列表
{"doc_id": 1, "term_freq": 2, "positions": [5, 23]},
{"doc_id": 3, "term_freq": 1, "positions": [12]},
{"doc_id": 7, "term_freq": 3, "positions": [1, 15, 34]}
]
}
}
时间复杂度:O(1) - 直接定位到包含该词的文档
具体举例说明 #
假设我们有以下三篇文档:
文档1: "Elasticsearch是一个强大的搜索引擎"
文档2: "Lucene是Elasticsearch的底层引擎"
文档3: "搜索引擎技术发展很快"
传统数据库存储: #
| ID | 内容 |
|----|------|
| 1 | Elasticsearch是一个强大的搜索引擎 |
| 2 | Lucene是Elasticsearch的底层引擎 |
| 3 | 搜索引擎技术发展很快 |
查找"搜索"时需要逐行检查。
Elasticsearch 倒排索引: #
{
"elasticsearch": [1, 2],
"搜索": [1, 3],
"引擎": [1, 2, 3],
"强大": [1],
"lucene": [2],
"技术": [3]
}
查找"搜索"时直接返回文档[1, 3],无需遍历。
2. 分片和分布式并行处理 #
分片机制 #
Elasticsearch 将一个索引分成多个分片(Shard),每个分片独立存储和搜索。
# 创建5个分片的索引
PUT /my_index
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}
并行搜索过程 #
用户查询 "elasticsearch tutorial"
↓
协调节点接收请求
↓
并行发送到5个分片
┌─────┬─────┬─────┬─────┬─────┐
│Shard│Shard│Shard│Shard│Shard│
│ 0 │ 1 │ 2 │ 3 │ 4 │
└─────┴─────┴─────┴─────┴─────┘
↓
每个分片并行搜索
↓
协调节点聚合结果
↓
返回最终结果
性能对比 #
单分片搜索:1000万文档 → 100ms
5分片并行:每分片200万文档 → 25ms(理论上)
实际测试代码:
# 搜索查询
GET /large_index/_search
{
"query": {
"match": {
"content": "elasticsearch"
}
}
}
# 响应时间统计
{
"took": 15, // 15毫秒完成搜索
"hits": {
"total": {"value": 10000}
}
}
3. 内存优化和多层缓存 #
缓存层次结构 #
请求 → Query Cache → Filter Cache → OS Page Cache → 磁盘
↓ ↓ ↓
查询结果缓存 过滤器缓存 操作系统缓存
具体缓存机制 #
1. 查询缓存(Query Cache) #
# 相同查询直接返回缓存结果
GET /my_index/_search
{
"query": {
"term": { "status": "published" }
}
}
# 第一次:50ms,第二次:2ms
2. 字段数据缓存(Fielddata Cache) #
# 聚合查询时将字段数据载入内存
GET /my_index/_search
{
"aggs": {
"avg_price": {
"avg": { "field": "price" }
}
}
}
3. 操作系统页面缓存 #
# ES 充分利用 OS 的文件系统缓存
# 倒排索引文件自动缓存在内存中
内存使用监控 #
# 查看缓存使用情况
GET /_nodes/stats/indices/query_cache,fielddata
4. 高效数据结构 #
Term Dictionary + Term Index #
Elasticsearch 使用双层数据结构来快速定位词项:
Term Index (内存中的前缀树)
├── "a" → Block 1
├── "el" → Block 5
└── "z" → Block 20
Term Dictionary (磁盘上的有序列表)
Block 5:
├── "elastic" → Posting List
├── "elasticsearch" → Posting List
└── "element" → Posting List
FST(Finite State Transducer) #
// 词典压缩示例
原始词典:
["cat", "cats", "dog", "dogs"]
FST 压缩后:
c → a → t → [end:1]
↓
s → [end:2]
d → o → g → [end:3]
↓
s → [end:4]
内存节省:80%以上
5. 智能分词和预处理 #
分词器工作原理 #
# 原始文本
"The quick brown fox jumps over the lazy dog"
# Standard 分词器处理后
["the", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
# 构建倒排索引
{
"quick": [doc1],
"brown": [doc1],
"fox": [doc1],
// ...
}
中文分词示例 #
# IK 分词器处理
原文:"中华人民共和国成立于1949年"
最细粒度分词:
["中华人民共和国", "中华人民", "中华", "华人", "人民", "共和国", "共和", "成立", "于", "1949", "年"]
索引构建:
{
"中华": [doc1],
"人民": [doc1],
"共和国": [doc1],
"成立": [doc1]
}
6. 性能对比测试 #
实际测试数据 #
数据集:100万篇文章,平均每篇1000字 #
# MySQL 全文搜索
SELECT * FROM articles WHERE MATCH(content) AGAINST('elasticsearch' IN NATURAL LANGUAGE MODE);
# 平均耗时:2.5秒
# Elasticsearch 搜索
GET /articles/_search
{
"query": {
"match": {
"content": "elasticsearch"
}
}
}
# 平均耗时:15毫秒
性能提升:167倍
复杂查询对比 #
多条件查询 #
-- MySQL
SELECT * FROM articles
WHERE MATCH(content) AGAINST('elasticsearch tutorial')
AND category = 'tech'
AND publish_date >= '2024-01-01'
AND author_id IN (1,2,3,4,5)
ORDER BY relevance_score DESC
LIMIT 10;
-- 耗时:8-15秒
# Elasticsearch
GET /articles/_search
{
"query": {
"bool": {
"must": [
{"match": {"content": "elasticsearch tutorial"}}
],
"filter": [
{"term": {"category": "tech"}},
{"range": {"publish_date": {"gte": "2024-01-01"}}},
{"terms": {"author_id": [1,2,3,4,5]}}
]
}
},
"sort": [{"_score": {"order": "desc"}}],
"size": 10
}
# 耗时:20-50毫秒
性能提升:300-750倍
7. 架构优化细节 #
写入优化 - Refresh 机制 #
# 数据写入流程
文档写入 → 内存缓冲区 → 刷新到文件系统缓存 → 可搜索
(实时) (1秒后默认) (立即)
# 自定义刷新间隔
PUT /my_index/_settings
{
"refresh_interval": "5s" // 5秒刷新一次
}
段合并优化 #
# 查看段信息
GET /my_index/_segments
# 手动强制合并
POST /my_index/_forcemerge?max_num_segments=1
路由优化 #
# 使用路由将相关文档分配到同一分片
PUT /my_index/_doc/1?routing=user123
{
"user_id": "user123",
"content": "用户相关内容"
}
# 搜索时指定路由,减少搜索分片数
GET /my_index/_search?routing=user123
{
"query": {
"term": {"user_id": "user123"}
}
}
8. 实际应用场景性能分析 #
电商搜索场景 #
# 商品搜索:关键词 + 多维过滤
GET /products/_search
{
"query": {
"bool": {
"must": [
{"multi_match": {
"query": "iPhone 14",
"fields": ["title^3", "description", "brand"]
}}
],
"filter": [
{"range": {"price": {"gte": 5000, "lte": 8000}}},
{"term": {"category": "手机"}},
{"term": {"in_stock": true}}
]
}
},
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{"to": 3000},
{"from": 3000, "to": 6000},
{"from": 6000}
]
}
}
}
}
# 1000万商品中搜索:平均15-30ms
日志分析场景 #
# 实时日志搜索和聚合
GET /logs-2025-06/_search
{
"query": {
"bool": {
"must": [
{"match": {"message": "ERROR"}},
{"range": {"@timestamp": {"gte": "now-1h"}}}
]
}
},
"aggs": {
"error_timeline": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "minute"
}
},
"top_errors": {
"terms": {"field": "error_code.keyword"}
}
}
}
# 亿级日志中查询:平均50-100ms
9. 与其他搜索技术对比 #
技术对比表 #
| 特性 | MySQL LIKE | MySQL 全文索引 | Solr | Elasticsearch |
|---|---|---|---|---|
| 搜索算法 | 字符串匹配 | 倒排索引 | 倒排索引 | 倒排索引 |
| 分布式 | 困难 | 不支持 | 支持 | 原生支持 |
| 实时性 | 实时 | 延迟 | 近实时 | 近实时 |
| 复杂查询 | 简单 | 有限 | 强大 | 强大 |
| 聚合分析 | 基础 | 基础 | 强大 | 强大 |
| 水平扩展 | 困难 | 困难 | 手动 | 自动 |
性能基准测试 #
数据量:1000万文档
查询:单词匹配
MySQL LIKE: 15-30秒
MySQL 全文索引: 0.5-2秒
Solr: 20-50毫秒
Elasticsearch: 15-30毫秒
10. 优化建议和最佳实践 #
硬件优化 #
# 推荐配置
- CPU: 多核心(并行处理)
- 内存: 大容量(缓存和聚合)
- 存储: SSD(快速I/O)
- 网络: 千兆网络(集群通信)
# JVM 内存设置
export ES_JAVA_OPTS="-Xms16g -Xmx16g" # 不超过物理内存的50%
索引设计优化 #
# 1. 合理设置分片数
PUT /my_index
{
"settings": {
"number_of_shards": 3, // 根据数据量和节点数决定
"number_of_replicas": 1, // 至少1个副本
"refresh_interval": "30s" // 适当延长刷新间隔
}
}
# 2. 优化映射设置
{
"mappings": {
"properties": {
"id": {
"type": "keyword",
"index": false // 不需要搜索的字段禁用索引
},
"content": {
"type": "text",
"analyzer": "standard",
"store": false // 不存储原始值节省空间
}
}
}
}
查询优化 #
# 1. 使用过滤器而非查询(filter vs query)
{
"query": {
"bool": {
"filter": [ // 使用filter,有缓存
{"term": {"status": "active"}}
]
}
}
}
# 2. 避免深度分页
{
"from": 0,
"size": 20, // 推荐小于10000
"search_after": [1234567890] // 使用search_after替代深度分页
}
总结 #
Elasticsearch 搜索速度快的核心技术原理:
1. 倒排索引 - 根本原因 #
- 将"文档→词"的关系转换为"词→文档"
- 时间复杂度从O(n)降至O(1)
- 类似书籍的索引目录,直接定位
2. 分布式并行 - 扩展能力 #
- 数据分片,并行处理
- 水平扩展,性能线性提升
- 节点越多,搜索越快
3. 多层缓存 - 性能加速 #
- 查询缓存、字段缓存、OS缓存
- 内存操作远快于磁盘I/O
- 智能缓存策略,命中率高
4. 高效数据结构 - 存储优化 #
- FST压缩词典,节省内存
- Term Index + Term Dictionary双层结构
- 二进制格式,读取效率高
5. 预处理分词 - 搜索准备 #
- 建索引时完成分词和预处理
- 搜索时直接使用预构建的索引
- 避免运行时的复杂计算
这些技术的协同作用,使得Elasticsearch在处理TB级数据时仍能保持毫秒级的搜索响应速度,这就是它成为业界标准搜索引擎的根本原因。
Elasticsearch 擅长与不擅长的事情详细分析 #
Elasticsearch 擅长的事情 #
1. 全文搜索和文本分析 #
擅长原因 #
- 基于 Apache Lucene,专为全文搜索设计
- 强大的分词器和文本分析能力
- 支持多语言分词和自定义分析器
具体应用场景 #
电商商品搜索
GET /products/_search
{
"query": {
"multi_match": {
"query": "苹果手机 64G",
"fields": ["title^3", "description", "brand", "specs"],
"type": "best_fields",
"fuzziness": "AUTO" // 支持模糊匹配
}
},
"highlight": {
"fields": {
"title": {},
"description": {}
}
}
}
内容管理系统
# 搜索包含特定关键词的文章
GET /articles/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"content": {
"query": "人工智能 机器学习",
"operator": "and"
}
}
}
],
"should": [
{"match": {"title": {"query": "AI", "boost": 2.0}}}
]
}
}
}
优势表现:
- 支持同义词搜索:搜索"手机"能找到"移动电话"
- 智能纠错:搜索"iphone"能找到"iPhone"
- 相关性评分:最相关的结果排在前面
- 多语言支持:中英文混合搜索
2. 实时数据分析和聚合 #
擅长原因 #
- 强大的聚合框架(Aggregations)
- 支持复杂的多维度分析
- 近实时的数据处理能力
具体应用场景 #
电商数据分析
GET /orders/_search
{
"size": 0,
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "day"
},
"aggs": {
"daily_revenue": {
"sum": {"field": "total_amount"}
},
"avg_order_value": {
"avg": {"field": "total_amount"}
}
}
},
"top_categories": {
"terms": {
"field": "category.keyword",
"size": 10
},
"aggs": {
"category_revenue": {
"sum": {"field": "total_amount"}
}
}
},
"price_distribution": {
"histogram": {
"field": "total_amount",
"interval": 100
}
}
}
}
用户行为分析
GET /user_events/_search
{
"size": 0,
"aggs": {
"user_segments": {
"terms": {"field": "user_type.keyword"},
"aggs": {
"avg_session_duration": {
"avg": {"field": "session_duration"}
},
"page_views": {
"terms": {"field": "page_url.keyword"}
},
"conversion_rate": {
"filter": {"term": {"event_type": "purchase"}},
"aggs": {
"conversion_count": {"value_count": {"field": "user_id"}}
}
}
}
}
}
}
优势表现:
- 实时计算:数据写入后秒级可查询
- 复杂聚合:支持嵌套、管道聚合
- 高性能:亿级数据聚合毫秒级响应
3. 日志分析和监控 #
擅长原因 #
- ELK Stack(Elasticsearch + Logstash + Kibana)生态完善
- 时间序列数据处理优化
- 支持大量并发写入
具体应用场景 #
应用日志分析
# 分析错误日志趋势
GET /app-logs-*/_search
{
"query": {
"bool": {
"must": [
{"term": {"level": "ERROR"}},
{"range": {"@timestamp": {"gte": "now-24h"}}}
]
}
},
"aggs": {
"error_timeline": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "hour"
}
},
"top_error_messages": {
"terms": {
"field": "message.keyword",
"size": 10
}
},
"error_by_service": {
"terms": {"field": "service_name.keyword"}
}
}
}
系统监控指标
# CPU和内存使用率分析
GET /system-metrics-*/_search
{
"size": 0,
"aggs": {
"servers": {
"terms": {"field": "hostname.keyword"},
"aggs": {
"avg_cpu": {
"avg": {"field": "cpu_percent"}
},
"max_memory": {
"max": {"field": "memory_percent"}
},
"cpu_timeline": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "5m"
},
"aggs": {
"avg_cpu": {"avg": {"field": "cpu_percent"}}
}
}
}
}
}
}
优势表现:
- 高吞吐:每秒处理数万条日志
- 灵活查询:复杂的时间范围和条件过滤
- 可视化:配合Kibana进行图表展示
4. 地理位置搜索 #
擅长原因 #
- 内置地理空间数据类型
- 支持多种地理查询(距离、边界、形状)
- 高效的地理空间索引
具体应用场景 #
附近商家搜索
PUT /restaurants
{
"mappings": {
"properties": {
"name": {"type": "text"},
"location": {"type": "geo_point"},
"category": {"type": "keyword"}
}
}
}
# 搜索附近2公里内的餐厅
GET /restaurants/_search
{
"query": {
"bool": {
"must": [
{"term": {"category": "中餐"}}
],
"filter": [
{
"geo_distance": {
"distance": "2km",
"location": {
"lat": 39.9042,
"lon": 116.4074
}
}
}
]
}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 39.9042,
"lon": 116.4074
},
"order": "asc",
"unit": "km"
}
}
]
}
配送范围查询
# 查询多边形区域内的配送点
GET /delivery_points/_search
{
"query": {
"geo_polygon": {
"location": {
"points": [
{"lat": 40.0, "lon": 116.0},
{"lat": 40.0, "lon": 117.0},
{"lat": 39.0, "lon": 117.0},
{"lat": 39.0, "lon": 116.0}
]
}
}
}
}
优势表现:
- 精确计算:支持各种距离计算算法
- 复杂查询:支持多边形、圆形、矩形区域查询
- 高性能:地理空间索引优化
5. 推荐系统和个性化搜索 #
擅长原因 #
- 灵活的评分机制
- 支持自定义评分函数
- 机器学习特性
具体应用场景 #
个性化商品推荐
GET /products/_search
{
"query": {
"function_score": {
"query": {
"bool": {
"should": [
{"terms": {"category": ["电子产品", "数码配件"]}},
{"terms": {"brand": ["Apple", "华为"]}}
]
}
},
"functions": [
{
"filter": {"term": {"user_viewed": true}},
"weight": 1.5
},
{
"filter": {"range": {"rating": {"gte": 4.0}}},
"weight": 1.2
},
{
"field_value_factor": {
"field": "sales_count",
"factor": 0.1,
"modifier": "log1p"
}
}
],
"boost_mode": "multiply"
}
}
}
基于用户行为的相似度搜索
# More Like This 查询
GET /articles/_search
{
"query": {
"more_like_this": {
"fields": ["title", "content", "tags"],
"like": [
{"_index": "articles", "_id": "1"}
],
"min_term_freq": 1,
"max_query_terms": 12
}
}
}
Elasticsearch 不擅长的事情 #
1. 事务处理(ACID) #
不擅长原因 #
- 分布式架构天然不支持ACID事务
- 最终一致性模型,非强一致性
- 无法保证跨文档的原子性操作
问题场景 #
电商订单处理
# ❌ ES无法保证以下操作的原子性
# 1. 扣减库存
PUT /inventory/_doc/product123
{
"quantity": 99 // 从100减到99
}
# 2. 创建订单
PUT /orders/_doc/order456
{
"product_id": "product123",
"quantity": 1,
"status": "pending"
}
# 3. 更新用户积分
POST /users/_update/user789
{
"script": {
"source": "ctx._source.points += params.increment",
"params": {"increment": 10}
}
}
如果第2步失败,第1步已经执行,库存会不一致。
银行转账场景
# ❌ 无法保证转账的原子性
# 账户A扣款
POST /accounts/_update/accountA
{
"script": "ctx._source.balance -= 1000"
}
# 账户B增款
POST /accounts/_update/accountB
{
"script": "ctx._source.balance += 1000"
}
正确做法:使用传统关系型数据库处理事务
-- ✅ MySQL 事务处理
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 'accountA';
UPDATE accounts SET balance = balance + 1000 WHERE id = 'accountB';
COMMIT;
2. 复杂关联查询 #
不擅长原因 #
- NoSQL设计,不支持SQL的JOIN操作
- 跨索引查询性能差
- 关联数据需要反规范化存储
问题场景 #
多表关联查询
-- ✅ 传统SQL轻松实现
SELECT u.name, o.order_date, p.product_name, od.quantity
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_details od ON o.id = od.order_id
JOIN products p ON od.product_id = p.id
WHERE u.city = '北京'
AND o.order_date >= '2025-01-01';
# ❌ ES需要复杂的嵌套查询和数据冗余
GET /orders/_search
{
"query": {
"bool": {
"must": [
{"nested": {
"path": "user",
"query": {"term": {"user.city": "北京"}}
}},
{"range": {"order_date": {"gte": "2025-01-01"}}}
]
}
}
}
# 需要在索引时就存储用户信息,造成数据冗余
用户权限管理
-- ✅ 复杂权限查询
SELECT DISTINCT r.resource_name
FROM users u
JOIN user_roles ur ON u.id = ur.user_id
JOIN roles r ON ur.role_id = r.id
JOIN role_permissions rp ON r.id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE u.username = 'admin'
AND p.action = 'read';
ES无法优雅地处理这种多层级关联。
3. 严格一致性要求 #
不擅长原因 #
- 分布式系统的最终一致性
- 数据刷新有延迟(默认1秒)
- 副本之间可能存在短暂不一致
问题场景 #
库存管理系统
# 问题:并发扣库存可能超卖
# 用户A查询库存
GET /inventory/_doc/product123
# 返回:{"quantity": 1}
# 用户B也查询库存
GET /inventory/_doc/product123
# 返回:{"quantity": 1}
# 两人同时下单,都认为有库存
# 实际结果:库存变成-1,出现超卖
金融交易系统
# 问题:余额查询和扣款之间的时间窗口
GET /accounts/_doc/user123
# 返回:{"balance": 1000}
# 0.5秒后扣款
POST /accounts/_update/user123
{
"script": "ctx._source.balance -= 1200"
}
# 可能成功执行,导致余额为负
正确做法:使用支持强一致性的数据库
-- ✅ 使用行锁保证一致性
SELECT balance FROM accounts WHERE id = 'user123' FOR UPDATE;
UPDATE accounts SET balance = balance - 1200 WHERE id = 'user123' AND balance >= 1200;
4. 小数据量的简单查询 #
不擅长原因 #
- 分布式架构开销大
- 索引构建和维护成本高
- 内存消耗大
问题场景 #
小型应用的用户管理
# 只有1000个用户的系统
# ES资源消耗:
- 内存:至少2GB
- 磁盘:索引开销大
- CPU:后台任务多
# MySQL资源消耗:
- 内存:几十MB
- 磁盘:数据文件小
- CPU:按需处理
简单配置表查询
-- ✅ 简单高效
SELECT * FROM system_config WHERE config_key = 'max_upload_size';
# ❌ 杀鸡用牛刀
GET /system_config/_search
{
"query": {
"term": {"config_key": "max_upload_size"}
}
}
5. 数据更新频繁的场景 #
不擅长原因 #
- 文档更新实际是删除+重建
- 频繁更新影响搜索性能
- 段合并开销大
问题场景 #
实时计数器
# ❌ 频繁更新计数器
POST /counters/_update/page_views
{
"script": "ctx._source.count += 1"
}
# 每秒数千次更新会严重影响性能
用户活跃状态
# ❌ 频繁更新用户在线状态
POST /users/_update/user123
{
"doc": {
"last_active": "2025-06-14T10:30:00",
"online": true
}
}
# 大量用户频繁上线下线会产生大量段文件
正确做法:使用Redis或关系型数据库
# ✅ Redis计数器
INCR page_views:home
EXPIRE page_views:home 86400
6. 复杂计算和统计 #
不擅长原因 #
- 聚合功能虽强但有限制
- 不支持窗口函数
- 复杂数学运算能力弱
问题场景 #
财务报表计算
-- ✅ SQL窗口函数
SELECT
month,
revenue,
LAG(revenue) OVER (ORDER BY month) as prev_month_revenue,
(revenue - LAG(revenue) OVER (ORDER BY month)) / LAG(revenue) OVER (ORDER BY month) * 100 as growth_rate
FROM monthly_sales;
ES无法直接实现这种复杂的窗口计算。
移动平均计算
-- ✅ 复杂统计函数
SELECT
date,
value,
AVG(value) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as moving_avg_7days
FROM daily_metrics;
选择建议 #
使用 Elasticsearch 的场景 #
✅ 全文搜索(电商、内容管理)
✅ 日志分析和监控
✅ 实时数据分析和可视化
✅ 地理位置搜索
✅ 推荐系统
✅ 大数据量的复杂查询
不使用 Elasticsearch 的场景 #
❌ 需要ACID事务的业务
❌ 复杂关联查询为主
❌ 小数据量简单应用
❌ 频繁更新的数据
❌ 严格一致性要求
❌ 复杂数学计算和统计
混合架构建议 #
关系型数据库 (MySQL/PostgreSQL)
├── 用户账户、订单、支付(事务性)
├── 权限管理、配置信息
└── 财务数据、报表统计
Elasticsearch
├── 商品搜索、内容检索
├── 日志分析、监控数据
├── 用户行为分析
└── 推荐算法数据
Redis/Memcached
├── 缓存、会话管理
├── 计数器、排行榜
└── 实时数据临时存储
选择合适的工具解决合适的问题,才能发挥最大价值!
XIUANGH: 我还有几个额外的问题: 第一个问题。其中的数据类型中的对象类型和嵌套类型有什么区别?请你详细分析。 第二个问题:对于中文,一般选择什么分词器?
- 对于非text,其搜搜的方式什么?对于text,搜索的方式是什么?
改索引设置 #
PUT /user_index/_settings
{
"number_of_replicas": 2
}
#
这个修改是什么意思? 5. 一个索引中有多少可以进行配置的setting的内容? 6. #### 使用脚本更新
POST /user_index/_update/1
{
"script": {
"source": "ctx._source.age += params.increment",
"params": {
"increment": 1
}
}
}
#
使用脚本更新是什么意思? 7. 这个是什么意思:#### 更新或插入(upsert)
POST /user_index/_update/2
{
"doc": {
"name": "王五",
"age": 28
},
"upsert": {
"name": "王五",
"age": 28,
"email": "wangwu@example.com",
"created_at": "2025-06-14T12:00:00"
}
}
#
5. 批量操作 #
POST /user_index/_bulk
{"index":{"_id":"1"}}
{"name":"张三","age":25,"email":"zhangsan@example.com"}
{"index":{"_id":"2"}}
{"name":"李四","age":30,"email":"lisi@example.com"}
{"update":{"_id":"1"}}
{"doc":{"age":26}}
{"delete":{"_id":"2"}}
这个批量操作是什么意思?介绍一下这个的语法 #
9 “_source”: [“name”, “age”] // 只返回指定字段 这个source和field有什么区别? 10 match和match_all的区别就是针对的对象不一样?然后一个是全部匹配,一个是搜索 分词进行匹配吗?这几个query的方式分别是什么意思?multi_match、match_phrase 11 { “query”: { “match”: { “name”: “张三” } } } 不指定返回的字段,就是直接返回一整条document吗 12 { “query”: { “multi_match”: { “query”: “张三”, “fields”: [“name”, “email”] } } } 这个是什么意思 { “query”: { “match_phrase”: { “name”: “张 三” } } }这个短语匹配又是什么意思 13 terms term和query有什么区别? 14 filter关键字是什么作用? 请你详细回答我的问题,越详细越好
Elasticsearch 几个详细问题解答 #
1. 对象类型 vs 嵌套类型的区别 #
Object 类型(对象类型) #
内部存储机制 #
# 原始文档
{
"user": {
"name": "张三",
"age": 25
}
}
# ES内部实际存储(扁平化)
{
"user.name": "张三",
"user.age": 25
}
问题场景 #
# 文档示例
PUT /blog/_doc/1
{
"comments": [
{
"author": "张三",
"content": "很好的文章"
},
{
"author": "李四",
"content": "写得不错"
}
]
}
# ES内部存储为
{
"comments.author": ["张三", "李四"],
"comments.content": ["很好的文章", "写得不错"]
}
# 错误查询结果
GET /blog/_search
{
"query": {
"bool": {
"must": [
{"match": {"comments.author": "张三"}},
{"match": {"comments.content": "写得不错"}}
]
}
}
}
# ❌ 会匹配到文档,但实际上张三没有说"写得不错"
这种存储方式丢失了原始对象中字段之间的关联关系,导致查询时无法保持对象内部的字段对应关系。
在原始查询中:
comments.author包含 “张三”comments.content包含 “写得不错”
虽然这两个值来自不同的评论对象,但由于存储时被扁平化为独立数组,Elasticsearch 简单检查这两个条件是否都满足,而不关心它们是否来自同一个对象。
Nested 类型(嵌套类型) #
解决方案 #
# 正确的映射定义
PUT /blog
{
"mappings": {
"properties": {
"comments": {
"type": "nested",
"properties": {
"author": {"type": "keyword"},
"content": {"type": "text"}
}
}
}
}
}
# 正确的查询方式
GET /blog/_search
{
"query": {
"nested": {
"path": "comments",
"query": {
"bool": {
"must": [
{"match": {"comments.author": "张三"}},
{"match": {"comments.content": "写得不错"}}
]
}
}
}
}
}
# ✅ 不会匹配到文档,因为张三确实没有说"写得不错"
核心区别总结 #
| 特性 | Object 类型 | Nested 类型 |
|---|---|---|
| 存储方式 | 扁平化存储 | 独立文档存储 |
| 查询方式 | 普通查询 | nested查询 |
| 关联性 | 丢失数组元素间关联 | 保持数组元素间关联 |
| 性能 | 更快 | 稍慢(需要join) |
| 使用场景 | 简单结构化数据 | 复杂关联数组 |
2. 中文分词器选择 #
IK 分词器(推荐) #
安装 #
# Elasticsearch 插件安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.11.0/elasticsearch-analysis-ik-8.11.0.zip
两种模式对比 #
# ik_max_word(最细粒度)
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "中华人民共和国成立于1949年"
}
# 结果:["中华人民共和国", "中华人民", "中华", "华人", "人民", "共和国", "共和", "成立", "于", "1949", "年"]
# ik_smart(智能分词)
POST /_analyze
{
"analyzer": "ik_smart",
"text": "中华人民共和国成立于1949年"
}
# 结果:["中华人民共和国", "成立", "于", "1949年"]
配置使用 #
PUT /chinese_index
{
"settings": {
"analysis": {
"analyzer": {
"ik_max_word": {"type": "ik_max_word"},
"ik_smart": {"type": "ik_smart"}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word", // 索引时用最细粒度
"search_analyzer": "ik_smart" // 搜索时用智能分词
},
"content": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
自定义词典 #
# 在 config/analysis-ik/ 目录下创建
# my_dict.dic 文件,添加自定义词汇
elasticsearch
大数据
人工智能
3. text vs 非text字段的搜索方式 #
text 字段搜索(分词搜索) #
索引过程 #
# 原始文本:"Elasticsearch是强大的搜索引擎"
# 分词结果:["elasticsearch", "是", "强大", "的", "搜索", "引擎"]
# 倒排索引:
{
"elasticsearch": [doc1],
"强大": [doc1],
"搜索": [doc1],
"引擎": [doc1]
}
搜索方式 #
# match查询(会分词)
GET /my_index/_search
{
"query": {
"match": {
"content": "搜索引擎" // 会被分词为["搜索", "引擎"]
}
}
}
# 匹配包含"搜索"或"引擎"的文档
# match_phrase查询(短语匹配)
GET /my_index/_search
{
"query": {
"match_phrase": {
"content": "搜索引擎" // 必须连续出现
}
}
}
# 只匹配"搜索引擎"连续出现的文档
非text字段搜索(精确搜索) #
keyword 字段 #
# 原始值:"Elasticsearch"
# 索引存储:完整的"Elasticsearch"(不分词)
# term查询(精确匹配)
GET /my_index/_search
{
"query": {
"term": {
"status.keyword": "published" // 必须完全匹配
}
}
}
# terms查询(多值精确匹配)
GET /my_index/_search
{
"query": {
"terms": {
"category.keyword": ["tech", "science", "news"]
}
}
}
数字字段 #
# range查询
GET /my_index/_search
{
"query": {
"range": {
"age": {
"gte": 18,
"lte": 65
}
}
}
}
# term查询
GET /my_index/_search
{
"query": {
"term": {
"price": 99.99
}
}
}
日期字段 #
GET /my_index/_search
{
"query": {
"range": {
"created_at": {
"gte": "2025-01-01",
"lte": "2025-12-31",
"format": "yyyy-MM-dd"
}
}
}
}
4. 修改索引设置的含义 #
PUT /user_index/_settings
{
"number_of_replicas": 2
}
副本数量调整 #
原理说明 #
- 主分片(Primary Shard):存储原始数据,数量创建后不可修改
- 副本分片(Replica Shard):主分片的完整拷贝,可动态调整
具体含义 #
# 假设原来的设置
{
"number_of_shards": 3, // 3个主分片(不可改)
"number_of_replicas": 1 // 每个主分片1个副本
}
# 总分片数 = 3主分片 + 3副本分片 = 6个分片
# 修改后
{
"number_of_replicas": 2 // 每个主分片2个副本
}
# 总分片数 = 3主分片 + 6副本分片 = 9个分片
实际影响 #
# 集群状态变化
原来:Node1[P0,R1] Node2[P1,R2] Node3[P2,R0]
修改后:Node1[P0,R1,R2] Node2[P1,R0,R2] Node3[P2,R0,R1]
# P=Primary主分片,R=Replica副本分片
副本数量选择建议 #
# 开发环境
"number_of_replicas": 0 // 无副本,节省资源
# 测试环境
"number_of_replicas": 1 // 1个副本,基本容错
# 生产环境
"number_of_replicas": 2 // 2个副本,高可用
5. 索引可配置的Settings内容 #
核心设置分类 #
分片设置 #
{
"number_of_shards": 3, // 主分片数(创建后不可改)
"number_of_replicas": 1, // 副本数(可动态修改)
"shard.check_on_startup": true // 启动时检查分片
}
刷新设置 #
{
"refresh_interval": "1s", // 刷新间隔
"max_refresh_listeners": 1000 // 最大刷新监听器
}
索引缓存设置 #
{
"index.queries.cache.enabled": true, // 查询缓存
"index.requests.cache.enable": true, // 请求缓存
"index.fielddata.cache.size": "40%" // 字段数据缓存大小
}
分析器设置 #
{
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "asciifolding"]
}
},
"tokenizer": {
"my_tokenizer": {
"type": "pattern",
"pattern": "\\W+"
}
}
}
}
合并策略设置 #
{
"index.merge.policy.max_merge_at_once": 10, // 同时合并的段数
"index.merge.policy.segments_per_tier": 10, // 每层段数
"index.merge.scheduler.max_thread_count": 1 // 合并线程数
}
存储设置 #
{
"index.store.type": "fs", // 存储类型
"index.store.preload": ["nvd", "dvd"], // 预加载文件
"index.codec": "best_compression" // 压缩算法
}
路由设置 #
{
"index.routing.allocation.require.rack": "r1", // 分片分配要求
"index.routing.allocation.include.node": "node1", // 包含节点
"index.routing.allocation.exclude.node": "node2" // 排除节点
}
完整配置示例 #
PUT /comprehensive_index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "5s",
"index": {
"max_result_window": 50000,
"max_rescore_window": 10000,
"blocks": {
"read_only": false,
"read": false,
"write": false
}
},
"analysis": {
"analyzer": {
"chinese_analyzer": {
"type": "ik_max_word"
}
}
}
}
}
6. 脚本更新的含义 #
脚本更新原理 #
基本语法 #
POST /user_index/_update/1
{
"script": {
"source": "ctx._source.age += params.increment",
"params": {
"increment": 1
}
}
}
ctx上下文对象 #
# ctx._source:当前文档的源数据
# ctx._id:文档ID
# ctx._version:文档版本
# ctx._routing:路由值
# ctx.op:操作类型(index, delete, noop)
高级脚本示例 #
条件更新 #
POST /user_index/_update/1
{
"script": {
"source": """
if (ctx._source.age < 18) {
ctx._source.category = 'minor';
} else if (ctx._source.age >= 18 && ctx._source.age < 60) {
ctx._source.category = 'adult';
} else {
ctx._source.category = 'senior';
}
ctx._source.last_updated = params.now;
""",
"params": {
"now": "2025-06-14T10:30:00"
}
}
}
数组操作 #
POST /user_index/_update/1
{
"script": {
"source": """
if (ctx._source.tags == null) {
ctx._source.tags = [];
}
ctx._source.tags.add(params.new_tag);
ctx._source.tags = ctx._source.tags.stream().distinct().collect(Collectors.toList());
""",
"params": {
"new_tag": "elasticsearch"
}
}
}
条件删除 #
POST /user_index/_update/1
{
"script": {
"source": """
if (ctx._source.status == 'deleted') {
ctx.op = 'delete';
} else {
ctx._source.last_seen = params.now;
}
""",
"params": {
"now": "2025-06-14T10:30:00"
}
}
}
脚本语言支持 #
# Painless(默认,推荐)
{
"script": {
"lang": "painless",
"source": "ctx._source.count += 1"
}
}
# 存储脚本
PUT /_scripts/increment_age
{
"script": {
"lang": "painless",
"source": "ctx._source.age += params.increment"
}
}
# 使用存储脚本
POST /user_index/_update/1
{
"script": {
"id": "increment_age",
"params": {
"increment": 1
}
}
}
7. Upsert操作详解 #
Upsert的工作机制 #
POST /user_index/_update/2
{
"doc": {
"name": "王五",
"age": 28
},
"upsert": {
"name": "王五",
"age": 28,
"email": "wangwu@example.com",
"created_at": "2025-06-14T12:00:00"
}
}
执行逻辑 #
# 1. 检查文档ID=2是否存在
if (document_exists(id=2)) {
# 文档存在:执行更新操作
# 只更新doc中指定的字段
merge_document(existing_doc, {"name": "王五", "age": 28});
} else {
# 文档不存在:执行插入操作
# 使用upsert中的完整数据创建新文档
create_document(id=2, {
"name": "王五",
"age": 28,
"email": "wangwu@example.com",
"created_at": "2025-06-14T12:00:00"
});
}
实际应用场景 #
用户资料更新 #
# 用户登录时更新最后登录时间,如果用户不存在则创建
POST /users/_update/user123
{
"doc": {
"last_login": "2025-06-14T10:30:00",
"login_count": 1
},
"upsert": {
"user_id": "user123",
"username": "newuser",
"created_at": "2025-06-14T10:30:00",
"last_login": "2025-06-14T10:30:00",
"login_count": 1,
"status": "active"
}
}
计数器场景 #
# 页面访问计数
POST /page_views/_update/home_page
{
"script": {
"source": "ctx._source.count += 1"
},
"upsert": {
"page": "home",
"count": 1,
"first_visit": "2025-06-14T10:30:00"
}
}
Upsert vs 普通更新对比 #
| 操作 | 文档存在 | 文档不存在 |
|---|---|---|
| 普通更新 | 成功更新 | 返回404错误 |
| Upsert | 更新doc字段 | 创建upsert文档 |
8. 批量操作详解 #
批量操作语法结构 #
POST /user_index/_bulk
{"index":{"_id":"1"}} # 操作行
{"name":"张三","age":25,"email":"zhangsan@example.com"} # 数据行
{"index":{"_id":"2"}} # 操作行
{"name":"李四","age":30,"email":"lisi@example.com"} # 数据行
{"update":{"_id":"1"}} # 操作行
{"doc":{"age":26}} # 数据行
{"delete":{"_id":"2"}} # 操作行(无数据行)
操作类型详解 #
index 操作 #
{"index":{"_id":"1"}}
{"name":"张三","age":25}
# 等价于
PUT /user_index/_doc/1
{
"name":"张三",
"age":25
}
create 操作 #
{"create":{"_id":"1"}}
{"name":"张三","age":25}
# 等价于(仅当文档不存在时)
PUT /user_index/_create/1
{
"name":"张三",
"age":25
}
update 操作 #
{"update":{"_id":"1"}}
{"doc":{"age":26}}
# 等价于
POST /user_index/_update/1
{
"doc":{"age":26}
}
delete 操作 #
{"delete":{"_id":"2"}}
# 等价于
DELETE /user_index/_doc/2
完整批量操作示例 #
POST /_bulk
{"index":{"_index":"products","_id":"1"}}
{"name":"iPhone 14","price":7999,"category":"手机"}
{"index":{"_index":"products","_id":"2"}}
{"name":"MacBook Pro","price":12999,"category":"电脑"}
{"update":{"_index":"products","_id":"1"}}
{"doc":{"price":7599},"doc_as_upsert":true}
{"delete":{"_index":"products","_id":"3"}}
{"create":{"_index":"users","_id":"100"}}
{"username":"admin","email":"admin@example.com","role":"administrator"}
批量操作的响应格式 #
{
"took": 15,
"errors": false,
"items": [
{
"index": {
"_index": "products",
"_id": "1",
"_version": 1,
"result": "created",
"status": 201
}
},
{
"update": {
"_index": "products",
"_id": "1",
"_version": 2,
"result": "updated",
"status": 200
}
},
{
"delete": {
"_index": "products",
"_id": "3",
"_version": 3,
"result": "deleted",
"status": 200
}
}
]
}
性能优化建议 #
# 批量大小建议
- 单次批量:1000-5000个文档
- 数据大小:5MB-15MB
- 过大会占用内存,过小效率低
# 并发控制
POST /_bulk?refresh=false&timeout=60s
9. _source vs fields的区别 #
_source(源字段过滤) #
基本用法 #
GET /user_index/_search
{
"query": {"match_all": {}},
"_source": ["name", "age"] // 只返回指定字段
}
# 返回结果
{
"hits": [
{
"_source": {
"name": "张三",
"age": 25
}
}
]
}
高级过滤 #
GET /user_index/_search
{
"_source": {
"includes": ["name", "profile.*"], // 包含字段
"excludes": ["*.password", "internal.*"] // 排除字段
}
}
fields(字段值提取) #
基本用法 #
GET /user_index/_search
{
"query": {"match_all": {}},
"fields": ["name", "age", "created_at"]
}
# 返回结果
{
"hits": [
{
"_source": {...}, // 完整源文档
"fields": {
"name": ["张三"],
"age": [25],
"created_at": ["2025-06-14T10:30:00.000Z"]
}
}
]
}
字段格式化 #
GET /user_index/_search
{
"fields": [
"name",
{
"field": "created_at",
"format": "yyyy-MM-dd" // 日期格式化
},
{
"field": "location",
"format": "wkt" // 地理位置格式化
}
]
}
核心区别对比 #
| 特性 | _source | fields |
|---|---|---|
| 返回内容 | 源文档片段 | 字段值数组 |
| 字段格式 | 原始JSON格式 | ES处理后格式 |
| 字段不存在 | 不返回该字段 | 返回空数组[] |
| 性能 | 更快(直接过滤) | 稍慢(需要处理) |
| 格式化 | 不支持 | 支持日期、地理等格式化 |
| 使用场景 | 简单字段过滤 | 需要格式化或计算字段 |
实际应用对比 #
# 查询文档
GET /user_index/_doc/1
# 原始文档
{
"_source": {
"name": "张三",
"birthday": "1998-05-20T00:00:00.000Z",
"location": {"lat": 39.9042, "lon": 116.4074},
"tags": ["student", "tech"]
}
}
# 使用_source
GET /user_index/_search
{
"_source": ["name", "birthday"]
}
# 结果
{
"_source": {
"name": "张三",
"birthday": "1998-05-20T00:00:00.000Z"
}
}
# 使用fields
GET /user_index/_search
{
"fields": [
"name",
{"field": "birthday", "format": "yyyy-MM-dd"},
{"field": "location", "format": "wkt"}
]
}
# 结果
{
"fields": {
"name": ["张三"],
"birthday": ["1998-05-20"],
"location": ["POINT (116.4074 39.9042)"]
}
}
10. 查询类型详解 #
match_all vs match #
match_all(匹配所有文档) #
GET /user_index/_search
{
"query": {
"match_all": {} // 返回索引中的所有文档
}
}
# 等价于SQL
SELECT * FROM user_table;
match(分词匹配查询) #
GET /user_index/_search
{
"query": {
"match": {
"name": "张三" // 会对"张三"进行分词
}
}
}
# 分词过程
"张三" → ["张", "三"]
# 匹配包含"张"或"三"的文档
multi_match(多字段匹配) #
GET /user_index/_search
{
"query": {
"multi_match": {
"query": "张三",
"fields": ["name", "email"] // 在多个字段中搜索
}
}
}
# 等价于
{
"query": {
"bool": {
"should": [
{"match": {"name": "张三"}},
{"match": {"email": "张三"}}
]
}
}
}
multi_match的类型 #
# best_fields(默认)- 最佳字段匹配
{
"multi_match": {
"query": "elasticsearch tutorial",
"fields": ["title^2", "content"], // title权重x2
"type": "best_fields"
}
}
# most_fields - 最多字段匹配
{
"multi_match": {
"query": "elasticsearch",
"fields": ["title", "description", "tags"],
"type": "most_fields"
}
}
# cross_fields - 跨字段匹配
{
"multi_match": {
"query": "张 三",
"fields": ["first_name", "last_name"],
"type": "cross_fields"
}
}
match_phrase(短语匹配) #
GET /user_index/_search
{
"query": {
"match_phrase": {
"content": "elasticsearch tutorial"
}
}
}
短语匹配原理 #
# 文档内容:"Learning elasticsearch tutorial is fun"
# 查询:"elasticsearch tutorial"
# match查询
"elasticsearch tutorial" → ["elasticsearch", "tutorial"]
# 匹配:包含这两个词的文档(顺序无关)
# match_phrase查询
# 匹配:必须连续出现"elasticsearch tutorial"的文档
# 允许词项间隔
{
"match_phrase": {
"content": {
"query": "elasticsearch tutorial",
"slop": 2 // 允许中间插入2个词
}
}
}
# 可以匹配:"elasticsearch advanced tutorial"
查询类型总结 #
| 查询类型 | 用途 | 分词 | 示例场景 |
|---|---|---|---|
| match_all | 获取所有文档 | 无 | 分页浏览、统计总数 |
| match | 单字段分词搜索 | 是 | 商品名称搜索 |
| multi_match | 多字段分词搜索 | 是 | 全站搜索 |
| match_phrase | 短语精确匹配 | 是 | 引用、习语搜索 |
| term | 精确值匹配 | 否 | 状态、类别过滤 |
| terms | 多值精确匹配 | 否 | 批量ID查询 |
11. 默认返回完整文档 #
GET /user_index/_search
{
"query": {
"match": {
"name": "张三"
}
}
}
默认返回结构 #
{
"took": 5,
"timed_out": false,
"hits": {
"total": {"value": 1},
"hits": [
{
"_index": "user_index",
"_id": "1",
"_score": 1.0,
"_source": { // ✅ 完整的文档内容
"name": "张三",
"age": 25,
"email": "zhangsan@example.com",
"phone": "13812345678",
"address": "北京市朝阳区",
"created_at": "2025-06-14T10:30:00"
}
}
]
}
}
控制返回字段 #
只返回指定字段 #
GET /user_index/_search
{
"query": {"match": {"name": "张三"}},
"_source": ["name", "age"] // 只返回name和age
}
# 结果
{
"_source": {
"name": "张三",
"age": 25
}
}
不返回_source #
GET /user_index/_search
{
"query": {"match": {"name": "张三"}},
"_source": false // 不返回_source字段
}
# 结果(只有元数据,无文档内容)
{
"_index": "user_index",
"_id": "1",
"_score": 1.0
}
12. multi_match和match_phrase详解 #
multi_match深度解析 #
{
"query": {
"multi_match": {
"query": "张三",
"fields": ["name", "email"]
}
}
}
工作原理 #
# 等价转换
{
"query": {
"dis_max": { // 取最高分
"queries": [
{"match": {"name": "张三"}},
{"match": {"email": "张三"}}
]
}
}
}
字段权重设置 #
{
"multi_match": {
"query": "elasticsearch",
"fields": [
"title^3", // title字段权重3倍
"content^2", // content字段权重2倍
"tags" // tags字段权重1倍(默认)
]
}
}
实际应用场景 #
# 电商商品搜索
GET /products/_search
{
"query": {
"multi_match": {
"query": "苹果手机",
"fields": [
"title^3", // 标题最重要
"brand^2", // 品牌次重要
"description", // 描述
"specifications" // 规格参数
],
"type": "best_fields",
"fuzziness": "AUTO" // 自动模糊匹配
}
}
}
match_phrase深度解析 #
{
"query": {
"match_phrase": {
"content": "张 三"
}
}
}
短语匹配原理 #
# 假设文档内容
doc1: "张三是个好学生"
doc2: "张老师和三年级学生"
doc3: "三年级的张同学"
# 查询:"张 三"
# match查询:匹配doc1, doc2, doc3(包含"张"和"三")
# match_phrase查询:只匹配doc1("张三"连续出现)
slop参数(词项间隔) #
{
"match_phrase": {
"content": {
"query": "elasticsearch tutorial",
"slop": 1
}
}
}
# 可以匹配
"elasticsearch tutorial" // slop=0
"elasticsearch advanced tutorial" // slop=1(插入1个词)
"tutorial elasticsearch" // slop=2(需要移动位置)
实际应用 #
# 搜索名言警句
GET /quotes/_search
{
"query": {
"match_phrase": {
"content": {
"query": "人生苦短",
"slop": 0 // 必须完全匹配
}
}
}
}
# 搜索地址
GET /addresses/_search
{
"query": {
"match_phrase": {
"address": {
"query": "北京市 朝阳区",
"slop": 2 // 允许"北京市海淀区朝阳路"
}
}
}
}
13. term/terms vs query的区别 #
概念层次区别 #
Query Context(查询上下文) #
# 影响相关性评分
GET /products/_search
{
"query": { // 查询上下文
"match": {
"description": "手机"
}
}
}
# 返回结果包含_score评分
term/terms是具体查询类型 #
# term属于Query DSL中的一种查询方式
{
"query": {
"term": { // 精确匹配查询
"status": "published"
}
}
}
term查询详解 #
精确匹配原理 #
# 索引中的实际存储
"status": "published" // keyword字段,不分词
# term查询
{
"query": {
"term": {
"status": "published" // 必须完全匹配
}
}
}
# ✅ 匹配成功
{
"query": {
"term": {
"status": "Published" // 大小写不匹配
}
}
}
# ❌ 匹配失败
常见使用场景 #
# 状态过滤
{"term": {"status": "active"}}
# 类别过滤
{"term": {"category.keyword": "electronics"}}
# 数字精确匹配
{"term": {"price": 99.99}}
# 布尔值匹配
{"term": {"is_published": true}}
# 日期精确匹配
{"term": {"created_date": "2025-06-14"}}
terms查询详解 #
多值匹配 #
{
"query": {
"terms": {
"status": ["published", "draft", "pending"]
}
}
}
# 等价于SQL
SELECT * FROM articles
WHERE status IN ('published', 'draft', 'pending');
实际应用 #
# 批量用户查询
{
"terms": {
"user_id": [123, 456, 789, 1011]
}
}
# 多个标签匹配
{
"terms": {
"tags.keyword": ["java", "elasticsearch", "spring"]
}
}
# 多个分类
{
"terms": {
"category": ["手机", "电脑", "平板"]
}
}
text字段的term查询陷阱 #
# 错误示例
PUT /test_index/_doc/1
{
"message": "Hello World" // text字段,会被分词为["hello", "world"]
}
# 错误查询
{
"query": {
"term": {
"message": "Hello World" // ❌ 查不到,因为索引中没有"Hello World"这个完整term
}
}
}
# 正确查询方式1:使用match
{
"query": {
"match": {
"message": "Hello World" // ✅ 会分词匹配
}
}
}
# 正确查询方式2:使用keyword子字段
{
"query": {
"term": {
"message.keyword": "Hello World" // ✅ keyword字段不分词
}
}
}
查询类型对比总结 #
| 查询类型 | 分词 | 评分 | 用途 | 性能 |
|---|---|---|---|---|
| match | 是 | 是 | 全文搜索 | 中等 |
| term | 否 | 是 | 精确匹配 | 快 |
| terms | 否 | 是 | 多值精确匹配 | 快 |
| bool-filter | 否 | 否 | 条件过滤 | 最快 |
14. filter关键字的作用 #
Filter Context(过滤上下文) #
基本概念 #
{
"query": {
"bool": {
"must": [
{"match": {"title": "elasticsearch"}} // 查询上下文,影响评分
],
"filter": [ // 过滤上下文,不影响评分
{"term": {"status": "published"}},
{"range": {"price": {"gte": 100}}}
]
}
}
}
filter的核心特点 #
1. 不影响相关性评分 #
# 有评分的查询
{
"query": {
"bool": {
"must": [
{"match": {"content": "elasticsearch"}} // _score = 2.5
]
}
}
}
# 加入filter后
{
"query": {
"bool": {
"must": [
{"match": {"content": "elasticsearch"}} // _score 仍然是 2.5
],
"filter": [
{"term": {"status": "published"}} // 不影响评分
]
}
}
}
2. 可以被缓存 #
# filter查询会被ES自动缓存
{
"query": {
"bool": {
"filter": [
{"term": {"category": "tech"}}, // 会被缓存
{"range": {"date": {"gte": "2025-01-01"}}} // 会被缓存
]
}
}
}
# 相同的filter条件再次查询时,直接使用缓存结果
3. 执行速度更快 #
# 性能对比
# must查询(需要计算评分)
{
"query": {
"bool": {
"must": [
{"term": {"status": "active"}}, // 需要计算TF/IDF评分
{"range": {"age": {"gte": 18}}} // 需要计算评分
]
}
}
}
# filter查询(二进制判断)
{
"query": {
"bool": {
"filter": [
{"term": {"status": "active"}}, // 只判断匹配/不匹配
{"range": {"age": {"gte": 18}}} // 只判断true/false
]
}
}
}
实际应用场景 #
电商商品筛选 #
GET /products/_search
{
"query": {
"bool": {
"must": [
{"multi_match": { // 搜索查询,需要评分排序
"query": "智能手机",
"fields": ["title^3", "description"]
}}
],
"filter": [ // 筛选条件,不需要评分
{"term": {"category": "手机"}},
{"range": {"price": {"gte": 1000, "lte": 5000}}},
{"term": {"brand": "华为"}},
{"term": {"in_stock": true}},
{"terms": {"color": ["黑色", "白色", "蓝色"]}}
]
}
},
"sort": [
{"_score": {"order": "desc"}}, // 按相关性排序
{"sales_count": {"order": "desc"}} // 按销量排序
]
}
日志分析 #
GET /logs/_search
{
"query": {
"bool": {
"must": [
{"match": {"message": "ERROR"}} // 错误内容搜索,需要相关性
],
"filter": [ // 时间和级别过滤,不需要评分
{"range": {"@timestamp": {"gte": "now-1h"}}},
{"term": {"service": "user-service"}},
{"term": {"environment": "production"}}
]
}
}
}
filter的高级用法 #
1. 纯filter查询 #
{
"query": {
"bool": {
"filter": [ // 只有filter,没有must
{"term": {"status": "active"}},
{"range": {"created_at": {"gte": "2025-01-01"}}}
]
}
}
}
# 所有文档的_score都是0.0,按插入顺序返回
2. 复合filter条件 #
{
"query": {
"bool": {
"filter": [
{
"bool": { // 嵌套bool查询
"should": [
{"term": {"category": "手机"}},
{"term": {"category": "平板"}}
],
"minimum_should_match": 1
}
},
{
"range": {"price": {"gte": 100}}
}
]
}
}
}
3. filter聚合 #
{
"query": {"match_all": {}},
"aggs": {
"active_users": {
"filter": {"term": {"status": "active"}}, // 先过滤再聚合
"aggs": {
"avg_age": {"avg": {"field": "age"}}
}
}
}
}
性能优化建议 #
查询优化策略 #
# ✅ 好的做法:先filter后query
{
"query": {
"bool": {
"filter": [ // 先用filter缩小范围
{"term": {"status": "published"}},
{"range": {"date": {"gte": "2025-01-01"}}}
],
"must": [ // 再在小范围内计算相关性
{"match": {"content": "elasticsearch"}}
]
}
}
}
# ❌ 不好的做法:全量计算后再过滤
{
"query": {
"bool": {
"must": [ // 先对全量数据计算相关性
{"match": {"content": "elasticsearch"}},
{"term": {"status": "published"}}, // 这些应该用filter
{"range": {"date": {"gte": "2025-01-01"}}}
]
}
}
}
filter vs must/should/must_not #
| 子句 | 影响评分 | 缓存 | 用途 | 性能 |
|---|---|---|---|---|
| must | 是 | 否 | 必须匹配且影响评分 | 慢 |
| should | 是 | 否 | 可选匹配,提升评分 | 慢 |
| must_not | 否 | 是 | 必须不匹配 | 快 |
| filter | 否 | 是 | 必须匹配但不评分 | 最快 |
选择原则:
- 需要相关性评分 → 使用 must/should
- 不需要相关性评分 → 使用 filter
- 排除条件 → 使用 must_not
希望这些详细的解答能帮助你更好地理解 Elasticsearch!每个概念都有其特定的使用场景,在实际应用中根据需求选择合适的方式。
XIUANGH: 对于_search这种下划线开头的,都是es中预定义的指令动作吗?
VS Code: 这些不是Visual Studio Code相关的问题。这些是Elasticsearch的REST API命令,其中以下划线开头的确实是Elasticsearch中的预定义API端点:
_cat- 用于获取集群、节点、索引等信息的紧凑格式输出API_doc- 文档操作API的端点_search- 搜索API的端点
在Elasticsearch中,以下划线开头的路径通常表示系统级别的API端点,这是Elasticsearch的命名约定。
如果你想在Visual Studio Code中更好地处理Elasticsearch相关的开发工作,可以搜索相关扩展:
使用搜索扩展市场命令查找Elasticsearch相关扩展。
搜索扩展市场
XIUANGH: 详细介绍一下Elasticsearch的REST API命令,划线开头的是Elasticsearch中的预定义API端点,给出全部预定义的端口
GitHub Copilot: # Elasticsearch REST API 详细介绍
API 基本结构 #
Elasticsearch 的 REST API 遵循以下基本结构:
HTTP_METHOD /<index>/<endpoint>/<id>
其中以下划线 _ 开头的端点都是 Elasticsearch 预定义的系统 API。
文档操作 API #
_doc - 文档操作
#
# 添加文档(自动生成ID)
POST /my_index/_doc
{
"title": "Sample Document",
"content": "This is a test document"
}
# 添加/更新文档(指定ID)
PUT /my_index/_doc/1
{
"title": "Document with ID",
"content": "This document has a specific ID"
}
# 获取文档
GET /my_index/_doc/1
# 删除文档
DELETE /my_index/_doc/1
_create - 仅创建文档
#
# 仅当文档不存在时创建
PUT /my_index/_create/1
{
"title": "New Document",
"content": "This will only be created if ID 1 doesn't exist"
}
_update - 更新文档
#
# 部分更新
POST /my_index/_update/1
{
"doc": {
"title": "Updated Title"
}
}
# 脚本更新
POST /my_index/_update/1
{
"script": {
"source": "ctx._source.counter += params.increment",
"params": {
"increment": 1
}
}
}
_bulk - 批量操作
#
POST /_bulk
{"index":{"_index":"my_index","_id":"1"}}
{"title":"Doc 1","content":"Content 1"}
{"index":{"_index":"my_index","_id":"2"}}
{"title":"Doc 2","content":"Content 2"}
{"update":{"_index":"my_index","_id":"1"}}
{"doc":{"title":"Updated Doc 1"}}
{"delete":{"_index":"my_index","_id":"2"}}
搜索相关 API #
_search - 搜索文档
#
# 基本搜索
GET /my_index/_search
{
"query": {
"match": {
"content": "elasticsearch"
}
}
}
# 跨索引搜索
GET /index1,index2/_search
GET /_all/_search
GET /*/_search
_msearch - 多重搜索
#
POST /_msearch
{"index":"my_index"}
{"query":{"match":{"title":"elasticsearch"}}}
{"index":"another_index"}
{"query":{"match":{"content":"tutorial"}}}
_count - 计数查询
#
# 获取匹配文档数量
GET /my_index/_count
{
"query": {
"match": {
"status": "published"
}
}
}
_validate - 验证查询
#
# 验证查询语法是否正确
GET /my_index/_validate/query
{
"query": {
"match": {
"title": "elasticsearch"
}
}
}
# 详细验证信息
GET /my_index/_validate/query?explain=true
{
"query": {
"match": {
"title": "elasticsearch"
}
}
}
_explain - 解释评分
#
# 解释文档为什么匹配或不匹配
GET /my_index/_explain/1
{
"query": {
"match": {
"title": "elasticsearch"
}
}
}
索引管理 API #
_mapping - 映射管理
#
# 查看映射
GET /my_index/_mapping
# 创建/更新映射
PUT /my_index/_mapping
{
"properties": {
"title": {
"type": "text",
"analyzer": "standard"
},
"created_at": {
"type": "date"
}
}
}
_settings - 设置管理
#
# 查看设置
GET /my_index/_settings
# 更新设置
PUT /my_index/_settings
{
"number_of_replicas": 2,
"refresh_interval": "5s"
}
_aliases - 别名管理
#
# 查看别名
GET /_aliases
GET /my_index/_aliases
# 创建别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "my_index",
"alias": "search_alias"
}
}
]
}
# 删除别名
POST /_aliases
{
"actions": [
{
"remove": {
"index": "my_index",
"alias": "search_alias"
}
}
]
}
_template - 索引模板
#
# 创建索引模板
PUT /_template/my_template
{
"index_patterns": ["log-*"],
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"timestamp": {
"type": "date"
},
"message": {
"type": "text"
}
}
}
}
# 查看模板
GET /_template/my_template
GET /_template
集群管理 API #
_cluster - 集群操作
#
# 集群健康状态
GET /_cluster/health
# 集群状态
GET /_cluster/state
# 集群统计信息
GET /_cluster/stats
# 集群设置
GET /_cluster/settings
# 更新集群设置
PUT /_cluster/settings
{
"transient": {
"cluster.routing.allocation.disk.watermark.low": "85%"
}
}
_nodes - 节点信息
#
# 节点信息
GET /_nodes
# 节点统计
GET /_nodes/stats
# 特定节点信息
GET /_nodes/node1,node2
# 节点热线程
GET /_nodes/hot_threads
索引维护 API #
_refresh - 刷新索引
#
# 刷新单个索引
POST /my_index/_refresh
# 刷新所有索引
POST /_refresh
_flush - 强制刷新
#
# 强制刷新到磁盘
POST /my_index/_flush
# 强制刷新并等待完成
POST /my_index/_flush?wait_if_ongoing=true
_forcemerge - 强制合并
#
# 强制合并分段
POST /my_index/_forcemerge
# 合并到指定分段数
POST /my_index/_forcemerge?max_num_segments=1
_reindex - 重新索引
#
POST /_reindex
{
"source": {
"index": "old_index"
},
"dest": {
"index": "new_index"
}
}
_shrink - 收缩索引
#
# 收缩索引分片数
POST /my_index/_shrink/my_index_shrunk
{
"settings": {
"index.number_of_shards": 1
}
}
_split - 拆分索引
#
# 拆分索引增加分片数
POST /my_index/_split/my_index_split
{
"settings": {
"index.number_of_shards": 10
}
}
分析和监控 API #
_analyze - 文本分析
#
# 分析文本
POST /_analyze
{
"analyzer": "standard",
"text": "The quick brown fox jumps over the lazy dog"
}
# 使用特定索引的分析器
POST /my_index/_analyze
{
"field": "content",
"text": "Elasticsearch is awesome"
}
_stats - 统计信息
#
# 索引统计
GET /my_index/_stats
# 所有索引统计
GET /_stats
# 特定统计信息
GET /my_index/_stats/indexing,search
_segments - 分段信息
#
# 查看分段信息
GET /my_index/_segments
_recovery - 恢复信息
#
# 查看索引恢复状态
GET /my_index/_recovery
# 所有索引恢复状态
GET /_recovery
任务管理 API #
_tasks - 任务管理
#
# 查看所有任务
GET /_tasks
# 查看特定任务
GET /_tasks/task_id
# 取消任务
POST /_tasks/task_id/_cancel
脚本管理 API #
_scripts - 脚本管理
#
# 存储脚本
PUT /_scripts/my_script
{
"script": {
"lang": "painless",
"source": "ctx._source.counter += params.increment"
}
}
# 获取脚本
GET /_scripts/my_script
# 删除脚本
DELETE /_scripts/my_script
快照和恢复 API #
_snapshot - 快照管理
#
# 创建快照仓库
PUT /_snapshot/my_backup
{
"type": "fs",
"settings": {
"location": "/path/to/backup"
}
}
# 创建快照
PUT /_snapshot/my_backup/snapshot_1
{
"indices": "my_index",
"ignore_unavailable": true
}
# 恢复快照
POST /_snapshot/my_backup/snapshot_1/_restore
{
"indices": "my_index"
}
监控和调试 API #
_cat - 紧凑格式信息
#
# 查看所有cat API
GET /_cat
# 索引信息
GET /_cat/indices?v
# 节点信息
GET /_cat/nodes?v
# 分片信息
GET /_cat/shards?v
# 健康状态
GET /_cat/health?v
# 主节点信息
GET /_cat/master?v
# 分配信息
GET /_cat/allocation?v
# 计数信息
GET /_cat/count?v
# 插件信息
GET /_cat/plugins?v
# 线程池信息
GET /_cat/thread_pool?v
安全相关 API(需要X-Pack) #
_security - 安全管理
#
# 用户管理
PUT /_security/user/my_user
{
"password": "my_password",
"roles": ["my_role"]
}
# 角色管理
PUT /_security/role/my_role
{
"cluster": ["monitor"],
"indices": [
{
"names": ["my_index"],
"privileges": ["read", "write"]
}
]
}
机器学习 API(需要X-Pack) #
_ml - 机器学习
#
# 创建数据源
PUT /_ml/datafeeds/my_datafeed
{
"job_id": "my_job",
"indices": ["my_index"]
}
# 创建作业
PUT /_ml/anomaly_detectors/my_job
{
"analysis_config": {
"bucket_span": "10m",
"detectors": [
{
"function": "mean",
"field_name": "response_time"
}
]
}
}
完整的预定义 API 端点列表 #
以下是所有以下划线开头的预定义 API 端点:
核心 API #
_doc- 文档操作_create- 创建文档_update- 更新文档_bulk- 批量操作_delete_by_query- 按查询删除_update_by_query- 按查询更新
搜索 API #
_search- 搜索_msearch- 多重搜索_count- 计数_validate- 验证查询_explain- 解释评分_field_caps- 字段能力_rank_eval- 排名评估
索引管理 API #
_mapping- 映射管理_settings- 设置管理_aliases- 别名管理_template- 索引模板_component_template- 组件模板_index_template- 索引模板(新版)
集群管理 API #
_cluster- 集群操作_nodes- 节点信息_remote- 远程集群
维护 API #
_refresh- 刷新_flush- 强制刷新_forcemerge- 强制合并_reindex- 重新索引_shrink- 收缩索引_split- 拆分索引_rollover- 滚动索引_close- 关闭索引_open- 打开索引
分析和监控 API #
_analyze- 文本分析_stats- 统计信息_segments- 分段信息_recovery- 恢复信息_shard_stores- 分片存储
任务和脚本 API #
_tasks- 任务管理_scripts- 脚本管理
快照和恢复 API #
_snapshot- 快照管理
监控 API #
_cat- 紧凑格式信息_monitor- 监控信息
安全 API(X-Pack) #
_security- 安全管理_ssl- SSL 证书
机器学习 API(X-Pack) #
_ml- 机器学习_transform- 数据转换
其他专用 API #
_license- 许可证管理_xpack- X-Pack 信息_ilm- 索引生命周期管理_slm- 快照生命周期管理_ccr- 跨集群复制_enrich- 数据丰富_ingest- 摄取管道
这些 API 端点构成了 Elasticsearch 完整的功能体系,涵盖了从基本的 CRUD 操作到高级的集群管理和机器学习功能。
XIUANGH: GET /user_index/_search { “query”: { “bool”: { “must”: [ // 必须匹配(AND) { “match”: { “name”: “张三” } } ], “must_not”: [ // 必须不匹配(NOT) { “term”: { “age”: 25 } } ], “should”: [ // 应该匹配(OR,可选) { “term”: { “age”: 30 } } ], “filter”: [ // 过滤(不影响评分) { “range”: { “age”: { “gte”: 18 } } } ] } } }
详细介绍一下这个查询的全部内容,越详细越好
GitHub Copilot: # Elasticsearch Bool 查询详细解析
这是一个 Elasticsearch 的 bool 查询,它是最重要和最常用的复合查询类型之一。让我详细解析每个部分:
整体结构解析 #
Bool 查询的四个子句 #
Bool 查询包含四个可选的子句,每个子句都有不同的逻辑和性能特征:
{
"query": {
"bool": {
"must": [], // 必须匹配,影响评分
"must_not": [], // 必须不匹配,不影响评分
"should": [], // 应该匹配,影响评分
"filter": [] // 必须匹配,不影响评分,可缓存
}
}
}
1. must 子句详解(AND 逻辑)
#
基本特征 #
- 逻辑关系:AND(必须满足)
- 评分影响:是(影响
_score) - 缓存:否
- 执行顺序:参与相关性计算
代码分析 #
"must": [
{
"match": {
"name": "张三"
}
}
]
详细执行过程 #
- 分词处理:
"张三"会被分词器处理(如使用 IK 分词器可能分为["张", "三"]或保持["张三"]) - 匹配逻辑:查找
name字段中包含分词结果的文档 - 评分计算:使用 TF-IDF 算法计算相关性得分
- 结果要求:文档必须匹配此条件才能被返回
实际匹配示例 #
# 假设有以下文档
文档1: {"name": "张三", "age": 25} # ✅ 匹配
文档2: {"name": "张三丰", "age": 30} # ✅ 可能匹配(取决于分词)
文档3: {"name": "李四", "age": 25} # ❌ 不匹配
2. must_not 子句详解(NOT 逻辑)
#
基本特征 #
- 逻辑关系:NOT(必须不满足)
- 评分影响:否(不影响
_score) - 缓存:是(可以被缓存)
- 执行方式:过滤器上下文
代码分析 #
"must_not": [
{
"term": {
"age": 25
}
}
]
详细执行过程 #
- 精确匹配:
term查询对age字段进行精确匹配 - 排除逻辑:排除
age等于 25 的所有文档 - 不影响评分:被排除的文档不会影响其他文档的相关性得分
- 缓存机制:此条件会被 Elasticsearch 缓存,提高后续查询性能
实际排除示例 #
# 假设有以下文档
文档1: {"name": "张三", "age": 25} # ❌ 被排除(age=25)
文档2: {"name": "张三", "age": 30} # ✅ 不被排除
文档3: {"name": "王五", "age": 25} # ❌ 被排除(age=25)
3. should 子句详解(OR 逻辑)
#
基本特征 #
- 逻辑关系:OR(可选匹配)
- 评分影响:是(提升
_score) - 必须性:可选(但有条件)
- 作用:提升匹配文档的相关性得分
代码分析 #
"should": [
{
"term": {
"age": 30
}
}
]
详细执行逻辑 #
- 可选匹配:如果文档的
age等于 30,会提升其相关性得分 - 不是必须:文档不匹配此条件仍可能被返回(如果满足
must条件) - 得分提升:匹配的文档会获得额外的相关性得分
- minimum_should_match:可以设置最少匹配数量
实际评分示例 #
# 假设文档都满足 must 条件
文档1: {"name": "张三", "age": 30} # 基础分数 + should 分数
文档2: {"name": "张三", "age": 28} # 仅基础分数
文档3: {"name": "张三丰", "age": 30} # 基础分数 + should 分数
Should 的特殊行为 #
# 当没有 must 子句时,should 变为必须匹配
{
"query": {
"bool": {
"should": [
{"term": {"status": "published"}},
{"term": {"status": "draft"}}
],
"minimum_should_match": 1 // 至少匹配1个条件
}
}
}
4. filter 子句详解(过滤器)
#
基本特征 #
- 逻辑关系:AND(必须满足)
- 评分影响:否(不影响
_score) - 缓存:是(自动缓存)
- 性能:最快(二进制判断)
代码分析 #
"filter": [
{
"range": {
"age": {
"gte": 18
}
}
}
]
详细执行过程 #
- 范围判断:检查
age字段是否大于等于 18 - 二进制结果:只返回 true/false,不计算相关性
- 缓存机制:结果会被缓存,相同查询直接使用缓存
- 性能优化:通过位集合(bitset)进行快速过滤
实际过滤示例 #
# 所有文档的 age 字段检查
文档1: {"name": "张三", "age": 25} # ✅ 25 >= 18
文档2: {"name": "李四", "age": 16} # ❌ 16 < 18
文档3: {"name": "王五", "age": 30} # ✅ 30 >= 18
完整查询的执行流程 #
执行顺序和逻辑 #
1. Filter 阶段(最先执行,性能最优)
└── 筛选出 age >= 18 的文档
2. Must_not 阶段(排除不需要的文档)
└── 从剩余文档中排除 age = 25 的文档
3. Must 阶段(核心匹配 + 评分)
└── 在剩余文档中查找 name 匹配 "张三" 的文档
└── 计算基础相关性得分
4. Should 阶段(评分提升)
└── 如果文档 age = 30,增加额外得分
实际文档匹配示例 #
假设索引中有以下文档:
文档1: {"name": "张三", "age": 25, "status": "active"}
文档2: {"name": "张三", "age": 30, "status": "active"}
文档3: {"name": "张三丰", "age": 35, "status": "active"}
文档4: {"name": "李四", "age": 30, "status": "active"}
文档5: {"name": "张三", "age": 16, "status": "active"}
逐步筛选过程: #
1. Filter (age >= 18):
保留:文档1, 文档2, 文档3, 文档4
排除:文档5 (age=16)
2. Must_not (age != 25):
保留:文档2, 文档3, 文档4
排除:文档1 (age=25)
3. Must (name 匹配 "张三"):
保留:文档2 (张三), 文档3 (可能匹配张三丰)
排除:文档4 (李四)
4. Should (age = 30 加分):
文档2: 基础分 + should 分 = 高分
文档3: 仅基础分 = 中等分
最终结果排序: #
1. 文档2: {"name": "张三", "age": 30} - 最高分
2. 文档3: {"name": "张三丰", "age": 35} - 较低分
性能优化分析 #
执行性能对比 #
Filter > Must_not > Must > Should
↑ ↑ ↑ ↑
最快 较快 较慢 最慢
(缓存) (缓存) (计算分) (计算分)
缓存机制 #
# 会被缓存的查询
- filter 子句中的所有查询
- must_not 子句中的所有查询
# 不会被缓存的查询
- must 子句中的查询(需要计算相关性)
- should 子句中的查询(需要计算相关性)
查询优化建议 #
# ✅ 好的做法:先过滤再查询
{
"query": {
"bool": {
"filter": [ // 先用filter缩小范围
{"range": {"age": {"gte": 18}}},
{"term": {"status": "active"}}
],
"must": [ // 再在小范围内计算相关性
{"match": {"content": "elasticsearch"}}
]
}
}
}
# ❌ 不好的做法:全量计算后过滤
{
"query": {
"bool": {
"must": [ // 先对所有文档计算相关性
{"match": {"content": "elasticsearch"}},
{"term": {"status": "active"}}, // 这些应该放在filter中
{"range": {"age": {"gte": 18}}}
]
}
}
}
实际应用场景举例 #
电商商品搜索 #
GET /products/_search
{
"query": {
"bool": {
"must": [
{"multi_match": {
"query": "iPhone 手机",
"fields": ["title^3", "description"]
}}
],
"must_not": [
{"term": {"status": "discontinued"}}
],
"should": [
{"term": {"brand": "Apple"}},
{"range": {"rating": {"gte": 4.5}}}
],
"filter": [
{"term": {"in_stock": true}},
{"range": {"price": {"gte": 1000, "lte": 8000}}}
]
}
}
}
新闻内容搜索 #
GET /news/_search
{
"query": {
"bool": {
"must": [
{"match": {"title": "人工智能"}}
],
"must_not": [
{"term": {"category": "advertisement"}}
],
"should": [
{"match": {"author": "专家"}},
{"range": {"publish_date": {"gte": "2025-01-01"}}}
],
"filter": [
{"term": {"status": "published"}},
{"range": {"view_count": {"gte": 100}}}
]
}
}
}
常见问题和注意事项 #
1. Should 的最小匹配数 #
{
"query": {
"bool": {
"should": [
{"term": {"tag": "elasticsearch"}},
{"term": {"tag": "search"}},
{"term": {"tag": "lucene"}}
],
"minimum_should_match": 2 // 至少匹配2个条件
}
}
}
2. 嵌套 Bool 查询 #
{
"query": {
"bool": {
"must": [
{
"bool": { // 嵌套bool查询
"should": [
{"term": {"category": "tech"}},
{"term": {"category": "science"}}
]
}
}
]
}
}
}
3. 性能调优要点 #
- 将精确匹配条件放在
filter中 - 将全文搜索条件放在
must中 - 合理使用
should进行相关性调整 - 避免在
must中使用term查询
这个 bool 查询综合运用了 Elasticsearch 的核心查询能力,既保证了查询的准确性,又优化了性能,是实际生产环境中非常典型的查询模式。
XIUANGH: 对于elastic search它的查询、或者说是更新,删除之类的语法来说,使用的是纯json的格式。这种纯json的格式呃可读性比较好,但是在es收到这些消息之后是如何处理他们并把它拼成一条完整的查询语句的,或者说是他如何解析这个json的语法书让它变成一个可以查询的语句?
GitHub Copilot: # Elasticsearch JSON 查询解析和执行机制深度分析
整体处理流程概述 #
Elasticsearch 接收到 JSON 查询后,会经历以下几个关键步骤:
JSON 请求 → JSON 解析 → DSL 解析 → 查询构建 → Lucene 查询 → 执行
让我详细分析每个环节:
1. JSON 解析阶段 #
底层 JSON 解析器 #
Elasticsearch 使用 Jackson 库来解析 JSON:
// ES 内部 JSON 解析示例
ObjectMapper mapper = new ObjectMapper();
JsonNode queryNode = mapper.readTree(jsonString);
// 你的查询 JSON
{
"query": {
"bool": {
"must": [
{"match": {"name": "张三"}}
]
}
}
}
// 解析后的 JsonNode 树状结构
JsonNode queryNode = rootNode.get("query");
JsonNode boolNode = queryNode.get("bool");
JsonNode mustNode = boolNode.get("must");
JSON 到对象映射 #
ES 会将 JSON 映射为内部的 Java 对象:
// ES 内部处理流程
public class SearchRequest {
private QueryBuilder query; // 查询构建器
private List<SortBuilder> sort; // 排序构建器
private Integer from; // 分页起始
private Integer size; // 分页大小
// ... 其他字段
}
2. Query DSL 解析机制 #
查询构建器模式 #
ES 使用 Builder 模式 来构建查询:
// 你的 JSON 查询
{
"query": {
"bool": {
"must": [
{"match": {"name": "张三"}}
],
"filter": [
{"range": {"age": {"gte": 18}}}
]
}
}
}
// ES 内部转换为 QueryBuilder
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", "张三"))
.filter(QueryBuilders.rangeQuery("age").gte(18));
具体解析过程 #
1. 识别查询类型 #
// ES 查询类型映射表
Map<String, QueryParser> queryParsers = Map.of(
"match", new MatchQueryParser(),
"term", new TermQueryParser(),
"bool", new BoolQueryParser(),
"range", new RangeQueryParser(),
// ... 更多查询类型
);
// 根据 JSON 键值确定查询类型
String queryType = queryJson.fieldNames().next(); // "bool"
QueryParser parser = queryParsers.get(queryType);
2. 递归解析查询树 #
// BoolQueryParser 的解析逻辑
public class BoolQueryParser implements QueryParser {
public QueryBuilder parse(JsonNode json) {
BoolQueryBuilder boolQuery = new BoolQueryBuilder();
// 解析 must 子句
if (json.has("must")) {
JsonNode mustNode = json.get("must");
for (JsonNode clause : mustNode) {
QueryBuilder subQuery = parseSubQuery(clause);
boolQuery.must(subQuery);
}
}
// 解析 filter 子句
if (json.has("filter")) {
JsonNode filterNode = json.get("filter");
for (JsonNode clause : filterNode) {
QueryBuilder subQuery = parseSubQuery(clause);
boolQuery.filter(subQuery);
}
}
return boolQuery;
}
}
3. 查询构建器到 Lucene 查询转换 #
QueryBuilder 到 Lucene Query #
每个 QueryBuilder 都会转换为对应的 Lucene Query:
// ES QueryBuilder 转换示例
public class MatchQueryBuilder implements QueryBuilder {
public Query toQuery(QueryShardContext context) {
// 获取字段分析器
Analyzer analyzer = context.getAnalyzer();
// 分析查询文本
TokenStream tokens = analyzer.tokenStream("name", "张三");
List<String> terms = analyzeTokens(tokens);
// 构建 Lucene 查询
if (terms.size() == 1) {
return new TermQuery(new Term("name", terms.get(0)));
} else {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (String term : terms) {
builder.add(new TermQuery(new Term("name", term)),
BooleanClause.Occur.SHOULD);
}
return builder.build();
}
}
}
具体转换映射 #
// ES 查询类型到 Lucene 查询的映射
ES Query Builder → Lucene Query
├── MatchQueryBuilder → BooleanQuery/TermQuery
├── TermQueryBuilder → TermQuery
├── RangeQueryBuilder → NumericRangeQuery/TermRangeQuery
├── BoolQueryBuilder → BooleanQuery
├── WildcardQueryBuilder → WildcardQuery
└── PrefixQueryBuilder → PrefixQuery
4. 完整解析流程示例 #
让我用你之前的查询来演示完整流程:
原始 JSON 查询 #
{
"query": {
"bool": {
"must": [
{"match": {"name": "张三"}}
],
"must_not": [
{"term": {"age": 25}}
],
"should": [
{"term": {"age": 30}}
],
"filter": [
{"range": {"age": {"gte": 18}}}
]
}
}
}
第一步:JSON 解析 #
// Jackson 解析 JSON
JsonNode rootNode = objectMapper.readTree(jsonString);
JsonNode queryNode = rootNode.get("query");
JsonNode boolNode = queryNode.get("bool");
// 提取各个子句
JsonNode mustNode = boolNode.get("must"); // [{"match": {"name": "张三"}}]
JsonNode mustNotNode = boolNode.get("must_not"); // [{"term": {"age": 25}}]
JsonNode shouldNode = boolNode.get("should"); // [{"term": {"age": 30}}]
JsonNode filterNode = boolNode.get("filter"); // [{"range": {"age": {"gte": 18}}}]
第二步:构建 QueryBuilder #
// 创建 BoolQueryBuilder
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 解析 must 子句
for (JsonNode clause : mustNode) {
String queryType = clause.fieldNames().next(); // "match"
JsonNode matchNode = clause.get("match");
String field = matchNode.fieldNames().next(); // "name"
String value = matchNode.get(field).asText(); // "张三"
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery(field, value);
boolQuery.must(matchQuery);
}
// 解析 must_not 子句
for (JsonNode clause : mustNotNode) {
String queryType = clause.fieldNames().next(); // "term"
JsonNode termNode = clause.get("term");
String field = termNode.fieldNames().next(); // "age"
int value = termNode.get(field).asInt(); // 25
TermQueryBuilder termQuery = QueryBuilders.termQuery(field, value);
boolQuery.mustNot(termQuery);
}
// 类似地解析 should 和 filter...
第三步:转换为 Lucene 查询 #
// BoolQueryBuilder 转换为 Lucene BooleanQuery
public Query toQuery(QueryShardContext context) {
BooleanQuery.Builder luceneBuilder = new BooleanQuery.Builder();
// must 子句转换
for (QueryBuilder mustClause : mustClauses) {
Query luceneQuery = mustClause.toQuery(context);
luceneBuilder.add(luceneQuery, BooleanClause.Occur.MUST);
}
// must_not 子句转换
for (QueryBuilder mustNotClause : mustNotClauses) {
Query luceneQuery = mustNotClause.toQuery(context);
luceneBuilder.add(luceneQuery, BooleanClause.Occur.MUST_NOT);
}
// should 子句转换
for (QueryBuilder shouldClause : shouldClauses) {
Query luceneQuery = shouldClause.toQuery(context);
luceneBuilder.add(luceneQuery, BooleanClause.Occur.SHOULD);
}
// filter 子句转换(特殊处理)
for (QueryBuilder filterClause : filterClauses) {
Query luceneQuery = filterClause.toQuery(context);
luceneBuilder.add(luceneQuery, BooleanClause.Occur.FILTER);
}
return luceneBuilder.build();
}
第四步:最终 Lucene 查询 #
// 最终生成的 Lucene 查询结构
BooleanQuery finalQuery = new BooleanQuery.Builder()
.add(new BooleanQuery.Builder() // match 查询的结果
.add(new TermQuery(new Term("name", "张")), SHOULD)
.add(new TermQuery(new Term("name", "三")), SHOULD)
.build(), MUST)
.add(new TermQuery(new Term("age", "25")), MUST_NOT)
.add(new TermQuery(new Term("age", "30")), SHOULD)
.add(NumericRangeQuery.newIntRange("age", 18, Integer.MAX_VALUE, true, true), FILTER)
.build();
5. 执行和优化机制 #
查询执行顺序优化 #
ES 会重新排列查询子句以优化性能:
// ES 内部查询重排序逻辑
public Query rewrite(Query query) {
if (query instanceof BooleanQuery) {
BooleanQuery boolQuery = (BooleanQuery) query;
// 重排序策略:
// 1. Filter 子句最先执行(最快)
// 2. Must_not 子句次之(排除文档)
// 3. Must 子句(需要计算评分)
// 4. Should 子句最后(评分调整)
List<BooleanClause> reorderedClauses = reorderClauses(boolQuery.clauses());
return new BooleanQuery.Builder()
.addAll(reorderedClauses)
.build();
}
}
缓存机制 #
// Filter 查询缓存
public class FilterCache {
private Map<Query, BitSet> cache = new ConcurrentHashMap<>();
public BitSet getOrCompute(Query filterQuery, IndexReader reader) {
BitSet cached = cache.get(filterQuery);
if (cached != null) {
return cached; // 直接返回缓存结果
}
// 执行查询并缓存结果
BitSet result = executeFilter(filterQuery, reader);
cache.put(filterQuery, result);
return result;
}
}
6. 更新和删除操作解析 #
更新操作解析 #
// 更新查询 JSON
{
"script": {
"source": "ctx._source.age += params.increment",
"params": {
"increment": 1
}
}
}
// ES 内部更新解析
public class UpdateRequestParser {
public UpdateRequest parse(JsonNode json) {
UpdateRequest request = new UpdateRequest();
if (json.has("script")) {
JsonNode scriptNode = json.get("script");
Script script = parseScript(scriptNode);
request.script(script);
}
if (json.has("doc")) {
JsonNode docNode = json.get("doc");
request.doc(parseDocument(docNode));
}
return request;
}
}
删除操作解析 #
{
"query": {
"term": {
"status": "deleted"
}
}
}
// 删除查询解析
public class DeleteByQueryRequestParser {
public DeleteByQueryRequest parse(JsonNode json) {
DeleteByQueryRequest request = new DeleteByQueryRequest();
if (json.has("query")) {
QueryBuilder query = parseQuery(json.get("query"));
request.setQuery(query);
}
return request;
}
}
7. 性能优化策略 #
解析优化 #
// ES 使用对象池避免重复创建
public class QueryBuilderPool {
private Queue<BoolQueryBuilder> boolQueryPool = new ConcurrentLinkedQueue<>();
private Queue<MatchQueryBuilder> matchQueryPool = new ConcurrentLinkedQueue<>();
public BoolQueryBuilder getBoolQuery() {
BoolQueryBuilder query = boolQueryPool.poll();
return query != null ? query.reset() : new BoolQueryBuilder();
}
public void returnBoolQuery(BoolQueryBuilder query) {
boolQueryPool.offer(query);
}
}
查询缓存 #
// 查询结果缓存
public class QueryCache {
private Cache<QueryCacheKey, QueryCacheValue> cache;
public SearchResponse getCachedResult(SearchRequest request) {
QueryCacheKey key = new QueryCacheKey(request);
QueryCacheValue cached = cache.get(key);
if (cached != null && !cached.isExpired()) {
return cached.getResponse();
}
return null; // 缓存未命中
}
}
8. 错误处理和验证 #
JSON 语法验证 #
public class QueryValidator {
public void validate(JsonNode queryNode) throws QueryValidationException {
// 检查必填字段
if (!queryNode.has("query")) {
throw new QueryValidationException("Missing required field: query");
}
// 检查查询类型
JsonNode query = queryNode.get("query");
String queryType = query.fieldNames().next();
if (!SUPPORTED_QUERIES.contains(queryType)) {
throw new QueryValidationException("Unsupported query type: " + queryType);
}
// 递归验证子查询
validateSubQueries(query);
}
}
运行时错误处理 #
public class QueryExecutor {
public SearchResponse execute(SearchRequest request) {
try {
Query luceneQuery = request.source().query().toQuery(context);
return searcher.search(luceneQuery);
} catch (Exception e) {
return SearchResponse.error(
"Query execution failed: " + e.getMessage()
);
}
}
}
总结 #
Elasticsearch 的 JSON 查询解析是一个多层次的复杂过程:
- JSON 解析:使用 Jackson 将 JSON 字符串解析为 Java 对象树
- DSL 解析:根据查询类型选择对应的解析器,构建 QueryBuilder 对象
- 查询构建:使用 Builder 模式递归构建复杂的查询结构
- Lucene 转换:将 ES 的 QueryBuilder 转换为 Lucene 的 Query 对象
- 执行优化:重排序查询子句,应用缓存机制,优化执行性能
这种设计使得 ES 既保持了 JSON 的可读性和灵活性,又能够高效地转换为底层 Lucene 能够理解和执行的查询,是一个非常优雅的架构设计。
XIUANGH: “sort”: [ { “age”: { “order”: “desc” // desc降序,asc升序 } }, { “created_at”: { “order”: “desc” } } ] 对于这个查询来说,排序优先级是,先按照age 然后按照created_at排序吗?
GitHub Copilot: # Elasticsearch 排序优先级详解
是的,你的理解完全正确!在 Elasticsearch 的排序数组中,排序优先级按照数组元素的顺序执行。
排序执行顺序 #
"sort": [
{
"age": {
"order": "desc" // 第一优先级:按age降序
}
},
{
"created_at": {
"order": "desc" // 第二优先级:按created_at降序
}
}
]
具体执行逻辑 #
- 第一优先级:首先按照
age字段降序排列 - 第二优先级:当
age相同时,再按照created_at字段降序排列
实际排序示例 #
假设有以下文档数据:
文档1: {"name": "张三", "age": 30, "created_at": "2025-06-01"}
文档2: {"name": "李四", "age": 25, "created_at": "2025-06-15"}
文档3: {"name": "王五", "age": 30, "created_at": "2025-06-10"}
文档4: {"name": "赵六", "age": 25, "created_at": "2025-06-05"}
文档5: {"name": "孙七", "age": 35, "created_at": "2025-06-08"}
排序过程 #
第一步:按 age 降序排列 #
文档5: age=35 (最大)
文档1: age=30
文档3: age=30 (age相同)
文档2: age=25
文档4: age=25 (age相同)
第二步:age 相同的情况下,按 created_at 降序排列 #
文档5: age=35, created_at=2025-06-08
文档1: age=30, created_at=2025-06-01 (较早)
文档3: age=30, created_at=2025-06-10 (较晚,排在前面)
文档2: age=25, created_at=2025-06-15 (较晚,排在前面)
文档4: age=25, created_at=2025-06-05 (较早)
最终排序结果 #
1. 文档5: {"name": "孙七", "age": 35, "created_at": "2025-06-08"}
2. 文档3: {"name": "王五", "age": 30, "created_at": "2025-06-10"}
3. 文档1: {"name": "张三", "age": 30, "created_at": "2025-06-01"}
4. 文档2: {"name": "李四", "age": 25, "created_at": "2025-06-15"}
5. 文档4: {"name": "赵六", "age": 25, "created_at": "2025-06-05"}
多种排序方式示例 #
1. 混合升降序 #
"sort": [
{
"age": {
"order": "desc" // 年龄降序
}
},
{
"salary": {
"order": "asc" // 工资升序
}
},
{
"created_at": {
"order": "desc" // 创建时间降序
}
}
]
2. 字段缺失值处理 #
"sort": [
{
"age": {
"order": "desc",
"missing": "_last" // 缺失值排在最后
}
},
{
"score": {
"order": "desc",
"missing": 0 // 缺失值当作0处理
}
}
]
3. 相关性得分结合字段排序 #
"sort": [
{
"_score": {
"order": "desc" // 先按相关性得分降序
}
},
{
"created_at": {
"order": "desc" // 得分相同时按时间降序
}
}
]
排序性能优化建议 #
1. 字段映射优化 #
PUT /my_index
{
"mappings": {
"properties": {
"age": {
"type": "integer",
"doc_values": true // 启用文档值,提升排序性能
},
"created_at": {
"type": "date",
"doc_values": true
}
}
}
}
2. 避免对 text 字段排序 #
# ❌ 不推荐:对 text 字段排序
"sort": [
{
"title": { // text 字段,性能差
"order": "asc"
}
}
]
# ✅ 推荐:对 keyword 字段排序
"sort": [
{
"title.keyword": { // keyword 字段,性能好
"order": "asc"
}
}
]
3. 限制排序字段数量 #
# 过多的排序字段会影响性能
"sort": [
{"field1": {"order": "desc"}},
{"field2": {"order": "asc"}},
{"field3": {"order": "desc"}},
// ... 避免过多排序字段
]
特殊排序场景 #
1. 地理位置排序 #
"sort": [
{
"_geo_distance": {
"location": {
"lat": 40.0,
"lon": 116.0
},
"order": "asc",
"unit": "km"
}
}
]
2. 脚本排序 #
"sort": [
{
"_script": {
"type": "number",
"script": {
"source": "doc['likes'].value - doc['dislikes'].value"
},
"order": "desc"
}
}
]
3. 嵌套字段排序 #
"sort": [
{
"comments.rating": {
"order": "desc",
"nested": {
"path": "comments"
}
}
}
]
排序与分页结合 #
GET /my_index/_search
{
"query": {
"match": {"title": "elasticsearch"}
},
"sort": [
{
"age": {"order": "desc"}
},
{
"created_at": {"order": "desc"}
}
],
"from": 0,
"size": 10
}
执行顺序:查询 → 排序 → 分页
总结要点 #
- 排序优先级:严格按照
sort数组中的顺序执行 - 多级排序:前面字段相同时,才会使用后面字段排序
- 性能考虑:尽量对 keyword、数字、日期等字段排序,避免对 text 字段排序
- 缺失值处理:可以通过
missing参数控制缺失值的排序位置
你的理解是正确的:先按照 age 降序排序,当 age 相同时,再按照 created_at 降序排序!
XIUANGH: ### 7. 高亮显示
GET /user_index/_search
{
"query": {
"match": {
"name": "张三"
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": ["<em>"],
"post_tags": ["</em>"]
}
}
}
}
#
这个高亮显示是什么意思?详细说明一下
GitHub Copilot: # Elasticsearch 高亮显示详解
什么是高亮显示? #
高亮显示(Highlighting)是 Elasticsearch 的一个重要功能,它会在搜索结果中标记出匹配查询词的部分,类似于我们在 Google 搜索时看到的效果——搜索关键词会以不同颜色或样式突出显示。
基本工作原理 #
执行流程 #
1. 执行搜索查询
2. 找到匹配的文档
3. 在匹配字段中定位查询词
4. 用指定标签包围查询词
5. 返回高亮片段
你的示例解析 #
GET /user_index/_search
{
"query": {
"match": {
"name": "张三"
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": ["<em>"], // 前置标签
"post_tags": ["</em>"] // 后置标签
}
}
}
}
返回结果示例 #
假设索引中有这些文档 #
文档1: {"name": "张三是个好学生", "age": 25}
文档2: {"name": "张三丰是武术大师", "age": 100}
文档3: {"name": "小张三岁了", "age": 3}
查询结果 #
{
"hits": {
"hits": [
{
"_source": {
"name": "张三是个好学生",
"age": 25
},
"highlight": {
"name": ["<em>张三</em>是个好学生"] // 高亮结果
}
},
{
"_source": {
"name": "张三丰是武术大师",
"age": 100
},
"highlight": {
"name": ["<em>张三</em>丰是武术大师"]
}
},
{
"_source": {
"name": "小张三岁了",
"age": 3
},
"highlight": {
"name": ["小<em>张</em><em>三</em>岁了"] // 分词后分别高亮
}
}
]
}
}
高亮配置详解 #
1. 基本配置参数 #
pre_tags 和 post_tags #
"highlight": {
"fields": {
"content": {
"pre_tags": ["<strong class='highlight'>"], // 自定义开始标签
"post_tags": ["</strong>"] // 自定义结束标签
}
}
}
# 结果示例
"highlight": {
"content": ["这是<strong class='highlight'>搜索</strong>的内容"]
}
fragment_size(片段大小) #
"highlight": {
"fields": {
"content": {
"fragment_size": 150, // 高亮片段长度(字符数)
"number_of_fragments": 3 // 返回的片段数量
}
}
}
# 对于长文本,会返回包含关键词的多个片段
"highlight": {
"content": [
"这是第一个包含<em>关键词</em>的片段...",
"这是第二个包含<em>关键词</em>的片段...",
"这是第三个包含<em>关键词</em>的片段..."
]
}
no_match_size(无匹配时的片段) #
"highlight": {
"fields": {
"content": {
"no_match_size": 100 // 即使没有匹配也返回前100个字符
}
}
}
2. 高级配置选项 #
高亮器类型 #
"highlight": {
"fields": {
"content": {
"type": "unified", // 默认,性能最好
// "type": "plain", // 基础高亮器
// "type": "fvh" // 快速向量高亮器
}
}
}
短语高亮 #
"highlight": {
"fields": {
"content": {
"type": "unified",
"phrase_limit": 256 // 短语查询的词项限制
}
}
}
实际应用场景 #
1. 电商商品搜索 #
GET /products/_search
{
"query": {
"multi_match": {
"query": "苹果手机",
"fields": ["title^3", "description"]
}
},
"highlight": {
"pre_tags": ["<mark class='search-highlight'>"],
"post_tags": ["</mark>"],
"fields": {
"title": {
"fragment_size": 50,
"number_of_fragments": 1
},
"description": {
"fragment_size": 200,
"number_of_fragments": 2
}
}
}
}
返回结果 #
{
"hits": [
{
"_source": {
"title": "Apple iPhone 14 Pro 智能手机",
"description": "这款苹果手机采用最新A16芯片,拍照效果出色..."
},
"highlight": {
"title": ["Apple iPhone 14 Pro 智能<mark class='search-highlight'>手机</mark>"],
"description": [
"这款<mark class='search-highlight'>苹果</mark><mark class='search-highlight'>手机</mark>采用最新A16芯片...",
"专业级拍照<mark class='search-highlight'>手机</mark>,满足您的需求..."
]
}
}
]
}
2. 文章内容搜索 #
GET /articles/_search
{
"query": {
"match": {
"content": "Elasticsearch 搜索引擎"
}
},
"highlight": {
"pre_tags": ["<span style='background-color: yellow'>"],
"post_tags": ["</span>"],
"fields": {
"title": {},
"content": {
"fragment_size": 300,
"number_of_fragments": 3,
"fragmenter": "span" // 按句子分割
}
}
}
}
3. 新闻搜索系统 #
GET /news/_search
{
"query": {
"bool": {
"must": [
{"match": {"title": "人工智能"}},
{"match": {"content": "机器学习"}}
]
}
},
"highlight": {
"require_field_match": false, // 不要求字段匹配
"fields": {
"title": {
"pre_tags": ["<strong class='title-highlight'>"],
"post_tags": ["</strong>"]
},
"content": {
"pre_tags": ["<em class='content-highlight'>"],
"post_tags": ["</em>"],
"fragment_size": 150,
"number_of_fragments": 2
},
"summary": {} // 使用默认配置
}
}
}
高亮显示的类型 #
1. Unified Highlighter(推荐) #
"highlight": {
"fields": {
"content": {
"type": "unified" // 默认类型,性能最佳
}
}
}
特点:
- 性能最好
- 支持复杂查询
- 准确度高
- 支持短语查询
2. Plain Highlighter #
"highlight": {
"fields": {
"content": {
"type": "plain"
}
}
}
特点:
- 基础功能
- 兼容性好
- 性能中等
3. Fast Vector Highlighter #
"highlight": {
"fields": {
"content": {
"type": "fvh"
}
}
}
特点:
- 需要
term_vector设置 - 适合大文本
- 支持复杂高亮
中文高亮的特殊考虑 #
IK 分词器配合高亮 #
GET /chinese_articles/_search
{
"query": {
"match": {
"content": "中华人民共和国"
}
},
"highlight": {
"pre_tags": ["<em class='chinese-highlight'>"],
"post_tags": ["</em>"],
"fields": {
"content": {
"type": "unified",
"fragment_size": 100,
"fragmenter": "simple" // 简单分片器,适合中文
}
}
}
}
中文高亮结果 #
{
"highlight": {
"content": [
"<em class='chinese-highlight'>中华人民共和国</em>成立于1949年...",
"...在<em class='chinese-highlight'>中华人民共和国</em>的土地上..."
]
}
}
前端展示实现 #
HTML 展示 #
<!-- 搜索结果展示 -->
<div class="search-results">
<div class="result-item">
<h3 class="result-title">
<!-- 显示高亮的标题 -->
Apple iPhone 14 Pro 智能<mark class='search-highlight'>手机</mark>
</h3>
<p class="result-description">
<!-- 显示高亮的描述片段 -->
这款<mark class='search-highlight'>苹果</mark><mark class='search-highlight'>手机</mark>采用最新A16芯片...
</p>
</div>
</div>
CSS 样式 #
.search-highlight {
background-color: #ffff99;
font-weight: bold;
padding: 1px 2px;
border-radius: 2px;
}
.title-highlight {
background-color: #ffd700;
color: #333;
}
.content-highlight {
background-color: #e6f3ff;
color: #0066cc;
}
JavaScript 处理 #
// 处理搜索结果中的高亮
function displaySearchResults(response) {
const results = response.hits.hits;
results.forEach(result => {
const source = result._source;
const highlight = result.highlight;
// 使用高亮内容替代原始内容
const title = highlight.title ? highlight.title[0] : source.title;
const content = highlight.content ? highlight.content.join('...') : source.content;
// 渲染到页面
renderResult(title, content);
});
}
性能优化建议 #
1. 字段映射优化 #
PUT /articles
{
"mappings": {
"properties": {
"content": {
"type": "text",
"store": true, // 存储原始值,提升高亮性能
"term_vector": "with_positions_offsets" // 支持高性能高亮
}
}
}
}
2. 限制高亮字段 #
# ❌ 不好的做法:对所有字段高亮
"highlight": {
"fields": {
"*": {} // 通配符会影响性能
}
}
# ✅ 好的做法:只对需要的字段高亮
"highlight": {
"fields": {
"title": {},
"content": {}
}
}
3. 控制片段数量 #
"highlight": {
"fields": {
"content": {
"fragment_size": 150,
"number_of_fragments": 3, // 不要过多
"no_match_size": 50 // 控制无匹配片段大小
}
}
}
总结 #
Elasticsearch 的高亮显示功能:
- 作用:在搜索结果中突出显示匹配的关键词
- 原理:用指定的 HTML 标签包围匹配的词项
- 配置:可以自定义标签、片段大小、数量等
- 应用:广泛用于搜索引擎、电商、内容管理系统等
- 优化:合理配置字段映射和高亮参数以提升性能
这个功能让用户能够快速识别搜索结果中的相关内容,极大提升了用户体验!
XIUANGH: 详细介绍一下elasticsearch中涉及到的全部关键字
GitHub Copilot: # Elasticsearch 全部关键字详细介绍
1. 查询类关键字 (Query Keywords) #
基础查询 (Basic Queries) #
query
#
GET /index/_search
{
"query": { # 查询的根节点
"match_all": {}
}
}
- 作用:定义查询的根节点
- 位置:请求体顶层
- 必须性:可选(默认使用 match_all)
match
#
{
"query": {
"match": {
"field_name": "search_text"
}
}
}
- 作用:全文搜索,会对查询词进行分词
- 适用字段:text 类型
- 特点:支持模糊匹配、同义词
match_all
#
{
"query": {
"match_all": {}
}
}
- 作用:匹配所有文档
- 评分:所有文档得分相同
- 用途:获取全部数据、分页浏览
match_none
#
{
"query": {
"match_none": {}
}
}
- 作用:不匹配任何文档
- 用途:测试、条件查询的占位符
match_phrase
#
{
"query": {
"match_phrase": {
"content": {
"query": "elasticsearch tutorial",
"slop": 2
}
}
}
}
- 作用:短语匹配,词序敏感
- slop:允许词项间的最大距离
- 用途:精确短语搜索
match_phrase_prefix
#
{
"query": {
"match_phrase_prefix": {
"title": "elasticsearch tuto"
}
}
}
- 作用:短语前缀匹配
- 特点:最后一个词支持前缀匹配
- 用途:搜索建议、自动补全
multi_match
#
{
"query": {
"multi_match": {
"query": "elasticsearch",
"fields": ["title^3", "content", "tags"],
"type": "best_fields"
}
}
}
- 作用:多字段搜索
- type:best_fields、most_fields、cross_fields
- fields:搜索的字段列表,支持权重设置
精确查询 (Term-based Queries) #
term
#
{
"query": {
"term": {
"status.keyword": "published"
}
}
}
- 作用:精确值匹配
- 不分词:查询词不会被分析
- 适用:keyword、数字、日期、布尔值
terms
#
{
"query": {
"terms": {
"tags.keyword": ["java", "elasticsearch", "lucene"]
}
}
}
- 作用:多值精确匹配(IN 操作)
- 等价SQL:WHERE field IN (value1, value2, value3)
range
#
{
"query": {
"range": {
"age": {
"gte": 18,
"lte": 65
}
}
}
}
- 操作符:
gte:大于等于 (>=)gt:大于 (>)lte:小于等于 (<=)lt:小于 (<)
- 适用:数字、日期字段
exists
#
{
"query": {
"exists": {
"field": "email"
}
}
}
- 作用:检查字段是否存在且不为空
- 等价SQL:WHERE field IS NOT NULL
prefix
#
{
"query": {
"prefix": {
"username.keyword": "admin"
}
}
}
- 作用:前缀匹配
- 性能:可能较慢,建议使用 keyword 字段
wildcard
#
{
"query": {
"wildcard": {
"filename.keyword": "*.jpg"
}
}
}
- 通配符:
*:匹配零个或多个字符?:匹配单个字符
- 性能:避免以通配符开头
regexp
#
{
"query": {
"regexp": {
"phone": "[0-9]{11}"
}
}
}
- 作用:正则表达式匹配
- 语法:支持 Lucene 正则语法
- 性能:复杂正则会影响性能
fuzzy
#
{
"query": {
"fuzzy": {
"title": {
"value": "elasticsearc",
"fuzziness": 2
}
}
}
}
- 作用:模糊匹配(拼写纠错)
- fuzziness:允许的编辑距离
- 用途:处理用户输入错误
复合查询 (Compound Queries) #
bool
#
{
"query": {
"bool": {
"must": [],
"must_not": [],
"should": [],
"filter": []
}
}
}
- must:必须匹配(AND,影响评分)
- must_not:必须不匹配(NOT,不影响评分)
- should:应该匹配(OR,影响评分)
- filter:过滤条件(AND,不影响评分,可缓存)
dis_max
#
{
"query": {
"dis_max": {
"queries": [
{"match": {"title": "elasticsearch"}},
{"match": {"content": "elasticsearch"}}
],
"tie_breaker": 0.3
}
}
}
- 作用:取最高分查询的结果
- tie_breaker:其他查询的权重系数
constant_score
#
{
"query": {
"constant_score": {
"filter": {
"term": {"status": "published"}
},
"boost": 1.2
}
}
}
- 作用:固定评分查询
- boost:设置固定评分值
boosting
#
{
"query": {
"boosting": {
"positive": {
"match": {"content": "elasticsearch"}
},
"negative": {
"match": {"content": "deprecated"}
},
"negative_boost": 0.2
}
}
}
- positive:正向查询
- negative:负向查询(降低评分)
- negative_boost:负向查询的权重
特殊查询 (Specialized Queries) #
more_like_this
#
{
"query": {
"more_like_this": {
"fields": ["title", "content"],
"like": [
{"_index": "articles", "_id": "1"}
],
"min_term_freq": 1,
"max_query_terms": 12
}
}
}
- 作用:相似文档查询
- like:参考文档
- 参数:min_term_freq、max_query_terms 等
function_score
#
{
"query": {
"function_score": {
"query": {"match": {"title": "elasticsearch"}},
"functions": [
{
"filter": {"term": {"category": "tech"}},
"weight": 1.5
}
],
"boost_mode": "multiply"
}
}
}
- 作用:自定义评分函数
- functions:评分函数列表
- boost_mode:评分合并方式
2. 聚合类关键字 (Aggregation Keywords) #
aggs / aggregations
#
{
"aggs": {
"my_aggregation": {
"terms": {
"field": "category.keyword"
}
}
}
}
- 作用:聚合操作的根节点
- 别名:aggs 和 aggregations 等价
桶聚合 (Bucket Aggregations) #
terms
#
{
"aggs": {
"categories": {
"terms": {
"field": "category.keyword",
"size": 10
}
}
}
}
- 作用:分组聚合
- size:返回桶的数量
- 等价SQL:GROUP BY
range
#
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{"to": 100},
{"from": 100, "to": 200},
{"from": 200}
]
}
}
}
}
- 作用:范围分组
- ranges:范围定义列表
date_range
#
{
"aggs": {
"date_ranges": {
"date_range": {
"field": "created_at",
"ranges": [
{"from": "2024-01-01", "to": "2024-06-30"},
{"from": "2024-07-01", "to": "2024-12-31"}
]
}
}
}
}
- 作用:日期范围分组
- format:日期格式设置
date_histogram
#
{
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "month"
}
}
}
}
- 作用:时间直方图
- calendar_interval:日历间隔(year、month、day)
- fixed_interval:固定间隔(30d、1h)
histogram
#
{
"aggs": {
"price_histogram": {
"histogram": {
"field": "price",
"interval": 50
}
}
}
}
- 作用:数值直方图
- interval:间隔大小
filters
#
{
"aggs": {
"messages": {
"filters": {
"filters": {
"errors": {"match": {"level": "ERROR"}},
"warnings": {"match": {"level": "WARNING"}}
}
}
}
}
}
- 作用:多个过滤器分组
- filters:命名过滤器集合
nested
#
{
"aggs": {
"nested_agg": {
"nested": {
"path": "comments"
},
"aggs": {
"avg_rating": {
"avg": {"field": "comments.rating"}
}
}
}
}
}
- 作用:嵌套对象聚合
- path:嵌套字段路径
指标聚合 (Metric Aggregations) #
avg
#
{
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
- 作用:平均值计算
- 等价SQL:AVG(field)
sum
#
{
"aggs": {
"total_sales": {
"sum": {
"field": "amount"
}
}
}
}
- 作用:求和计算
- 等价SQL:SUM(field)
min / max
#
{
"aggs": {
"min_price": {"min": {"field": "price"}},
"max_price": {"max": {"field": "price"}}
}
}
- 作用:最小值/最大值
- 等价SQL:MIN(field)、MAX(field)
stats
#
{
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
}
}
}
- 返回:count、min、max、avg、sum
- 作用:基础统计信息
extended_stats
#
{
"aggs": {
"price_extended_stats": {
"extended_stats": {
"field": "price"
}
}
}
}
- 返回:stats + variance、std_deviation 等
- 作用:扩展统计信息
value_count
#
{
"aggs": {
"unique_users": {
"value_count": {
"field": "user_id"
}
}
}
}
- 作用:非空值计数
- 等价SQL:COUNT(field)
cardinality
#
{
"aggs": {
"unique_users": {
"cardinality": {
"field": "user_id.keyword"
}
}
}
}
- 作用:唯一值计数(近似)
- 等价SQL:COUNT(DISTINCT field)
percentiles
#
{
"aggs": {
"load_time_outlier": {
"percentiles": {
"field": "load_time",
"percents": [95, 99, 99.9]
}
}
}
}
- 作用:百分位数计算
- percents:指定百分位
top_hits
#
{
"aggs": {
"categories": {
"terms": {"field": "category.keyword"},
"aggs": {
"top_sales": {
"top_hits": {
"sort": [{"sales": {"order": "desc"}}],
"size": 3
}
}
}
}
}
}
- 作用:返回每个桶的顶部文档
- sort:排序方式
- size:返回文档数
3. 控制类关键字 (Control Keywords) #
分页和大小控制 #
from
#
{
"from": 20,
"size": 10,
"query": {"match_all": {}}
}
- 作用:分页起始位置(从0开始)
- 限制:from + size <= 10000(默认)
size
#
{
"size": 50,
"query": {"match_all": {}}
}
- 作用:返回文档数量
- 默认:10
- 最大:10000(可配置)
search_after
#
{
"size": 10,
"query": {"match_all": {}},
"sort": [{"timestamp": "desc"}, {"_id": "desc"}],
"search_after": ["2024-01-01T00:00:00", "doc_id_123"]
}
- 作用:深度分页解决方案
- 优势:不受 10000 限制
- 要求:必须有唯一排序字段
字段控制 #
_source
#
{
"query": {"match_all": {}},
"_source": ["title", "author", "publish_date"]
}
- 作用:控制返回的字段
- 值类型:
true:返回完整 _sourcefalse:不返回 _source["field1", "field2"]:指定字段列表{"includes": [], "excludes": []}:包含/排除模式
fields
#
{
"query": {"match_all": {}},
"fields": [
"title",
{"field": "date", "format": "yyyy-MM-dd"}
]
}
- 作用:提取并格式化字段值
- format:支持日期、数字格式化
- 区别:与 _source 不同,可以格式化
stored_fields
#
{
"query": {"match_all": {}},
"stored_fields": ["title", "content"]
}
- 作用:返回存储的字段
- 要求:字段映射中设置
"store": true
排序控制 #
sort
#
{
"query": {"match_all": {}},
"sort": [
{"score": {"order": "desc"}},
{"date": {"order": "desc"}},
"_doc"
]
}
- order:asc(升序)、desc(降序)
- 特殊值:
_score:相关性评分_doc:文档顺序(最快)
track_scores
#
{
"query": {"match": {"title": "elasticsearch"}},
"sort": [{"date": "desc"}],
"track_scores": true
}
- 作用:排序时仍计算评分
- 默认:排序时不计算评分
超时和性能控制 #
timeout
#
{
"timeout": "5s",
"query": {"match_all": {}}
}
- 作用:查询超时时间
- 格式:5s、1m、500ms
terminate_after
#
{
"terminate_after": 1000,
"query": {"match_all": {}}
}
- 作用:匹配指定数量文档后终止
- 用途:快速估算匹配数量
batched_reduce_size
#
{
"batched_reduce_size": 5,
"aggs": {
"categories": {
"terms": {"field": "category.keyword"}
}
}
}
- 作用:聚合批量归约大小
- 用途:内存优化
4. 高亮和建议关键字 #
高亮显示 #
highlight
#
{
"query": {"match": {"content": "elasticsearch"}},
"highlight": {
"pre_tags": ["<em>"],
"post_tags": ["</em>"],
"fields": {
"content": {
"fragment_size": 150,
"number_of_fragments": 3
}
}
}
}
- pre_tags:高亮前置标签
- post_tags:高亮后置标签
- fragment_size:片段大小
- number_of_fragments:片段数量
require_field_match
#
{
"highlight": {
"require_field_match": false,
"fields": {
"title": {},
"content": {}
}
}
}
- 作用:是否要求字段匹配才高亮
- false:即使字段不匹配也高亮
搜索建议 #
suggest
#
{
"suggest": {
"my_suggestion": {
"text": "elasticsearc",
"term": {
"field": "title"
}
}
}
}
- term:词项建议(拼写纠错)
- phrase:短语建议
- completion:自动补全建议
5. 索引和映射关键字 #
索引操作 #
index
#
PUT /my_index/_doc/1
{
"title": "Elasticsearch Guide",
"content": "This is a comprehensive guide"
}
- 作用:创建或更新文档
- 幂等性:相同 ID 会覆盖
create
#
PUT /my_index/_create/1
{
"title": "New Document"
}
- 作用:仅创建新文档
- 限制:文档已存在时失败
update
#
POST /my_index/_update/1
{
"doc": {
"title": "Updated Title"
}
}
- doc:部分更新内容
- script:脚本更新
- upsert:不存在时创建
delete
#
DELETE /my_index/_doc/1
- 作用:删除文档
- 结果:返回删除结果
批量操作 #
bulk
#
POST /_bulk
{"index":{"_index":"my_index","_id":"1"}}
{"title":"Document 1"}
{"update":{"_index":"my_index","_id":"1"}}
{"doc":{"title":"Updated Document 1"}}
{"delete":{"_index":"my_index","_id":"2"}}
- index:索引操作
- create:创建操作
- update:更新操作
- delete:删除操作
映射定义 #
mappings
#
PUT /my_index
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "standard"
},
"date": {
"type": "date",
"format": "yyyy-MM-dd"
}
}
}
}
properties
#
- 作用:定义字段属性
- 包含:type、analyzer、format 等
数据类型关键字 #
- text:全文搜索
- keyword:精确值搜索
- integer、long、float、double:数值类型
- date:日期类型
- boolean:布尔类型
- object:对象类型
- nested:嵌套对象类型
- geo_point:地理坐标点
- ip:IP 地址类型
设置关键字 #
settings
#
PUT /my_index
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "custom",
"tokenizer": "standard"
}
}
}
}
}
核心设置 #
- number_of_shards:主分片数
- number_of_replicas:副本数
- refresh_interval:刷新间隔
- max_result_window:最大结果窗口
6. 脚本和函数关键字 #
脚本相关 #
script
#
{
"script": {
"source": "ctx._source.views += params.increment",
"params": {
"increment": 1
}
}
}
- source:脚本源码
- params:脚本参数
- lang:脚本语言(默认 painless)
ctx
#
- ctx._source:当前文档源
- ctx._id:文档 ID
- ctx._version:文档版本
- ctx.op:操作类型
函数评分 #
boost
#
{
"query": {
"match": {
"title": {
"query": "elasticsearch",
"boost": 2.0
}
}
}
}
- 作用:查询权重调整
- 默认:1.0
minimum_should_match
#
{
"query": {
"bool": {
"should": [
{"match": {"title": "elasticsearch"}},
{"match": {"content": "search"}}
],
"minimum_should_match": 1
}
}
}
- 作用:should 子句最小匹配数
- 格式:数字或百分比
7. 地理位置关键字 #
geo_distance
#
{
"query": {
"geo_distance": {
"distance": "10km",
"location": {
"lat": 40.0,
"lon": 116.0
}
}
}
}
- distance:距离范围
- location:中心点坐标
geo_bounding_box
#
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": {"lat": 40.0, "lon": 116.0},
"bottom_right": {"lat": 39.0, "lon": 117.0}
}
}
}
}
- 作用:矩形区域查询
- top_left:左上角坐标
- bottom_right:右下角坐标
8. 特殊控制关键字 #
版本控制 #
version
#
PUT /my_index/_doc/1?version=1
{
"title": "Document with version control"
}
- 作用:乐观锁控制
- 类型:内部版本、外部版本
if_seq_no / if_primary_term
#
PUT /my_index/_doc/1?if_seq_no=5&if_primary_term=1
{
"title": "Updated document"
}
- 作用:基于序列号的并发控制
- 优势:比 version 更精确
路由控制 #
routing
#
PUT /my_index/_doc/1?routing=user123
{
"user_id": "user123",
"content": "User content"
}
- 作用:控制文档分片路由
- 用途:相关文档放在同一分片
刷新控制 #
refresh
#
POST /my_index/_doc/1?refresh=wait_for
{
"title": "New document"
}
- true:立即刷新
- wait_for:等待刷新
- false:不刷新(默认)
总结 #
Elasticsearch 的关键字体系庞大而完整,涵盖了:
- 查询类:从基础的 match 到复杂的 function_score
- 聚合类:桶聚合和指标聚合的各种类型
- 控制类:分页、排序、字段控制等
- 索引类:CRUD 操作和批量处理
- 映射类:数据类型和字段属性定义
- 特殊类:脚本、地理位置、版本控制等
掌握这些关键字的使用方法和组合技巧,是高效使用 Elasticsearch 的基础。在实际应用中,通常会根据业务需求组合使用多个关键字来构建复杂的查询和聚合操作。
找到具有 1 个许可证类型的类似代码
XIUANGH: 详细介绍一下elasticsearch内部的存储结构
GitHub Copilot: # Elasticsearch 内部存储结构详解
1. 整体架构概览 #
Elasticsearch 的存储结构是多层次的,从上到下包括:
集群 (Cluster)
├── 节点 (Node)
├── 索引 (Index)
├── 分片 (Shard)
├── 段 (Segment)
├── 倒排索引 (Inverted Index)
├── 正排索引 (Forward Index)
├── 列存储 (Columnar Storage)
└── 文档存储 (Document Store)
2. 逻辑存储结构 #
集群层级 (Cluster Level) #
# 集群状态信息
{
"cluster_name": "my-cluster",
"nodes": {
"node-1": {...},
"node-2": {...},
"node-3": {...}
},
"indices": {
"user_index": {...},
"product_index": {...}
}
}
特点:
- 集群元数据存储在主节点
- 包含节点信息、索引信息、分片分配信息
- 使用 Raft 协议保证一致性
索引层级 (Index Level) #
# 索引结构
user_index/
├── _mapping # 映射定义
├── _settings # 索引设置
├── _aliases # 别名信息
└── shards/ # 分片数据
├── 0/ # 主分片0
├── 1/ # 主分片1
└── 2/ # 主分片2
映射存储示例:
{
"user_index": {
"mappings": {
"properties": {
"name": {"type": "text", "analyzer": "ik_max_word"},
"age": {"type": "integer"},
"created_at": {"type": "date"}
}
}
}
}
分片层级 (Shard Level) #
# 分片内部结构
shard_0/
├── index/ # Lucene 索引文件
│ ├── segments_N # 段信息文件
│ ├── write.lock # 写锁文件
│ └── _*.si # 段元数据文件
├── translog/ # 事务日志
│ ├── translog-1.tlog
│ └── translog.ckp
└── _state/ # 分片状态
└── state-*.st
3. 物理存储结构 #
基于 Lucene 的存储 #
Elasticsearch 底层使用 Apache Lucene 作为存储引擎:
# Lucene 索引文件结构
index/
├── segments_N # 段提交点文件
├── _0.cfs # 复合文件(包含多个索引文件)
├── _0.cfe # 复合文件入口
├── _0.si # 段信息文件
├── _0_Lucene50_0.doc # 文档存储
├── _0_Lucene50_0.tim # 词项索引
├── _0_Lucene50_0.tip # 词项指针
├── _0_Lucene50_0.pos # 位置信息
└── _0_Lucene50_0.pay # 载荷信息
段 (Segment) 详细结构 #
每个段包含以下核心文件:
1. 倒排索引文件 #
# 词项字典 (.tim 文件)
{
"elasticsearch": {
"doc_freq": 1000, # 文档频率
"total_term_freq": 1500 # 总词频
},
"java": {
"doc_freq": 800,
"total_term_freq": 1200
}
}
# 文档列表 (.doc 文件)
"elasticsearch" -> [doc1, doc5, doc8, doc12, ...]
"java" -> [doc2, doc5, doc9, doc15, ...]
2. 存储字段文件 #
# .fdt 文件 - 文档存储
文档1: {"name": "张三", "age": 25, "content": "elasticsearch tutorial"}
文档2: {"name": "李四", "age": 30, "content": "java programming"}
# .fdx 文件 - 文档索引
文档1 -> offset: 0, length: 120
文档2 -> offset: 120, length: 95
3. 词向量文件 #
# .tvd 文件 - 词向量数据
文档1_content: ["elasticsearch"(1), "tutorial"(1)]
文档2_content: ["java"(1), "programming"(1)]
# .tvx 文件 - 词向量索引
文档1 -> offset: 0
文档2 -> offset: 48
4. 核心存储组件 #
倒排索引 (Inverted Index) #
# 倒排索引结构示例
词项 "elasticsearch":
├── 文档频率: 3
├── 文档列表: [1, 5, 8]
├── 词频信息: [2, 1, 3] # 每个文档中的出现次数
├── 位置信息:
│ ├── 文档1: [0, 15] # 在文档中的位置
│ ├── 文档5: [5]
│ └── 文档8: [2, 10, 25]
└── 载荷信息: [...] # 额外的元数据
实际存储格式:
// Term Dictionary (词项字典)
Map<String, TermInfo> termDict = {
"elasticsearch" -> TermInfo{
docFreq: 3,
filePointer: 1024, // 指向 posting list 的位置
skipPointer: 2048 // 跳跃表指针
}
}
// Posting List (倒排表)
"elasticsearch" -> {
docs: [1, 5, 8], # 文档ID列表(Delta编码)
freqs: [2, 1, 3], # 词频列表
positions: [[0,15], [5], [2,10,25]] # 位置列表
}
正排索引 (Doc Values) #
# Doc Values 存储结构
字段 "age":
├── 文档1: 25
├── 文档2: 30
├── 文档3: 35
└── 索引结构:
├── 排序索引: [25->doc1, 30->doc2, 35->doc3]
└── 压缩存储: BitPacking/LZ4压缩
存储优化:
// 数值字段的压缩存储
int[] ages = [25, 30, 35, 25, 40, 30, 25];
// 去重 + 映射表
uniqueValues = [25, 30, 35, 40]; // 4个唯一值
ordinals = [0, 1, 2, 0, 3, 1, 0]; // 映射到唯一值
// 使用2位存储每个序号(4个值需要2位)
bitPacked = "00 01 10 00 11 01 00"
列存储 (Columnar Storage) #
# 按列存储优化
原始文档:
doc1: {name: "张三", age: 25, city: "北京"}
doc2: {name: "李四", age: 30, city: "上海"}
doc3: {name: "王五", age: 35, city: "北京"}
# 列存储格式
name列: ["张三", "李四", "王五"]
age列: [25, 30, 35]
city列: ["北京", "上海", "北京"]
# 每列独立压缩和索引
city列压缩:
字典: ["北京"->0, "上海"->1]
数据: [0, 1, 0] # 2位存储
5. 存储优化技术 #
压缩算法 #
1. Delta 编码 #
# 文档ID压缩
原始: [1, 5, 8, 12, 15, 20]
Delta: [1, 4, 3, 4, 3, 5] # 差值编码
VByte: [1, 4, 3, 4, 3, 5] # 变长字节编码
2. 前缀压缩 #
# 词项压缩
词项列表:
- "elasticsearch"
- "elasticsuite"
- "elastic"
压缩后:
- "elasticsearch" (完整)
- "suite" (前缀长度:7, 后缀:"suite")
- "" (前缀长度:7, 后缀:"")
3. 块压缩 #
# 文档存储压缩
原始文档块 (16KB):
[doc1_json, doc2_json, doc3_json, ...]
LZ4压缩后 (4KB):
[compressed_block + metadata]
缓存机制 #
1. 文件系统缓存 #
# 操作系统级别缓存
索引文件 -> 内存映射 -> 文件系统缓存
├── 热点数据常驻内存
├── 冷数据按需加载
└── LRU 淘汰策略
2. 查询缓存 #
# Filter 查询缓存
{
"query_hash": "abc123",
"result_bitset": "010101010...", # 匹配文档的位集合
"cache_time": "2025-06-14T10:30:00"
}
3. 字段数据缓存 #
# 聚合/排序字段缓存
{
"field": "category.keyword",
"values": ["tech", "science", "news", ...],
"ordinals": [0, 1, 2, 0, 1, ...],
"memory_usage": "50MB"
}
6. 事务日志 (Translog) #
Translog 结构 #
# Translog 文件组织
translog/
├── translog-1.tlog # 事务日志文件
├── translog-2.tlog
├── translog.ckp # 检查点文件
└── translog-gen # 生成号文件
日志记录格式 #
// Translog 操作记录
TranslogOperation {
opType: INDEX, // 操作类型
id: "doc_123", // 文档ID
source: "{\"name\":\"张三\"}", // 文档内容
version: 1, // 版本号
timestamp: 1634567890, // 时间戳
seqNo: 100, // 序列号
primaryTerm: 1 // 主分片任期
}
持久化机制 #
# 写入流程
1. 文档写入 -> Translog (持久化)
2. 文档写入 -> 内存缓冲区
3. Refresh -> 内存缓冲区刷新到段
4. Flush -> 段同步到磁盘 + Translog清理
7. 分片分配和路由 #
分片路由算法 #
// 分片路由计算
public int calculateShard(String id, int numberOfShards) {
return Math.abs(id.hashCode()) % numberOfShards;
}
// 示例
文档ID "user_123" -> hash: 1234567 -> 1234567 % 3 = 0 -> 分片0
文档ID "user_456" -> hash: 7891234 -> 7891234 % 3 = 1 -> 分片1
副本分配策略 #
# 分片分配示例 (3节点, 3主分片, 1副本)
节点1: [P0, R1, R2] # P=主分片, R=副本分片
节点2: [P1, R0, R2]
节点3: [P2, R0, R1]
# 确保:
# 1. 主分片和副本分片不在同一节点
# 2. 分片均匀分布在各节点
# 3. 故障转移时自动重新分配
8. 存储性能优化 #
写入优化 #
1. 批量写入 #
# 批量操作减少网络和处理开销
POST /_bulk
{"index":{"_index":"logs","_id":"1"}}
{"message":"log entry 1","timestamp":"2025-06-14T10:30:00"}
{"index":{"_index":"logs","_id":"2"}}
{"message":"log entry 2","timestamp":"2025-06-14T10:30:01"}
2. 异步写入 #
// 异步写入流程
1. 写入请求 -> 内存缓冲区 (立即返回)
2. 后台线程 -> 定期刷新缓冲区
3. 段合并 -> 后台异步执行
3. 段合并策略 #
# 段合并配置
{
"index.merge.policy.max_merge_at_once": 10,
"index.merge.policy.segments_per_tier": 10,
"index.merge.scheduler.max_thread_count": 1
}
读取优化 #
1. 预加载策略 #
# 预加载热点数据
{
"index.store.preload": ["nvd", "dvd", "tim", "doc"]
}
2. 查询优化 #
// 查询执行顺序优化
1. Filter查询 (最快, 可缓存)
2. 精确匹配查询 (term, range)
3. 全文搜索查询 (match, 需要评分)
4. 聚合计算 (基于前面结果)
9. 存储监控和维护 #
存储状态监控 #
# 查看索引存储信息
GET /_cat/indices?v&h=index,store.size,pri.store.size
# 查看分片存储分布
GET /_cat/shards?v&h=index,shard,prirep,state,store
# 查看段信息
GET /my_index/_segments
存储维护操作 #
1. 强制合并 #
# 合并段以优化存储
POST /my_index/_forcemerge?max_num_segments=1
2. 存储压缩 #
# 启用最佳压缩
PUT /my_index/_settings
{
"index.codec": "best_compression"
}
3. 冷热数据分离 #
# 热数据节点
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.node_concurrent_recoveries": 2
}
}
# 冷数据索引设置
PUT /old_logs/_settings
{
"index.routing.allocation.require.box_type": "cold"
}
总结 #
Elasticsearch 的存储结构是一个精心设计的多层次架构:
- 逻辑层次:集群 → 索引 → 分片 → 段
- 物理存储:基于 Lucene 的文件系统存储
- 核心组件:倒排索引、正排索引、列存储
- 优化技术:压缩算法、缓存机制、异步处理
- 可靠性保证:事务日志、副本机制、故障恢复
这种设计既保证了高性能的搜索和聚合能力,又提供了良好的扩展性和可靠性,是现代搜索引擎存储架构的典型代表。
XIUANGH: GET /user_index/_search { “size”: 0, “aggs”: { “age_stats”: { “stats”: { “field”: “age” } } } } 这个stats是干什么的?
GitHub Copilot: # Elasticsearch stats 聚合详解
stats 聚合的作用
#
stats 聚合是 Elasticsearch 中的一个指标聚合(Metric Aggregation),它会对指定数值字段进行基础统计分析,一次性返回多个统计指标。
返回的统计指标 #
stats 聚合会返回以下 5 个基础统计值:
{
"aggregations": {
"age_stats": {
"count": 1000, // 文档数量(非空值)
"min": 18.0, // 最小值
"max": 80.0, // 最大值
"avg": 35.5, // 平均值
"sum": 35500.0 // 总和
}
}
}
各指标说明 #
- count:参与计算的文档数量(字段值非空的文档)
- min:字段的最小值
- max:字段的最大值
- avg:字段的平均值(sum / count)
- sum:字段值的总和
完整查询示例和结果 #
假设数据 #
# 用户索引中的文档
文档1: {"name": "张三", "age": 25}
文档2: {"name": "李四", "age": 30}
文档3: {"name": "王五", "age": 35}
文档4: {"name": "赵六", "age": 40}
文档5: {"name": "孙七", "age": null} # null值不参与计算
查询结果 #
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 5,
"relation": "eq"
},
"max_score": null,
"hits": [] // size: 0,不返回文档内容
},
"aggregations": {
"age_stats": {
"count": 4, // 4个非空age值
"min": 25.0, // 最小年龄
"max": 40.0, // 最大年龄
"avg": 32.5, // 平均年龄 (25+30+35+40)/4
"sum": 130.0 // 年龄总和 25+30+35+40
}
}
}
实际应用场景 #
1. 用户年龄分析 #
GET /users/_search
{
"size": 0,
"aggs": {
"age_analysis": {
"stats": {
"field": "age"
}
}
}
}
# 结果用途:
# - 了解用户年龄分布范围
# - 计算平均年龄
# - 统计用户总数
2. 商品价格统计 #
GET /products/_search
{
"size": 0,
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
}
}
}
# 可以得到:
# - 最便宜和最贵的商品价格
# - 平均价格
# - 商品总价值
3. 订单金额分析 #
GET /orders/_search
{
"size": 0,
"query": {
"range": {
"order_date": {
"gte": "2025-01-01",
"lte": "2025-06-30"
}
}
},
"aggs": {
"order_amount_stats": {
"stats": {
"field": "amount"
}
}
}
}
# 半年订单统计:
# - 订单数量
# - 最大/最小订单金额
# - 平均订单价值
# - 总营收
与其他聚合的对比 #
stats vs 单独的指标聚合
#
# 使用 stats(推荐)
{
"aggs": {
"age_stats": {
"stats": {"field": "age"}
}
}
}
# 等价于分别使用多个聚合(不推荐)
{
"aggs": {
"age_count": {"value_count": {"field": "age"}},
"age_min": {"min": {"field": "age"}},
"age_max": {"max": {"field": "age"}},
"age_avg": {"avg": {"field": "age"}},
"age_sum": {"sum": {"field": "age"}}
}
}
优势:
- 一次聚合获得多个统计值
- 性能更好(只遍历一次数据)
- 代码更简洁
stats vs extended_stats
#
# stats:基础统计
{
"aggs": {
"basic_stats": {
"stats": {"field": "age"}
}
}
}
# 返回:count, min, max, avg, sum
# extended_stats:扩展统计
{
"aggs": {
"detailed_stats": {
"extended_stats": {"field": "age"}
}
}
}
# 额外返回:variance(方差), std_deviation(标准差),
# std_deviation_bounds(标准差边界)等
高级用法 #
1. 结合查询条件 #
GET /users/_search
{
"size": 0,
"query": {
"bool": {
"filter": [
{"term": {"status": "active"}},
{"range": {"created_at": {"gte": "2025-01-01"}}}
]
}
},
"aggs": {
"active_user_age_stats": {
"stats": {
"field": "age"
}
}
}
}
2. 分组统计 #
GET /users/_search
{
"size": 0,
"aggs": {
"by_gender": {
"terms": {
"field": "gender.keyword"
},
"aggs": {
"age_stats": {
"stats": {
"field": "age"
}
}
}
}
}
}
# 结果:每个性别组的年龄统计
{
"aggregations": {
"by_gender": {
"buckets": [
{
"key": "male",
"doc_count": 500,
"age_stats": {
"count": 500,
"min": 18.0,
"max": 65.0,
"avg": 35.2,
"sum": 17600.0
}
},
{
"key": "female",
"doc_count": 500,
"age_stats": {
"count": 500,
"min": 20.0,
"max": 60.0,
"avg": 33.8,
"sum": 16900.0
}
}
]
}
}
}
3. 脚本字段统计 #
GET /products/_search
{
"size": 0,
"aggs": {
"profit_stats": {
"stats": {
"script": {
"source": "doc['sell_price'].value - doc['cost_price'].value"
}
}
}
}
}
注意事项 #
1. 字段类型要求 #
# ✅ 适用的字段类型
- integer、long、float、double
- date(会转换为数值)
- boolean(true=1, false=0)
# ❌ 不适用的字段类型
- text(全文本字段)
- keyword(除非是数值型字符串)
2. 空值处理 #
# null 值会被忽略
文档1: {"age": 25} # 参与计算
文档2: {"age": null} # 被忽略
文档3: {} # 字段不存在,被忽略
# 如果所有值都是null,返回:
{
"count": 0,
"min": null,
"max": null,
"avg": null,
"sum": 0.0
}
3. 性能考虑 #
# ✅ 性能优化建议
- 对 doc_values 字段进行统计(默认开启)
- 使用 filter 减少计算范围
- 避免对 text 字段统计
# 字段映射优化
{
"mappings": {
"properties": {
"age": {
"type": "integer",
"doc_values": true // 开启文档值,提升聚合性能
}
}
}
}
总结 #
stats 聚合是一个非常实用的统计工具:
- 功能:一次性获得数值字段的 5 个基础统计指标
- 优势:性能好、使用简单、结果全面
- 适用场景:数据分析、报表统计、监控指标
- 返回指标:count、min、max、avg、sum
在你的示例中,"size": 0 表示不返回具体文档,只返回聚合结果,这是做数据统计分析时的常用模式。
找到具有 2 个许可证类型的类似代码
XIUANGH: terms 聚合(分组统计)、range 聚合(范围分组)、date_histogram 聚合(时间分组) 分别是什么意思?
GitHub Copilot: # Elasticsearch 聚合类型详解
1. terms 聚合(分组统计)
#
基本概念 #
terms 聚合是最常用的桶聚合,它会按照字段的不同值进行分组,类似于 SQL 中的 GROUP BY。
语法结构 #
{
"aggs": {
"聚合名称": {
"terms": {
"field": "字段名",
"size": 返回桶的数量
}
}
}
}
实际示例 #
按商品类别分组 #
GET /products/_search
{
"size": 0,
"aggs": {
"categories": {
"terms": {
"field": "category.keyword",
"size": 10
}
}
}
}
返回结果 #
{
"aggregations": {
"categories": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "电子产品",
"doc_count": 1500
},
{
"key": "服装",
"doc_count": 1200
},
{
"key": "书籍",
"doc_count": 800
},
{
"key": "食品",
"doc_count": 600
}
]
}
}
}
结合子聚合 #
GET /orders/_search
{
"size": 0,
"aggs": {
"by_status": {
"terms": {
"field": "status.keyword"
},
"aggs": {
"avg_amount": {
"avg": {
"field": "amount"
}
},
"total_amount": {
"sum": {
"field": "amount"
}
}
}
}
}
}
应用场景 #
- 商品分类统计
- 用户地区分布
- 订单状态统计
- 日志错误类型分析
2. range 聚合(范围分组)
#
基本概念 #
range 聚合按照数值范围对文档进行分组,每个范围作为一个桶。适用于将连续的数值划分为不同的区间。
语法结构 #
{
"aggs": {
"聚合名称": {
"range": {
"field": "数值字段",
"ranges": [
{"to": 值},
{"from": 值, "to": 值},
{"from": 值}
]
}
}
}
}
实际示例 #
按价格范围分组 #
GET /products/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{"to": 100, "key": "低价位"},
{"from": 100, "to": 500, "key": "中价位"},
{"from": 500, "to": 1000, "key": "高价位"},
{"from": 1000, "key": "奢侈品"}
]
}
}
}
}
返回结果 #
{
"aggregations": {
"price_ranges": {
"buckets": [
{
"key": "低价位",
"from": 0.0,
"to": 100.0,
"doc_count": 2500
},
{
"key": "中价位",
"from": 100.0,
"to": 500.0,
"doc_count": 1800
},
{
"key": "高价位",
"from": 500.0,
"to": 1000.0,
"doc_count": 600
},
{
"key": "奢侈品",
"from": 1000.0,
"doc_count": 100
}
]
}
}
}
按年龄段分组用户 #
GET /users/_search
{
"size": 0,
"aggs": {
"age_groups": {
"range": {
"field": "age",
"ranges": [
{"to": 18, "key": "未成年"},
{"from": 18, "to": 30, "key": "青年"},
{"from": 30, "to": 50, "key": "中年"},
{"from": 50, "key": "老年"}
]
},
"aggs": {
"avg_income": {
"avg": {
"field": "income"
}
}
}
}
}
}
应用场景 #
- 商品价格区间分析
- 用户年龄段统计
- 成绩等级划分
- 收入水平分层
3. date_histogram 聚合(时间分组)
#
基本概念 #
date_histogram 聚合是专门用于时间字段的分组聚合,按照指定的时间间隔(如天、月、年)对文档进行分组。
语法结构 #
{
"aggs": {
"聚合名称": {
"date_histogram": {
"field": "日期字段",
"calendar_interval": "时间间隔", // 或使用 fixed_interval
"format": "日期格式",
"time_zone": "时区"
}
}
}
}
时间间隔类型 #
calendar_interval(日历间隔) #
- "1M" 或 "month":按月分组
- "1w" 或 "week":按周分组
- "1d" 或 "day":按天分组
- "1h" 或 "hour":按小时分组
- "1y" 或 "year":按年分组
fixed_interval(固定间隔) #
- "30d":每30天
- "12h":每12小时
- "15m":每15分钟
- "30s":每30秒
实际示例 #
按月统计订单量 #
GET /orders/_search
{
"size": 0,
"aggs": {
"orders_over_time": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "month",
"format": "yyyy-MM",
"time_zone": "Asia/Shanghai"
},
"aggs": {
"total_amount": {
"sum": {
"field": "amount"
}
},
"avg_amount": {
"avg": {
"field": "amount"
}
}
}
}
}
}
返回结果 #
{
"aggregations": {
"orders_over_time": {
"buckets": [
{
"key_as_string": "2025-01",
"key": 1735689600000,
"doc_count": 1500,
"total_amount": {
"value": 450000.0
},
"avg_amount": {
"value": 300.0
}
},
{
"key_as_string": "2025-02",
"key": 1738368000000,
"doc_count": 1800,
"total_amount": {
"value": 540000.0
},
"avg_amount": {
"value": 300.0
}
}
]
}
}
}
按天统计网站访问量 #
GET /access_logs/_search
{
"size": 0,
"query": {
"range": {
"timestamp": {
"gte": "2025-06-01",
"lte": "2025-06-30"
}
}
},
"aggs": {
"daily_visits": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "day",
"format": "yyyy-MM-dd"
},
"aggs": {
"unique_visitors": {
"cardinality": {
"field": "user_id.keyword"
}
},
"page_views": {
"value_count": {
"field": "page_url.keyword"
}
}
}
}
}
}
按小时分析系统负载 #
GET /system_metrics/_search
{
"size": 0,
"aggs": {
"hourly_load": {
"date_histogram": {
"field": "timestamp",
"fixed_interval": "1h",
"time_zone": "+08:00"
},
"aggs": {
"avg_cpu": {
"avg": {
"field": "cpu_usage"
}
},
"max_memory": {
"max": {
"field": "memory_usage"
}
}
}
}
}
}
应用场景 #
- 销售趋势分析
- 网站流量统计
- 系统监控指标
- 业务增长分析
4. 三种聚合的对比总结 #
| 聚合类型 | 分组依据 | 适用字段 | 常见用途 | SQL等价 |
|---|---|---|---|---|
| terms | 字段的不同值 | keyword, 数值 | 分类统计 | GROUP BY |
| range | 数值范围区间 | 数值字段 | 区间分布 | CASE WHEN |
| date_histogram | 时间间隔 | 日期字段 | 时间序列 | GROUP BY DATE_FORMAT() |
组合使用示例 #
GET /ecommerce/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": {
"field": "category.keyword"
},
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{"to": 100},
{"from": 100, "to": 500},
{"from": 500}
]
},
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "sale_date",
"calendar_interval": "month"
}
}
}
}
}
}
}
}
这个查询展示了三层聚合:
- 按商品类别分组(terms)
- 在每个类别中按价格区间分组(range)
- 在每个价格区间中按月份分组(date_histogram)
这样可以得到:每个商品类别下,不同价格区间的月度销售情况。
XIUANGH: # 搜索"苹果手机" GET /_search { “query”: { “bool”: { “must”: [ { “multi_match”: { “query”: “苹果 手机”, “fields”: [“title^2”, “description”] // title权重更高 } } ], “filter”: [ { “term”: { “category”: “手机” } }, { “range”: { “price”: { “gte”: 5000, “lte”: 10000 } } } ] } }, “sort”: [ { “price”: { “order”: “asc” } } ], “highlight”: { “fields”: { “title”: {}, “description”: {} } } }
这里面的权重是什么意思: “fields”: [“title^2”, “description”] // title权重更高?详细介绍一下
GitHub Copilot: # Elasticsearch 字段权重详解
权重的基本概念 #
"title^2" 中的 ^2 表示字段权重(Field Boost),它告诉 Elasticsearch 在计算相关性评分时,title 字段的重要性是 description 字段的 2 倍。
权重如何影响评分 #
基础权重概念 #
"fields": ["title^2", "description"]
# 等价于
"fields": ["title^2", "description^1"] // description默认权重为1
权重计算过程 #
假设有两个文档:
文档1: {
"title": "苹果手机iPhone14",
"description": "这是一款高端智能手机"
}
文档2: {
"title": "手机配件大全",
"description": "苹果手机专用配件,包括充电器、耳机等"
}
不使用权重的评分 #
# 查询:"苹果 手机"
文档1评分计算:
- title匹配度: 0.8 (包含"苹果"和"手机")
- description匹配度: 0.3 (包含"手机")
- 总分: 0.8 + 0.3 = 1.1
文档2评分计算:
- title匹配度: 0.4 (包含"手机")
- description匹配度: 0.6 (包含"苹果"和"手机")
- 总分: 0.4 + 0.6 = 1.0
使用权重后的评分 #
# "fields": ["title^2", "description"]
文档1评分计算:
- title匹配度: 0.8 × 2 = 1.6 (权重加倍)
- description匹配度: 0.3 × 1 = 0.3
- 总分: 1.6 + 0.3 = 1.9
文档2评分计算:
- title匹配度: 0.4 × 2 = 0.8 (权重加倍)
- description匹配度: 0.6 × 1 = 0.6
- 总分: 0.8 + 0.6 = 1.4
结果:使用权重后,文档1(标题匹配更好)的排名更靠前。
权重设置的实际应用 #
1. 电商商品搜索 #
GET /products/_search
{
"query": {
"multi_match": {
"query": "苹果手机",
"fields": [
"title^3", // 标题最重要
"brand^2", // 品牌次重要
"description^1", // 描述基础权重
"tags^0.5" // 标签权重较低
]
}
}
}
权重设置逻辑 #
- title^3:商品标题最能代表商品本质
- brand^2:品牌对购买决策很重要
- description^1:详细描述有一定参考价值
- tags^0.5:标签只是辅助信息
2. 新闻文章搜索 #
GET /articles/_search
{
"query": {
"multi_match": {
"query": "人工智能",
"fields": [
"title^5", // 标题权重最高
"summary^3", // 摘要权重较高
"content^1", // 正文基础权重
"author^2", // 作者权重中等
"category^1.5" // 分类权重略高
]
}
}
}
3. 用户搜索 #
GET /users/_search
{
"query": {
"multi_match": {
"query": "张三",
"fields": [
"username^4", // 用户名最重要
"real_name^3", // 真实姓名很重要
"email^2", // 邮箱中等重要
"nickname^1.5", // 昵称略重要
"bio^1" // 个人简介基础权重
]
}
}
}
权重的数值含义 #
权重范围和建议 #
| 权重值 | 含义 | 适用场景 |
|---|---|---|
| ^0.1 | 极低权重 | 几乎不重要的字段 |
| ^0.5 | 低权重 | 辅助信息字段 |
| ^1 | 基础权重 | 普通内容字段 |
| ^2 | 中等权重 | 重要属性字段 |
| ^3-5 | 高权重 | 核心标识字段 |
| ^10+ | 极高权重 | 唯一标识或关键字段 |
权重设置示例 #
# 合理的权重配置
{
"multi_match": {
"query": "elasticsearch",
"fields": [
"exact_title^10", // 精确标题匹配
"title^5", // 普通标题
"keywords^3", // 关键词
"summary^2", // 摘要
"content^1", // 内容
"tags^0.5" // 标签
]
}
}
权重的数学原理 #
TF-IDF 评分影响 #
# 基础评分公式(简化版)
score = tf × idf × fieldNorm × queryNorm × boost
# 其中 boost 就是字段权重
final_score = base_score × boost_value
实际计算示例 #
// 不带权重的评分
{
"title": {
"term_freq": 2, // 词频
"doc_freq": 100, // 文档频率
"base_score": 0.8
}
}
// 带权重的评分
{
"title^3": {
"term_freq": 2,
"doc_freq": 100,
"base_score": 0.8,
"boost": 3.0,
"final_score": 0.8 × 3.0 = 2.4
}
}
高级权重用法 #
1. 动态权重调整 #
GET /products/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "手机",
"fields": ["title^2", "description"]
}
},
"functions": [
{
"filter": {"term": {"is_promoted": true}},
"weight": 2.0 // 推广商品额外加权
},
{
"filter": {"range": {"rating": {"gte": 4.5}}},
"weight": 1.5 // 高评分商品加权
}
]
}
}
}
2. 条件权重 #
GET /articles/_search
{
"query": {
"bool": {
"should": [
{
"multi_match": {
"query": "人工智能",
"fields": ["title^3", "content"]
}
},
{
"multi_match": {
"query": "人工智能",
"fields": ["title^5"], // 标题完全匹配时权重更高
"type": "phrase"
}
}
]
}
}
}
3. 时间相关权重 #
GET /news/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "新闻",
"fields": ["title^3", "content"]
}
},
"functions": [
{
"gauss": {
"publish_date": {
"origin": "now",
"scale": "7d",
"decay": 0.5
}
}
}
]
}
}
}
权重设置最佳实践 #
1. 权重设置原则 #
业务重要性优先 #
# 电商搜索权重设置
"fields": [
"sku^10", // SKU完全匹配最重要
"title^5", // 商品标题很重要
"brand^3", // 品牌重要
"category^2", // 类别中等重要
"description^1", // 描述基础重要
"specifications^0.5" // 规格参数参考性
]
用户行为数据指导 #
# 根据点击率调整权重
"fields": [
"title^4", // 标题点击率高,权重高
"summary^2.5", // 摘要点击率中等
"author^2", // 作者权威性影响点击
"content^1" // 内容匹配基础权重
]
2. A/B 测试验证权重 #
# 版本A:传统权重
{
"fields": ["title^2", "description^1"]
}
# 版本B:调整权重
{
"fields": ["title^3", "description^1", "brand^2"]
}
# 通过转化率、点击率等指标验证效果
3. 避免权重过高 #
# ❌ 权重过高的问题
{
"fields": ["title^100", "description^1"]
}
# 问题:title字段权重过高,可能导致其他字段完全失效
# ✅ 合理的权重范围
{
"fields": ["title^5", "description^1", "tags^0.5"]
}
# 优点:保持字段间的平衡,避免单一字段主导
实际效果对比 #
搜索"苹果手机"的结果对比 #
不使用权重 #
# 搜索结果排序
1. 商品A: title="手机壳", description="适用于苹果手机iPhone系列..."
2. 商品B: title="苹果手机iPhone14", description="最新款智能手机"
3. 商品C: title="充电器", description="苹果手机专用快充充电器..."
使用权重 title^3
#
# 搜索结果排序
1. 商品B: title="苹果手机iPhone14", description="最新款智能手机"
2. 商品D: title="苹果手机iPhone13", description="经典款智能手机"
3. 商品A: title="手机壳", description="适用于苹果手机iPhone系列..."
效果:标题直接包含"苹果手机"的商品排名显著提升,用户体验更好。
总结 #
字段权重 ^数字 的作用:
- 提升重要字段的影响力:让关键字段在评分中占更大比重
- 优化搜索相关性:确保最相关的结果排在前面
- 反映业务逻辑:根据业务重要性合理分配权重
- 改善用户体验:让用户更容易找到想要的内容
设置建议:
- 根据业务重要性设置权重
- 权重范围控制在 0.1-10 之间较为合理
- 通过 A/B 测试验证权重效果
- 定期根据用户反馈调整权重配置
XIUANGH: # 品牌和价格区间统计 GET /_search { “size”: 0, “aggs”: { “brands”: { “terms”: { “field”: “brand” } }, “price_ranges”: { “range”: { “field”: “price”, “ranges”: [ { “to”: 5000 }, { “from”: 5000, “to”: 10000 }, { “from”: 10000 } ] } } } } 这个聚合分析的最终结果是什么,请你详细分析一下
GitHub Copilot: # Elasticsearch 聚合分析详细解析
查询分析 #
这个查询包含了两个并行的聚合,它们是独立执行的,不是嵌套关系。
查询结构分解 #
{
"size": 0, # 不返回具体文档,只返回聚合结果
"aggs": {
"brands": { # 第一个聚合:品牌分组统计
"terms": {
"field": "brand"
}
},
"price_ranges": { # 第二个聚合:价格区间统计
"range": {
"field": "price",
"ranges": [...]
}
}
}
}
聚合执行逻辑 #
1. brands 聚合(terms 分组)
#
- 作用:按照
brand字段的不同值进行分组 - 结果:统计每个品牌下有多少个文档
- 等价SQL:
SELECT brand, COUNT(*) FROM products GROUP BY brand
2. price_ranges 聚合(range 分组)
#
- 作用:按照价格区间对所有文档进行分组
- 区间定义:
- 第一组:价格 < 5000
- 第二组:5000 ≤ 价格 < 10000
- 第三组:价格 ≥ 10000
返回结果示例 #
假设索引中有以下商品数据:
文档1: {"name": "iPhone 14", "brand": "Apple", "price": 7999}
文档2: {"name": "Galaxy S23", "brand": "Samsung", "price": 6999}
文档3: {"name": "MacBook Pro", "brand": "Apple", "price": 12999}
文档4: {"name": "小米13", "brand": "Xiaomi", "price": 3999}
文档5: {"name": "华为P60", "brand": "Huawei", "price": 5999}
文档6: {"name": "iPad Pro", "brand": "Apple", "price": 8999}
完整返回结果 #
{
"took": 15,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 6,
"relation": "eq"
},
"max_score": null,
"hits": [] // size: 0,不返回具体文档
},
"aggregations": {
"brands": { // 品牌统计结果
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Apple",
"doc_count": 3 // Apple 品牌有 3 个商品
},
{
"key": "Samsung",
"doc_count": 1 // Samsung 品牌有 1 个商品
},
{
"key": "Xiaomi",
"doc_count": 1 // Xiaomi 品牌有 1 个商品
},
{
"key": "Huawei",
"doc_count": 1 // Huawei 品牌有 1 个商品
}
]
},
"price_ranges": { // 价格区间统计结果
"buckets": [
{
"key": "*-5000.0",
"to": 5000.0,
"doc_count": 1 // 价格 < 5000 的商品:1个(小米13)
},
{
"key": "5000.0-10000.0",
"from": 5000.0,
"to": 10000.0,
"doc_count": 4 // 5000 ≤ 价格 < 10000 的商品:4个
},
{
"key": "10000.0-*",
"from": 10000.0,
"doc_count": 1 // 价格 ≥ 10000 的商品:1个(MacBook Pro)
}
]
}
}
}
结果详细分析 #
品牌统计分析 (brands 聚合)
#
Apple: 3个商品 (iPhone 14, MacBook Pro, iPad Pro)
Samsung: 1个商品 (Galaxy S23)
Xiaomi: 1个商品 (小米13)
Huawei: 1个商品 (华为P60)
业务价值:
- 了解每个品牌在商品库中的占比
- 识别主要品牌和次要品牌
- 为品牌运营策略提供数据支持
价格区间分析 (price_ranges 聚合)
#
低价位 (< 5000): 1个商品 (16.7%)
中价位 (5000-10000): 4个商品 (66.7%)
高价位 (≥ 10000): 1个商品 (16.7%)
业务价值:
- 了解商品价格分布情况
- 识别主要价格区间
- 为定价策略提供参考
实际应用场景 #
1. 电商平台商品分析 #
# 这个查询可以回答以下问题:
1. 哪些品牌的商品最多?
2. 商品主要集中在哪个价格区间?
3. 是否需要增加某个价格区间的商品?
4. 品牌分布是否均衡?
2. 库存管理决策 #
# 基于结果可以做出的决策:
1. Apple 商品占比最高,可以加大采购力度
2. 中价位商品最受欢迎,可以重点推广
3. 高价位和低价位商品较少,考虑扩充产品线
3. 营销策略制定 #
# 营销策略建议:
1. 针对 Apple 等主力品牌制定专门的营销活动
2. 中价位商品可以作为主推商品
3. 考虑引入更多低价位商品满足不同消费群体
查询优化建议 #
1. 添加查询条件 #
GET /products/_search
{
"size": 0,
"query": {
"bool": {
"filter": [
{"term": {"status": "active"}}, // 只统计上架商品
{"range": {"created_at": {"gte": "2025-01-01"}}} // 只统计今年的商品
]
}
},
"aggs": {
"brands": {
"terms": {
"field": "brand.keyword",
"size": 20 // 增加返回品牌数量
}
},
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{"to": 5000, "key": "经济型"},
{"from": 5000, "to": 10000, "key": "中端"},
{"from": 10000, "key": "高端"}
]
}
}
}
}
2. 添加子聚合 #
{
"aggs": {
"brands": {
"terms": {
"field": "brand.keyword"
},
"aggs": {
"avg_price": { // 每个品牌的平均价格
"avg": {
"field": "price"
}
},
"price_stats": { // 每个品牌的价格统计
"stats": {
"field": "price"
}
}
}
}
}
}
总结 #
这个聚合查询的最终结果是:
- 品牌维度统计:展示每个品牌有多少个商品,了解品牌分布情况
- 价格维度统计:展示不同价格区间的商品数量,了解价格分布情况
两个聚合是独立并行执行的,不存在嵌套关系,因此结果也是独立的两组统计数据。
这种查询模式在电商、零售等场景中非常常见,可以为业务决策提供重要的数据支持,帮助了解商品结构、制定采购策略、优化商品组合等。
找到具有 1 个许可证类型的类似代码
XIUANGH: 1. 不需要搜索的字段设置为 keyword
{ “id”: { “type”: “keyword”, “index”: false }, “status”: { “type”: “keyword” } }
那如果我想要搜索这个字段,其搜索效率高吗
使用过滤器而非查询 #
# 使用 filter(更快,有缓存) { "query": { "bool": { "filter": [ { "term": { "status": "active" } } ] } } }为什么使用过滤器更快,一般的查询是什么样子的?
GitHub Copilot: # Elasticsearch 搜索效率详解
1. keyword 字段搜索效率分析 #
index: false 的影响 #
{
"id": { "type": "keyword", "index": false }, # 不可搜索
"status": { "type": "keyword" } # 可搜索
}
index: false 的字段无法搜索 #
# ❌ 这个查询会失败
GET /my_index/_search
{
"query": {
"term": {
"id": "user123" # id字段设置了 "index": false
}
}
}
# 错误信息
{
"error": {
"type": "query_shard_exception",
"reason": "Cannot search on field [id] since it is not indexed."
}
}
可搜索的 keyword 字段效率很高 #
# ✅ 这个查询效率很高
GET /my_index/_search
{
"query": {
"term": {
"status": "active" # keyword类型,精确匹配
}
}
}
keyword 字段搜索性能特点 #
1. 倒排索引结构 #
# keyword 字段的倒排索引
status字段:
├── "active" → [doc1, doc3, doc5, doc8, ...]
├── "inactive" → [doc2, doc4, doc6, ...]
├── "pending" → [doc7, doc9, ...]
└── "deleted" → [doc10, doc11, ...]
2. 搜索过程 #
# term 查询执行步骤
1. 直接在倒排索引中查找 "active"
2. 获取包含该词的文档ID列表
3. 返回匹配的文档
# 时间复杂度:O(1) - 哈希表查找
3. 性能对比测试 #
# 假设100万文档的索引
# keyword 精确匹配(极快)
GET /users/_search
{
"query": {
"term": {"status.keyword": "active"}
}
}
# 平均响应时间:1-5ms
# text 全文搜索(较慢)
GET /users/_search
{
"query": {
"match": {"description": "active user"}
}
}
# 平均响应时间:10-50ms
keyword 搜索优化建议 #
1. 正确的映射配置 #
PUT /users
{
"mappings": {
"properties": {
"id": {
"type": "keyword",
"index": true, # 如果需要搜索就设为true
"doc_values": true # 支持聚合和排序
},
"status": {
"type": "keyword",
"index": true
},
"internal_id": {
"type": "keyword",
"index": false, # 仅用于存储,不搜索
"doc_values": false # 不参与聚合
}
}
}
}
2. 高效的查询方式 #
# ✅ 推荐:使用 term 查询
{
"query": {
"term": {"status": "active"}
}
}
# ✅ 推荐:多值查询
{
"query": {
"terms": {"status": ["active", "pending"]}
}
}
# ❌ 不推荐:对 keyword 使用 match
{
"query": {
"match": {"status": "active"} # 没必要,term更快
}
}
2. filter vs query 性能差异详解 #
为什么 filter 更快? #
1. 评分计算差异 #
# filter(不计算评分)
{
"query": {
"bool": {
"filter": [
{"term": {"status": "active"}}
]
}
}
}
# 执行:二进制判断(匹配/不匹配)
# 结果:所有匹配文档的 _score = 0.0
# query(计算相关性评分)
{
"query": {
"term": {"status": "active"}
}
}
# 执行:计算TF-IDF评分
# 结果:每个文档都有不同的 _score
2. 缓存机制 #
# filter 查询会被缓存
{
"query": {
"bool": {
"filter": [
{"term": {"status": "active"}}, # 缓存这个结果
{"range": {"age": {"gte": 18}}} # 缓存这个结果
]
}
}
}
# 缓存存储格式(BitSet)
status="active" → 010101010111... # 1表示匹配,0表示不匹配
age>=18 → 111101111110...
3. 执行顺序优化 #
# ES 内部优化执行顺序
{
"query": {
"bool": {
"must": [
{"match": {"content": "elasticsearch"}} # 2. 在小范围内计算评分
],
"filter": [
{"term": {"status": "active"}}, # 1. 先用filter快速过滤
{"range": {"created_at": {"gte": "2025-01-01"}}}
]
}
}
}
性能对比实测 #
测试环境 #
# 数据量:100万文档
# 查询:status = "active"(约50万匹配文档)
性能测试结果 #
# 1. 纯 filter 查询
{
"query": {
"bool": {
"filter": [{"term": {"status": "active"}}]
}
}
}
# 首次查询:15ms
# 缓存后:2-3ms
# 2. 普通 query 查询
{
"query": {
"term": {"status": "active"}
}
}
# 每次查询:8-12ms(需要计算评分)
# 3. 复杂 query 查询
{
"query": {
"bool": {
"must": [
{"term": {"status": "active"}},
{"range": {"age": {"gte": 18}}}
]
}
}
}
# 每次查询:25-40ms
一般查询 vs 过滤器查询 #
一般查询(Query Context) #
# 特点:计算相关性评分
GET /articles/_search
{
"query": {
"bool": {
"must": [
{"match": {"title": "elasticsearch"}}, # 评分查询
{"match": {"content": "tutorial"}}, # 评分查询
{"term": {"status": "published"}} # 评分查询(不合理)
]
}
}
}
# 执行过程
1. 对每个 must 子句计算相关性评分
2. 合并评分:score = title_score + content_score + status_score
3. 按评分排序返回结果
# 性能特点
- 需要计算 TF-IDF 评分
- 不能被缓存
- 适合全文搜索场景
过滤器查询(Filter Context) #
# 特点:二进制判断,无评分计算
GET /articles/_search
{
"query": {
"bool": {
"must": [
{"match": {"title": "elasticsearch"}}, # 评分查询
{"match": {"content": "tutorial"}} # 评分查询
],
"filter": [
{"term": {"status": "published"}}, # 过滤条件
{"range": {"publish_date": {"gte": "2025-01-01"}}}
]
}
}
}
# 执行过程
1. 先执行 filter 快速过滤文档
2. 在过滤后的小范围内计算 must 查询的评分
3. 按评分排序返回结果
# 性能特点
- Filter 部分可被缓存
- 二进制判断,速度快
- 适合精确条件过滤
最佳实践示例 #
电商搜索优化 #
# ❌ 不好的做法:全部用 query
{
"query": {
"bool": {
"must": [
{"multi_match": {"query": "手机", "fields": ["title", "description"]}},
{"term": {"category": "电子产品"}}, # 应该用filter
{"term": {"status": "available"}}, # 应该用filter
{"range": {"price": {"gte": 1000}}} # 应该用filter
]
}
}
}
# ✅ 好的做法:合理分离 query 和 filter
{
"query": {
"bool": {
"must": [
{"multi_match": { # 需要评分的搜索
"query": "手机",
"fields": ["title^3", "description"]
}}
],
"filter": [ # 精确过滤条件
{"term": {"category": "电子产品"}},
{"term": {"status": "available"}},
{"range": {"price": {"gte": 1000, "lte": 5000}}},
{"term": {"in_stock": true}}
]
}
}
}
日志分析优化 #
# ✅ 日志搜索的正确姿势
{
"query": {
"bool": {
"must": [
{"match": {"message": "ERROR database"}} # 错误信息搜索
],
"filter": [ # 精确过滤
{"range": {"@timestamp": {"gte": "now-1h"}}},
{"term": {"service.keyword": "user-service"}},
{"term": {"environment.keyword": "production"}},
{"terms": {"level.keyword": ["ERROR", "FATAL"]}}
]
}
}
}
缓存机制详解 #
Filter 缓存工作原理 #
# 1. 首次执行 filter
GET /users/_search
{
"query": {
"bool": {
"filter": [{"term": {"status": "active"}}]
}
}
}
# ES 内部缓存(BitSet格式)
cache_key: "term:status:active"
cache_value: "0101010101..." # 每一位代表一个文档是否匹配
# 2. 后续相同 filter 查询
# 直接使用缓存的 BitSet,无需重新计算
缓存策略 #
# 自动缓存的条件
1. 查询在最近256次查询中出现过
2. 查询结果集不超过总文档数的1%
3. 查询不是脚本查询
# 手动控制缓存
{
"query": {
"bool": {
"filter": [
{
"term": {
"status": "active",
"_cache": true # 强制缓存
}
}
]
}
}
}
总结 #
keyword 字段搜索效率 #
- index: true 的 keyword 字段搜索效率极高(1-5ms)
- index: false 的字段无法搜索
- 使用
term查询比match查询更适合 keyword 字段
filter vs query 性能差异 #
Filter 更快的原因:
- 不计算相关性评分(二进制判断)
- 查询结果可以被缓存
- ES 会优先执行 filter 缩小搜索范围
最佳实践:
- 精确条件用
filter(状态、类别、时间范围) - 全文搜索用
query(标题、内容匹配) - 先 filter 后 query,让搜索在小范围内进行
- 精确条件用
性能提升:
- Filter 缓存后速度提升 5-10 倍
- 复合查询中合理使用 filter 可提升 2-5 倍性能
XIUANGH: 详细介绍一下Elasticsearch如何处理并发和锁机制
GitHub Copilot: # Elasticsearch 并发控制和锁机制详解
1. Elasticsearch 并发挑战 #
分布式环境下的并发问题 #
# 典型并发场景
场景1:多个客户端同时更新同一文档
客户端A:更新用户余额 1000 -> 900
客户端B:更新用户余额 1000 -> 800
最终结果:可能是900或800,而不是期望的700
场景2:读写并发
客户端A:读取库存数量 100
客户端B:同时减少库存 100 -> 99
客户端A:基于100进行业务逻辑处理(可能出错)
场景3:跨分片一致性
分片1:用户订单数据
分片2:商品库存数据
需要保证订单创建和库存扣减的一致性
2. 乐观锁机制 #
版本控制 (Version Control) #
内部版本控制 #
# 文档创建时自动分配版本号
PUT /users/_doc/1
{
"name": "张三",
"balance": 1000
}
# 响应包含版本信息
{
"_index": "users",
"_id": "1",
"_version": 1, # 初始版本
"result": "created"
}
基于版本的更新 #
# 客户端A:获取当前文档
GET /users/_doc/1
{
"_source": {"name": "张三", "balance": 1000},
"_version": 1
}
# 客户端A:基于版本1更新(扣款100)
PUT /users/_doc/1?version=1
{
"name": "张三",
"balance": 900
}
# 成功更新,版本变为2
# 客户端B:尝试基于版本1更新(扣款200)
PUT /users/_doc/1?version=1
{
"name": "张三",
"balance": 800
}
# 失败!版本冲突错误
{
"error": {
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, current version [2] is different than the one provided [1]"
}
}
外部版本控制 #
# 使用外部系统的版本号
PUT /users/_doc/1?version=1001&version_type=external
{
"name": "张三",
"balance": 1000,
"external_version": 1001
}
# 后续更新必须使用更大的版本号
PUT /users/_doc/1?version=1002&version_type=external
{
"name": "张三",
"balance": 900,
"external_version": 1002
}
序列号和主分片任期 (Sequence Numbers & Primary Terms) #
更精确的并发控制 #
# 获取文档的序列号信息
GET /users/_doc/1
{
"_seq_no": 5, # 序列号
"_primary_term": 1, # 主分片任期
"_version": 2,
"_source": {"name": "张三", "balance": 900}
}
# 基于序列号的条件更新
PUT /users/_doc/1?if_seq_no=5&if_primary_term=1
{
"name": "张三",
"balance": 800
}
序列号机制原理 #
// 每个操作都有全局唯一的序列号
操作1: seq_no=1, primary_term=1, 创建文档
操作2: seq_no=2, primary_term=1, 更新文档
操作3: seq_no=3, primary_term=1, 删除文档
操作4: seq_no=4, primary_term=2, 主分片故障转移后的操作
// 优势:比version更精确,支持跨分片的全局顺序
3. 文档级锁机制 #
写操作的锁定过程 #
单文档写入锁 #
// ES 内部文档锁实现(简化)
public class DocumentLock {
private final ConcurrentHashMap<String, ReentrantLock> locks = new ConcurrentHashMap<>();
public void lockDocument(String docId) {
ReentrantLock lock = locks.computeIfAbsent(docId, k -> new ReentrantLock());
lock.lock();
}
public void unlockDocument(String docId) {
ReentrantLock lock = locks.get(docId);
if (lock != null) {
lock.unlock();
}
}
}
写入操作流程 #
# 文档更新的内部流程
1. 客户端发送更新请求到协调节点
2. 协调节点确定目标分片(路由)
3. 请求转发到主分片节点
4. 主分片节点获取文档锁
5. 检查版本/序列号冲突
6. 执行更新操作
7. 将操作复制到副本分片
8. 释放文档锁
9. 返回结果给客户端
批量操作的锁处理 #
Bulk 操作的锁优化 #
POST /_bulk
{"update":{"_index":"users","_id":"1"}}
{"doc":{"balance":900}}
{"update":{"_index":"users","_id":"2"}}
{"doc":{"balance":800}}
{"update":{"_index":"users","_id":"1"}}
{"doc":{"last_update":"2025-06-14"}}
// 批量操作锁处理
public void processBulkRequest(List<Operation> operations) {
// 1. 按文档ID分组
Map<String, List<Operation>> groupedOps = groupByDocId(operations);
// 2. 对每个文档的操作串行化处理
for (Map.Entry<String, List<Operation>> entry : groupedOps.entrySet()) {
String docId = entry.getKey();
List<Operation> docOps = entry.getValue();
// 3. 获取文档锁
lockDocument(docId);
try {
// 4. 按顺序执行该文档的所有操作
for (Operation op : docOps) {
executeOperation(op);
}
} finally {
// 5. 释放锁
unlockDocument(docId);
}
}
}
4. 分片级并发控制 #
主副分片一致性 #
写入复制流程 #
# 主分片写入流程
1. 客户端请求 -> 协调节点
2. 协调节点 -> 主分片(P0)
3. 主分片执行写入操作
4. 主分片并行复制到副本分片(R0_1, R0_2)
5. 等待副本确认(可配置策略)
6. 返回成功响应
# 一致性策略配置
PUT /my_index/_settings
{
"index.write.wait_for_active_shards": "all" # 等待所有副本
}
分片锁机制 #
// 分片级别的读写锁
public class ShardLock {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读操作获取读锁(可并发)
public void acquireReadLock() {
rwLock.readLock().lock();
}
// 写操作获取写锁(独占)
public void acquireWriteLock() {
rwLock.writeLock().lock();
}
// 分片操作(如合并、恢复)需要独占锁
public void acquireShardOperationLock() {
rwLock.writeLock().lock();
}
}
分布式锁场景 #
集群状态变更锁 #
# 集群级别的操作需要分布式锁
操作类型:
1. 创建/删除索引
2. 分片分配调整
3. 集群设置变更
4. 节点加入/离开
# 主节点协调机制
主节点负责:
- 集群状态管理
- 分片分配决策
- 索引生命周期管理
- 协调分布式操作
5. 并发控制策略 #
读写分离优化 #
副本分片读取负载均衡 #
# 配置读取偏好
GET /users/_search?preference=_local
{
"query": {"match_all": {}}
}
# 读取偏好选项
- _primary:只从主分片读取
- _local:优先从本地节点读取
- _prefer_nodes:偏好指定节点
- _shards:指定分片读取
读写性能优化 #
// 读写分离的性能特点
读操作(可以从任意副本读取):
├── 主分片读取:一致性最强,性能一般
├── 副本分片读取:性能最佳,可能有轻微延迟
└── 就近读取:延迟最低
写操作(必须经过主分片):
├── 主分片写入:串行化执行
├── 副本复制:并行执行
└── 确认策略:可配置等待级别
冲突处理策略 #
自动重试机制 #
# 版本冲突自动重试
POST /users/_update/1?retry_on_conflict=3
{
"script": {
"source": "ctx._source.balance -= params.amount",
"params": {
"amount": 100
}
}
}
# 重试逻辑
1. 尝试更新操作
2. 如果版本冲突,重新获取最新版本
3. 基于最新版本重新执行操作
4. 最多重试3次
应用层冲突处理 #
// 应用层重试逻辑
public void updateUserBalance(String userId, double amount) {
int maxRetries = 5;
int retryCount = 0;
while (retryCount < maxRetries) {
try {
// 1. 获取当前文档
GetResponse response = client.get(userId);
long version = response.getVersion();
// 2. 计算新值
UserDocument user = parseUser(response.getSourceAsString());
user.setBalance(user.getBalance() - amount);
// 3. 条件更新
UpdateRequest request = new UpdateRequest("users", userId)
.version(version)
.doc(toJson(user));
client.update(request);
return; // 成功,退出重试循环
} catch (VersionConflictEngineException e) {
retryCount++;
// 指数退避
Thread.sleep(100 * (1 << retryCount));
}
}
throw new RuntimeException("Update failed after " + maxRetries + " retries");
}
6. 性能优化和最佳实践 #
减少锁竞争 #
文档设计优化 #
# ❌ 高竞争设计:全局计数器
{
"_id": "global_counter",
"total_visits": 1000000,
"daily_visits": 5000
}
# 问题:所有更新都竞争同一个文档
# ✅ 低竞争设计:分片计数器
{
"_id": "counter_shard_0",
"visits": 200000
}
{
"_id": "counter_shard_1",
"visits": 180000
}
# 优势:更新分散到多个文档,减少竞争
批量操作优化 #
# ✅ 批量更新减少锁开销
POST /_bulk
{"update":{"_index":"users","_id":"1"}}
{"doc":{"balance":900}}
{"update":{"_index":"users","_id":"2"}}
{"doc":{"balance":800}}
{"update":{"_index":"users","_id":"3"}}
{"doc":{"balance":700}}
# 比单独更新3次效率高得多
写入性能调优 #
刷新策略优化 #
# 减少刷新频率,提高写入性能
PUT /high_write_index/_settings
{
"refresh_interval": "30s", # 默认1s,调大减少开销
"number_of_replicas": 0 # 写入期间可以先不要副本
}
# 写入完成后恢复设置
PUT /high_write_index/_settings
{
"refresh_interval": "1s",
"number_of_replicas": 1
}
异步写入模式 #
# 异步写入,不等待副本确认
PUT /my_index/_settings
{
"index.write.wait_for_active_shards": 1 # 只等待主分片
}
# 同步写入,等待所有副本确认(更安全但更慢)
PUT /my_index/_settings
{
"index.write.wait_for_active_shards": "all"
}
7. 实际应用场景 #
电商库存管理 #
库存扣减的并发控制 #
# 悲观锁方式(应用层实现)
1. 获取商品当前库存和版本
GET /products/_doc/iphone14
{
"_version": 5,
"_source": {"stock": 100}
}
2. 检查库存是否充足
if (stock >= quantity) {
// 可以下单
}
3. 使用版本控制更新库存
PUT /products/_doc/iphone14?version=5
{
"stock": 99 # 减少1个
}
4. 处理版本冲突
if (version_conflict) {
// 重新获取最新库存,重试
}
乐观锁实现库存控制 #
public class InventoryService {
public boolean decreaseStock(String productId, int quantity) {
int maxRetries = 10;
for (int i = 0; i < maxRetries; i++) {
try {
// 1. 获取当前库存
GetResponse response = esClient.get(productId);
Product product = parseProduct(response);
// 2. 检查库存
if (product.getStock() < quantity) {
return false; // 库存不足
}
// 3. 扣减库存
product.setStock(product.getStock() - quantity);
// 4. 条件更新
UpdateRequest request = new UpdateRequest("products", productId)
.version(response.getVersion())
.doc(toJson(product));
esClient.update(request);
return true; // 成功
} catch (VersionConflictEngineException e) {
// 版本冲突,重试
continue;
}
}
return false; // 重试次数用完,失败
}
}
用户积分系统 #
积分更新的原子性 #
# 使用脚本保证原子性
POST /users/_update/user123?retry_on_conflict=5
{
"script": {
"source": """
// 检查积分是否足够
if (ctx._source.points >= params.cost) {
ctx._source.points -= params.cost;
ctx._source.last_purchase = params.timestamp;
} else {
// 积分不足,取消操作
ctx.op = 'noop';
}
""",
"params": {
"cost": 1000,
"timestamp": "2025-06-14T10:30:00"
}
}
}
# 检查操作结果
{
"_index": "users",
"_id": "user123",
"_version": 6,
"result": "updated" // 或 "noop"
}
8. 监控和调试 #
并发冲突监控 #
查看版本冲突统计 #
# 查看索引统计信息
GET /users/_stats/indexing
{
"indices": {
"users": {
"total": {
"indexing": {
"version_conflicts": 156, # 版本冲突次数
"throttle_time_in_millis": 0
}
}
}
}
}
监控热点文档 #
# 查看最频繁更新的文档
GET /_nodes/hot_threads?type=wait
# 自定义监控脚本
GET /users/_search
{
"size": 0,
"aggs": {
"top_versions": {
"terms": {
"script": "doc['_version'].value",
"size": 10
}
}
}
}
性能调优指标 #
关键性能指标 #
# 写入性能指标
- 索引速率:docs/second
- 版本冲突率:conflicts/total_operations
- 平均写入延迟:average_latency
- 队列积压:pending_operations
# 并发控制指标
- 锁等待时间:lock_wait_time
- 重试成功率:retry_success_rate
- 分片热度分布:shard_load_distribution
总结 #
Elasticsearch 的并发控制采用了多层次的设计:
1. 乐观锁为主 #
- 版本控制(version)
- 序列号控制(seq_no + primary_term)
- 自动重试机制
2. 文档级锁定 #
- 单文档操作的原子性保证
- 批量操作的顺序化处理
- 避免数据竞争
3. 分片级协调 #
- 主副分片一致性
- 读写分离优化
- 分布式锁协调
4. 应用层配合 #
- 合理的重试策略
- 冲突处理机制
- 性能优化配置
这种设计在保证数据一致性的同时,最大化了系统的并发性能,是分布式系统并发控制的经典实现。
找到具有 4 个许可证类型的类似代码
XIUANGH: 详细介绍一下 Elasticsearch 的索引生命周期管理 (ILM)
GitHub Copilot: # Elasticsearch 索引生命周期管理 (ILM) 详解
1. ILM 概念与背景 #
什么是索引生命周期管理? #
ILM (Index Lifecycle Management) 是 Elasticsearch 的一个重要特性,它可以自动管理索引在不同阶段的行为,包括索引的创建、优化、归档和删除等操作。
为什么需要 ILM? #
# 传统痛点场景
日志系统:
├── 每天产生 GB 级别的日志数据
├── 新数据需要快速写入和搜索(热数据)
├── 历史数据搜索频率低但需要保留(温数据)
├── 过期数据需要删除以节省存储(冷数据)
└── 手动管理成本高、容易出错
时间序列数据:
├── IoT 传感器数据持续写入
├── 实时监控需要访问最新数据
├── 历史数据用于趋势分析
├── 存储成本随时间线性增长
└── 需要自动化的数据生命周期管理
2. ILM 生命周期阶段 #
四个标准阶段 #
Hot Phase (热阶段)
├── 数据正在被频繁写入和查询
├── 需要高性能的存储和计算资源
├── 通常在 SSD 存储的高性能节点
└── 支持实时搜索和聚合
Warm Phase (温阶段)
├── 数据不再写入,但仍需要查询
├── 查询频率相对较低
├── 可以使用较便宜的存储
└── 可以减少副本数量节省资源
Cold Phase (冷阶段)
├── 数据很少被查询
├── 查询延迟要求不高
├── 使用最便宜的存储
└── 通常只保留少量副本
Delete Phase (删除阶段)
├── 数据不再需要
├── 自动删除索引
├── 释放存储空间
└── 符合数据保留政策
3. ILM 策略配置 #
创建 ILM 策略 #
基础策略示例 #
PUT /_ilm/policy/my-log-policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "5GB",
"max_age": "1d",
"max_docs": 10000000
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": {
"number_of_shards": 1
},
"allocate": {
"number_of_replicas": 0
},
"set_priority": {
"priority": 50
}
}
},
"cold": {
"min_age": "30d",
"actions": {
"allocate": {
"number_of_replicas": 0,
"require": {
"box_type": "cold"
}
},
"set_priority": {
"priority": 0
}
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}
策略解析 #
Hot 阶段 (立即生效):
├── rollover:当索引满足以下任一条件时滚动
│ ├── 大小超过 5GB
│ ├── 年龄超过 1 天
│ └── 文档数超过 1000万
├── set_priority:设置索引优先级为 100(最高)
└── 用途:保证写入性能和实时搜索
Warm 阶段 (7天后):
├── shrink:将分片数缩减到 1 个
├── allocate:副本数设为 0
├── set_priority:优先级降到 50
└── 用途:节省资源,保持一定查询性能
Cold 阶段 (30天后):
├── allocate:分配到冷存储节点
├── require:要求节点具有 box_type=cold 属性
├── set_priority:优先级降到 0
└── 用途:最大化节省成本
Delete 阶段 (90天后):
├── delete:删除整个索引
└── 用途:释放存储空间,符合保留政策
高级策略配置 #
复杂业务场景策略 #
PUT /_ilm/policy/ecommerce-policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "10GB",
"max_age": "1d"
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "3d",
"actions": {
"readonly": {}, # 设为只读
"shrink": {
"number_of_shards": 1
},
"forcemerge": { # 强制合并段
"max_num_segments": 1
},
"allocate": {
"number_of_replicas": 1,
"require": {
"data": "warm"
}
}
}
},
"cold": {
"min_age": "14d",
"actions": {
"searchable_snapshot": { # 可搜索快照
"snapshot_repository": "my-repository"
}
}
},
"delete": {
"min_age": "365d",
"actions": {
"wait_for_snapshot": { # 删除前等待快照完成
"policy": "daily-snapshots"
},
"delete": {}
}
}
}
}
}
4. 索引模板与 ILM 集成 #
创建索引模板 #
PUT /_index_template/logs-template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"index.lifecycle.name": "my-log-policy", # 关联 ILM 策略
"index.lifecycle.rollover_alias": "logs" # 滚动别名
},
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
},
"message": {
"type": "text",
"analyzer": "standard"
},
"level": {
"type": "keyword"
},
"service": {
"type": "keyword"
}
}
}
},
"composed_of": [],
"priority": 200
}
引导索引 (Bootstrap Index) #
# 创建初始索引并设置别名
PUT /logs-000001
{
"aliases": {
"logs": {
"is_write_index": true # 设为写入索引
}
}
}
# 后续写入都通过别名
POST /logs/_doc
{
"@timestamp": "2025-06-14T10:30:00",
"message": "Application started",
"level": "INFO",
"service": "user-service"
}
5. ILM 操作详解 #
Rollover 操作 #
工作原理 #
# Rollover 触发条件(任一满足即可)
条件1:索引大小 >= max_size
条件2:索引年龄 >= max_age
条件3:文档数量 >= max_docs
# Rollover 执行过程
1. 检查当前写入索引 (logs-000001)
2. 评估是否满足 rollover 条件
3. 如果满足,创建新索引 (logs-000002)
4. 更新别名:logs-000001 变为只读,logs-000002 成为写入索引
5. 新数据写入到 logs-000002
手动执行 Rollover #
# 手动触发 rollover
POST /logs/_rollover
{
"conditions": {
"max_age": "7d",
"max_docs": 1000,
"max_size": "5GB"
}
}
# 返回结果
{
"acknowledged": true,
"shards_acknowledged": true,
"old_index": "logs-000001",
"new_index": "logs-000002",
"rolled_over": true,
"dry_run": false,
"conditions": {
"[max_size: 5gb]": false,
"[max_age: 7d]": true, # 年龄条件满足
"[max_docs: 1000]": false
}
}
Shrink 操作 #
分片缩减原理 #
# 原始索引:5个分片
logs-000001:
├── shard 0 (1GB)
├── shard 1 (1GB)
├── shard 2 (1GB)
├── shard 3 (1GB)
└── shard 4 (1GB)
# Shrink 后:1个分片
logs-000001-shrunk:
└── shard 0 (5GB) # 合并所有数据
自动 Shrink 配置 #
{
"warm": {
"min_age": "7d",
"actions": {
"shrink": {
"number_of_shards": 1,
"allow_write_after_shrink": false # 缩减后禁止写入
}
}
}
}
Forcemerge 操作 #
段合并优化 #
# 段合并前
索引包含多个段:
├── segment_1.si (100MB, 1000 docs)
├── segment_2.si (80MB, 800 docs)
├── segment_3.si (120MB, 1200 docs)
└── segment_4.si (50MB, 500 docs)
# Forcemerge 后
索引包含一个段:
└── segment_merged.si (350MB, 3500 docs)
# 优势:
- 减少文件句柄数量
- 提高查询性能
- 释放被标记删除的空间
6. 实际应用场景 #
日志管理系统 #
完整的日志 ILM 策略 #
PUT /_ilm/policy/log-management-policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "2GB", # 日志文件不要太大
"max_age": "1d" # 每天滚动一次
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "1d", # 1天后进入温阶段
"actions": {
"readonly": {},
"shrink": {
"number_of_shards": 1
},
"forcemerge": {
"max_num_segments": 1 # 合并为单个段
},
"allocate": {
"number_of_replicas": 0, # 减少副本
"require": {
"data": "warm"
}
}
}
},
"cold": {
"min_age": "7d", # 7天后进入冷阶段
"actions": {
"allocate": {
"number_of_replicas": 0,
"require": {
"data": "cold"
}
}
}
},
"delete": {
"min_age": "30d", # 30天后删除
"actions": {
"delete": {}
}
}
}
}
}
配套的索引模板 #
PUT /_index_template/application-logs
{
"index_patterns": ["app-logs-*"],
"template": {
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1,
"index.lifecycle.name": "log-management-policy",
"index.lifecycle.rollover_alias": "app-logs",
"index.codec": "best_compression", # 压缩存储
"index.refresh_interval": "5s" # 降低刷新频率
},
"mappings": {
"properties": {
"@timestamp": {"type": "date"},
"level": {"type": "keyword"},
"logger": {"type": "keyword"},
"message": {
"type": "text",
"analyzer": "standard",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"thread": {"type": "keyword"},
"exception": {
"type": "text",
"analyzer": "standard"
}
}
}
}
}
指标监控系统 #
监控数据 ILM 策略 #
PUT /_ilm/policy/metrics-policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "5GB",
"max_age": "6h" # 指标数据6小时滚动
}
}
},
"warm": {
"min_age": "6h",
"actions": {
"readonly": {},
"shrink": {
"number_of_shards": 1
},
"forcemerge": {
"max_num_segments": 1
}
}
},
"cold": {
"min_age": "7d",
"actions": {
"searchable_snapshot": { # 转为可搜索快照
"snapshot_repository": "metrics-repo"
}
}
},
"delete": {
"min_age": "90d", # 保留3个月
"actions": {
"delete": {}
}
}
}
}
}
电商订单数据 #
订单数据 ILM 策略 #
PUT /_ilm/policy/order-policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "10GB",
"max_age": "30d" # 按月滚动
}
}
},
"warm": {
"min_age": "30d", # 当月订单保持热状态
"actions": {
"readonly": {},
"allocate": {
"number_of_replicas": 1 # 保持1个副本备份
}
}
},
"cold": {
"min_age": "180d", # 6个月后冷存储
"actions": {
"allocate": {
"require": {
"data": "cold"
}
}
}
},
"delete": {
"min_age": "2555d", # 7年后删除(法规要求)
"actions": {
"wait_for_snapshot": {
"policy": "order-backup"
},
"delete": {}
}
}
}
}
}
7. ILM 监控和管理 #
查看 ILM 状态 #
策略状态检查 #
# 查看所有 ILM 策略
GET /_ilm/policy
# 查看特定策略
GET /_ilm/policy/my-log-policy
# 查看索引的 ILM 状态
GET /logs-*/_ilm/explain
# 返回示例
{
"indices": {
"logs-000001": {
"index": "logs-000001",
"managed": true,
"policy": "my-log-policy",
"lifecycle_date_millis": 1718347800000,
"age": "2d",
"phase": "warm",
"phase_time_millis": 1718434200000,
"action": "shrink",
"action_time_millis": 1718434200000,
"step": "shrink",
"step_time_millis": 1718434200000
}
}
}
ILM 操作历史 #
# 查看 ILM 操作历史
GET /_ilm/status
# 查看错误信息
GET /logs-*/_ilm/explain?only_errors=true
# 手动重试失败的操作
POST /logs-000001/_ilm/retry
ILM 故障排查 #
常见问题和解决方案 #
# 问题1:Rollover 条件未满足
{
"error": {
"type": "illegal_argument_exception",
"reason": "Cannot rollover alias [logs] because it does not point to a write index"
}
}
# 解决方案:检查并设置写入索引
PUT /logs/_alias/logs
{
"is_write_index": true
}
# 问题2:Shrink 操作失败
{
"error": {
"reason": "Cannot shrink index [logs-000001] because it is not on a single node"
}
}
# 解决方案:将索引分配到单个节点
PUT /logs-000001/_settings
{
"index.routing.allocation.require._name": "node-1",
"index.blocks.write": true
}
# 问题3:Cold 阶段分配失败
{
"error": {
"reason": "Could not find any nodes with tag [data:cold]"
}
}
# 解决方案:检查节点标签配置
GET /_cat/nodeattrs?v&h=node,attr,value
性能监控 #
ILM 性能指标 #
# 监控关键指标
GET /_cluster/stats
# ILM 相关指标
{
"indices": {
"lifecycle": {
"policy_count": 5, # 策略数量
"indices_managed": 120, # 被管理的索引数
"phases": {
"hot": {"indices": 10},
"warm": {"indices": 45},
"cold": {"indices": 60},
"delete": {"indices": 5}
}
}
}
}
# 监控脚本示例
#!/bin/bash
# 检查ILM健康状态
curl -s "localhost:9200/_ilm/status" | jq '.operation_mode'
curl -s "localhost:9200/logs-*/_ilm/explain?only_errors=true" | jq '.indices | length'
8. 最佳实践和优化建议 #
策略设计原则 #
1. 基于业务需求设计 #
# 数据访问模式分析
实时分析需求:
├── 最近1天:高频查询,需要快速响应
├── 1-7天:中频查询,可接受一定延迟
├── 7-30天:低频查询,延迟容忍度高
└── 30天+:归档查询,偶尔访问
# 对应的ILM设计
Hot: 0-1天, 高性能SSD, 多副本
Warm: 1-7天, 标准存储, 少副本
Cold: 7-30天, 便宜存储, 可搜索快照
Delete: 30天+, 删除或长期归档
2. 资源成本优化 #
# 成本优化策略
存储成本:
├── Hot阶段:高性能存储(贵)
├── Warm阶段:标准存储(中等)
├── Cold阶段:廉价存储(便宜)
└── 快照存储:对象存储(最便宜)
计算成本:
├── 减少副本数量
├── 合并分片减少开销
├── 降低查询优先级
└── 使用冷数据节点
配置优化建议 #
1. Rollover 参数调优 #
# 避免分片过大或过小
{
"rollover": {
"max_size": "5GB", # 根据硬件性能调整
"max_age": "1d", # 根据数据增长速度调整
"max_docs": 100000000 # 避免单个分片文档过多
}
}
# 分片大小建议
- 小型集群:1-5GB per shard
- 大型集群:10-50GB per shard
- 避免:100GB+ per shard
2. 阶段转换时间优化 #
# 根据查询模式调整
{
"phases": {
"hot": {...},
"warm": {
"min_age": "1d", # 实时查询需求:缩短
// "min_age": "1h", # 高实时性要求
// "min_age": "7d", # 低实时性要求
},
"cold": {
"min_age": "30d" # 根据历史查询频率调整
}
}
}
3. 节点标签和分配策略 #
# 节点配置示例
# Hot节点(elasticsearch.yml)
node.attr.data: hot
node.attr.box_type: hot
# Warm节点
node.attr.data: warm
node.attr.box_type: warm
# Cold节点
node.attr.data: cold
node.attr.box_type: cold
# ILM中的对应配置
{
"allocate": {
"require": {
"data": "cold",
"box_type": "cold"
}
}
}
9. 与其他特性的集成 #
快照生命周期管理 (SLM) #
SLM 与 ILM 配合 #
# 创建快照策略
PUT /_slm/policy/daily-backup
{
"schedule": "0 1 * * *", # 每天凌晨1点
"name": "<logs-{now/d}>",
"repository": "backup-repo",
"config": {
"indices": ["logs-*"],
"ignore_unavailable": true,
"include_global_state": false
},
"retention": {
"expire_after": "30d", # 快照保留30天
"min_count": 5, # 最少保留5个快照
"max_count": 50 # 最多保留50个快照
}
}
# ILM中引用快照策略
{
"delete": {
"min_age": "30d",
"actions": {
"wait_for_snapshot": {
"policy": "daily-backup" # 等待快照完成再删除
},
"delete": {}
}
}
}
可搜索快照 (Searchable Snapshots) #
冷数据的可搜索快照 #
# 在ILM中配置可搜索快照
{
"cold": {
"min_age": "30d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": "s3-repo",
"force_merge_index": true # 合并后再做快照
}
}
}
}
# 查询可搜索快照索引
GET /restored-logs-*/_search
{
"query": {
"range": {
"@timestamp": {
"gte": "2025-01-01",
"lte": "2025-01-31"
}
}
}
}
总结 #
Elasticsearch ILM 是一个强大的数据生命周期管理工具:
核心优势 #
- 自动化管理:减少人工干预,降低运维成本
- 成本优化:合理分配资源,最大化存储效率
- 性能保证:热数据快速访问,冷数据成本优化
- 政策合规:自动数据保留和删除
最佳实践 #
- 策略设计:基于业务访问模式设计阶段转换
- 资源规划:合理配置不同类型的存储节点
- 监控告警:及时发现和处理ILM操作异常
- 测试验证:在生产环境前充分测试策略效果
适用场景 #
- 日志管理系统
- 监控指标存储
- 时间序列数据
- 审计日志归档
- IoT 数据处理
通过合理配置 ILM,可以显著降低 Elasticsearch 集群的运维复杂度和存储成本,同时保证不同阶段数据的访问性能。
XIUANGH: elasticsearch的安全机制有哪些?请详细介绍
GitHub Copilot: # Elasticsearch 安全机制详解
1. Elasticsearch 安全概述 #
安全架构层次 #
# Elasticsearch 安全体系
应用层安全
├── API 访问控制
├── 用户身份验证
├── 角色权# Elasticsearch 安全体系
应用层安全
├── API 访问控制
├── 用户身份验证
├── 角色权
找到具有 1 个许可证类型的类似代码
XIUANGH: 详细介绍一下 elasticsearch的分片、集群管理相关的内容,还有这些内容命令是什意思?
# 强制合并分段
POST /_forcemerge
# 刷新索引
POST /_refresh
请你详细的分析
GitHub Copilot: # Elasticsearch 分片、集群管理详解
## 1. 分片(Shard)概念深度解析
### 分片的基本概念
```bash
# 分片是 Elasticsearch 数据分布的基本单位
索引 (Index)
├── 主分片 0 (Primary Shard 0)
├── 主分片 1 (Primary Shard 1)
├── 主分片 2 (Primary Shard 2)
├── 副本分片 0 (Replica Shard 0)
├── 副本分片 1 (Replica Shard 1)
└── 副本分片 2 (Replica Shard 2)
分片的作用 #
1. 水平扩展 #
# 单机存储限制
单节点: 1TB 磁盘 -> 最多存储 1TB 数据
# 分片后的扩展
3个分片 x 3个节点 = 9TB 存储能力
节点1: 分片0 (1TB)
节点2: 分片1 (1TB)
节点3: 分片2 (1TB)
2. 并行处理 #
# 查询并行执行
用户查询 -> 协调节点
↓
并行发送到3个分片
├── 分片0: 搜索 1/3 数据
├── 分片1: 搜索 1/3 数据
└── 分片2: 搜索 1/3 数据
↓
协调节点合并结果 -> 返回给用户
2. 分片详细配置和管理 #
创建索引时配置分片 #
# 基础分片配置
PUT /my_index
{
"settings": {
"number_of_shards": 3, // 主分片数(创建后不可修改)
"number_of_replicas": 1 // 副本数(可动态修改)
}
}
分片分配原理 #
文档路由算法 #
// 分片路由计算公式
shard_num = hash(document_id) % number_of_primary_shards
// 实际示例
文档ID "user_001" -> hash值: 12345 -> 12345 % 3 = 0 -> 分片0
文档ID "user_002" -> hash值: 67890 -> 67890 % 3 = 1 -> 分片1
文档ID "user_003" -> hash值: 54321 -> 54321 % 3 = 2 -> 分片2
自定义路由 #
# 相关文档放在同一分片,提高查询效率
PUT /user_orders/_doc/1?routing=user_123
{
"user_id": "user_123",
"order_id": "order_001",
"amount": 100
}
# 查询时指定相同路由
GET /user_orders/_search?routing=user_123
{
"query": {
"term": {"user_id": "user_123"}
}
}
分片状态管理 #
查看分片信息 #
# 查看所有分片状态
GET /_cat/shards?v
# 输出示例
index shard prirep state docs store ip node
my_index 0 p STARTED 1000 5mb 10.0.0.1 node-1
my_index 0 r STARTED 1000 5mb 10.0.0.2 node-2
my_index 1 p STARTED 1200 6mb 10.0.0.2 node-2
my_index 1 r STARTED 1200 6mb 10.0.0.3 node-3
分片状态说明 #
- STARTED: 分片正常运行
- INITIALIZING: 分片正在初始化
- RELOCATING: 分片正在迁移
- UNASSIGNED: 分片未分配到节点
分片重新分配 #
# 手动分配分片到指定节点
POST /_cluster/reroute
{
"commands": [
{
"move": {
"index": "my_index",
"shard": 0,
"from_node": "node-1",
"to_node": "node-2"
}
}
]
}
# 分配未分配的分片
POST /_cluster/reroute
{
"commands": [
{
"allocate_primary": {
"index": "my_index",
"shard": 0,
"node": "node-1",
"accept_data_loss": true
}
}
]
}
3. 集群管理详解 #
集群架构 #
# 典型集群架构
Elasticsearch Cluster
├── Master Node (主节点)
│ ├── 集群状态管理
│ ├── 索引创建/删除
│ └── 分片分配决策
├── Data Node (数据节点)
│ ├── 存储数据
│ ├── 执行查询
│ └── 索引操作
├── Coordinating Node (协调节点)
│ ├── 接收客户端请求
│ ├── 分发查询到数据节点
│ └── 合并结果返回
└── Ingest Node (摄取节点)
├── 数据预处理
├── 文档转换
└── 数据清洗
节点类型配置 #
专用主节点 #
# elasticsearch.yml 配置
node.name: master-node-1
node.roles: [ master ] # 只担任主节点角色
node.master: true
node.data: false
node.ingest: false
专用数据节点 #
# elasticsearch.yml 配置
node.name: data-node-1
node.roles: [ data ] # 只担任数据节点角色
node.master: false
node.data: true
node.ingest: false
混合节点 #
# elasticsearch.yml 配置
node.name: mixed-node-1
node.roles: [ master, data, ingest ] # 多种角色
集群状态监控 #
集群健康状态 #
# 查看集群健康
GET /_cluster/health
# 详细输出
{
"cluster_name": "my-cluster",
"status": "green", // green: 健康, yellow: 警告, red: 错误
"timed_out": false,
"number_of_nodes": 3,
"number_of_data_nodes": 3,
"active_primary_shards": 5,
"active_shards": 10,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}
集群状态详情 #
# 查看集群详细状态
GET /_cluster/state
# 包含内容:
{
"cluster_name": "my-cluster",
"master_node": "node-1-id",
"nodes": {...}, // 所有节点信息
"routing_table": {...}, // 分片路由表
"metadata": {...} // 索引元数据
}
节点信息查看 #
# 查看所有节点
GET /_cat/nodes?v
# 输出示例
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
10.0.0.1 45 67 12 1.23 1.15 1.08 mdi * node-1
10.0.0.2 38 52 8 0.87 0.92 0.95 di - node-2
10.0.0.3 42 61 15 1.45 1.32 1.28 di - node-3
集群设置管理 #
动态设置 #
# 临时设置(重启后失效)
PUT /_cluster/settings
{
"transient": {
"cluster.routing.allocation.disk.watermark.low": "85%",
"cluster.routing.allocation.disk.watermark.high": "90%"
}
}
# 持久设置(重启后仍有效)
PUT /_cluster/settings
{
"persistent": {
"cluster.max_shards_per_node": 1000,
"cluster.routing.allocation.enable": "all"
}
}
重要集群设置 #
# 分片分配设置
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.node_concurrent_recoveries": 2, // 并发恢复数
"cluster.routing.allocation.cluster_concurrent_rebalance": 2, // 并发重平衡数
"cluster.routing.allocation.node_initial_primaries_recoveries": 4 // 初始主分片恢复数
}
}
# 分片分配过滤
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.exclude._ip": "10.0.0.1", // 排除特定IP节点
"cluster.routing.allocation.require.rack": "rack1" // 要求在特定机架
}
}
4. 强制合并分段详解 #
POST /_forcemerge 命令分析
#
基本概念 #
# 段(Segment)的形成过程
文档写入 -> 内存缓冲区 -> 刷新形成段
时间推移 -> 产生多个小段 -> 影响查询性能
# 合并前的状态
索引包含多个段:
├── segment_0 (100个文档)
├── segment_1 (150个文档)
├── segment_2 (80个文档)
├── segment_3 (200个文档)
└── segment_4 (70个文档)
# 合并后的状态
索引合并为少数段:
├── segment_merged_0 (300个文档)
└── segment_merged_1 (300个文档)
强制合并语法 #
# 合并所有索引
POST /_forcemerge
# 合并特定索引
POST /my_index/_forcemerge
# 合并多个索引
POST /index1,index2/_forcemerge
# 合并索引模式
POST /log-*/_forcemerge
合并参数详解 #
# 指定最大段数
POST /my_index/_forcemerge?max_num_segments=1
# 只合并有删除文档的段
POST /my_index/_forcemerge?only_expunge_deletes=true
# 等待合并完成
POST /my_index/_forcemerge?wait_for_completion=true
# 完整参数示例
POST /my_index/_forcemerge?max_num_segments=1&only_expunge_deletes=false&flush=true
参数说明 #
- max_num_segments: 合并后的最大段数(默认不限制)
- only_expunge_deletes: 只合并包含删除文档的段(默认false)
- flush: 合并前是否执行flush(默认true)
- wait_for_completion: 是否等待操作完成(默认true)
强制合并的影响 #
性能影响 #
# 合并过程中的资源消耗
CPU使用: 高 (数据读取、写入、压缩)
磁盘I/O: 极高 (大量读写操作)
内存使用: 中等 (缓冲区)
网络: 低 (主要是本地操作)
# 合并期间的集群状态
- 查询性能可能下降
- 写入性能可能受影响
- 磁盘空间临时增加(需要存储新段)
最佳实践 #
# ✅ 合适的合并时机
- 业务低峰期(夜间、周末)
- 索引写入完成后(日志轮转后)
- 段数过多影响查询性能时
# ❌ 避免的情况
- 业务高峰期
- 频繁写入的热索引
- 磁盘空间不足时
# 分批合并大索引
POST /large_index/_forcemerge?max_num_segments=5
# 等待完成后再继续
POST /large_index/_forcemerge?max_num_segments=1
监控合并进度 #
# 查看合并进度
GET /_cat/segments/my_index?v
# 查看索引统计(包含合并信息)
GET /my_index/_stats/merge
# 输出示例
{
"indices": {
"my_index": {
"total": {
"merges": {
"current": 1, // 当前进行的合并数
"current_docs": 50000, // 当前合并的文档数
"current_size_in_bytes": 100000000, // 当前合并的字节数
"total": 10, // 总合并次数
"total_time_in_millis": 5000, // 总合并时间
"total_docs": 500000, // 总合并文档数
"total_size_in_bytes": 1000000000 // 总合并字节数
}
}
}
}
}
5. 刷新索引详解 #
POST /_refresh 命令分析
#
基本概念 #
# 文档可见性流程
文档写入 -> 内存缓冲区 -> refresh -> 可搜索 -> flush -> 持久化
# 时间线示例
00:00:00 - 文档写入内存缓冲区
00:00:01 - refresh操作(默认1秒)
00:00:01 - 文档变为可搜索
00:30:00 - flush操作(translog满或时间到)
00:30:00 - 数据持久化到磁盘
刷新语法 #
# 刷新所有索引
POST /_refresh
# 刷新特定索引
POST /my_index/_refresh
# 刷新多个索引
POST /index1,index2/_refresh
# 刷新索引模式
POST /log-*/_refresh
刷新机制详解 #
自动刷新配置 #
# 查看当前刷新设置
GET /my_index/_settings
# 修改刷新间隔
PUT /my_index/_settings
{
"refresh_interval": "5s" // 5秒刷新一次
}
# 常用刷新间隔设置
"refresh_interval": "1s" // 默认值,近实时搜索
"refresh_interval": "30s" // 降低刷新频率,提高写入性能
"refresh_interval": "-1" // 禁用自动刷新,手动控制
写入时控制刷新 #
# 写入后立即刷新
PUT /my_index/_doc/1?refresh=true
{
"title": "New Document",
"content": "This document will be immediately searchable"
}
# 写入后等待刷新
PUT /my_index/_doc/2?refresh=wait_for
{
"title": "Another Document",
"content": "Wait for the next refresh cycle"
}
# 不刷新(默认)
PUT /my_index/_doc/3?refresh=false
{
"title": "Third Document",
"content": "Will be searchable after next automatic refresh"
}
刷新性能优化 #
批量操作中的刷新策略 #
# 批量写入时禁用刷新
PUT /my_index/_settings
{
"refresh_interval": "-1"
}
# 执行批量操作
POST /my_index/_bulk
{"index":{"_id":"1"}}
{"title":"Document 1"}
{"index":{"_id":"2"}}
{"title":"Document 2"}
# ... 更多文档
# 批量完成后手动刷新
POST /my_index/_refresh
# 恢复自动刷新
PUT /my_index/_settings
{
"refresh_interval": "1s"
}
监控刷新性能 #
# 查看刷新统计
GET /my_index/_stats/refresh
# 输出示例
{
"indices": {
"my_index": {
"total": {
"refresh": {
"total": 1000, // 总刷新次数
"total_time_in_millis": 5000, // 总刷新时间
"external_total": 50, // 外部触发的刷新次数
"external_total_time_in_millis": 500, // 外部刷新时间
"listeners": 0 // 等待刷新的监听器数量
}
}
}
}
}
6. 实际运维场景 #
索引生命周期管理 #
热温冷架构 #
# 热节点配置(高性能SSD)
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.awareness.attributes": "box_type"
}
}
# 创建热索引
PUT /logs-hot
{
"settings": {
"number_of_shards": 6,
"number_of_replicas": 1,
"index.routing.allocation.require.box_type": "hot"
}
}
# 迁移到温节点
PUT /logs-warm/_settings
{
"index.routing.allocation.require.box_type": "warm",
"number_of_replicas": 0
}
# 迁移到冷节点并强制合并
PUT /logs-cold/_settings
{
"index.routing.allocation.require.box_type": "cold"
}
POST /logs-cold/_forcemerge?max_num_segments=1
索引模板和生命周期策略 #
# 创建ILM策略
PUT /_ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "5GB",
"max_age": "1d"
}
}
},
"warm": {
"min_age": "1d",
"actions": {
"allocate": {
"number_of_replicas": 0
},
"forcemerge": {
"max_num_segments": 1
}
}
},
"cold": {
"min_age": "30d",
"actions": {
"allocate": {
"number_of_replicas": 0,
"require": {
"box_type": "cold"
}
}
}
},
"delete": {
"min_age": "90d"
}
}
}
}
故障恢复场景 #
分片故障恢复 #
# 查看未分配分片原因
GET /_cluster/allocation/explain
{
"index": "my_index",
"shard": 0,
"primary": true
}
# 强制分配主分片(数据可能丢失)
POST /_cluster/reroute
{
"commands": [
{
"allocate_empty_primary": {
"index": "my_index",
"shard": 0,
"node": "node-1",
"accept_data_loss": true
}
}
]
}
集群恢复步骤 #
# 1. 检查集群状态
GET /_cluster/health?level=shards
# 2. 查看节点状态
GET /_cat/nodes?v
# 3. 检查分片分配
GET /_cat/shards?v&h=index,shard,prirep,state,unassigned.reason
# 4. 如有必要,调整恢复速度
PUT /_cluster/settings
{
"transient": {
"cluster.routing.allocation.node_concurrent_recoveries": 5,
"indices.recovery.max_bytes_per_sec": "100mb"
}
}
# 5. 恢复完成后刷新和合并
POST /_refresh
POST /_forcemerge?max_num_segments=1
总结 #
关键要点 #
分片管理:
- 主分片数量影响扩展能力,创建后不可修改
- 副本分片提供高可用和读取性能
- 合理的分片数量 = 节点数 × 1-3倍
集群管理:
- 主节点负责集群状态管理
- 数据节点存储数据并执行查询
- 监控集群健康状态和资源使用
强制合并:
- 减少段数量,提高查询性能
- 消耗大量I/O和CPU资源
- 适合在低峰期对稳定索引执行
索引刷新:
- 控制文档的可搜索性
- 影响写入性能和搜索延迟
- 根据业务需求调整刷新策略
这些操作是 Elasticsearch 运维的核心,正确使用能显著提升集群性能和稳定性。
XIUANGH: elasticsearch的缓存机制是其快速的原因之一,那么什么时候缓存会失效?什么时候缓存需要更新? 这个高效的数据结构是什么意思:## 4. 高效数据结构
Term Dictionary + Term Index #
Elasticsearch 使用双层数据结构来快速定位词项:
Term Index (内存中的前缀树)
├── "a" → Block 1
├── "el" → Block 5
└── "z" → Block 20
Term Dictionary (磁盘上的有序列表)
Block 5:
├── "elastic" → Posting List
├── "elasticsearch" → Posting List
└── "element" → Posting List
FST(Finite State Transducer) #
// 词典压缩示例
原始词典:
["cat", "cats", "dog", "dogs"]
FST 压缩后:
c → a → t → [end:1]
↓
s → [end:2]
d → o → g → [end:3]
↓
s → [end:4]
内存节省:80%以上 其原理是什么?
GitHub Copilot: # Elasticsearch 缓存机制详解
1. 缓存失效时机 #
索引刷新导致的缓存失效 #
Query Cache 失效 #
# 当索引刷新时,查询缓存会失效
POST /my_index/_doc/1
{
"title": "新文档"
}
# 索引自动刷新(默认1秒)或手动刷新
POST /my_index/_refresh
# 此时之前的查询缓存失效,因为索引内容已变化
Filter Cache 失效 #
# Filter 缓存在以下情况失效:
1. 索引刷新(新段产生)
2. 段合并操作
3. 索引重新打开
4. 集群重启
# 示例:文档更新导致缓存失效
PUT /products/_doc/1
{
"category": "electronics", # 原来是 "books"
"price": 999
}
# 之前缓存的 filter 查询失效
{
"query": {
"bool": {
"filter": [
{"term": {"category": "books"}} # 这个缓存失效
]
}
}
}
内存压力导致的缓存淘汰 #
LRU 淘汰策略 #
# 当内存不足时,使用 LRU 算法淘汰缓存
# 查看缓存使用情况
GET /_nodes/stats/indices/query_cache,fielddata
# 缓存大小限制
PUT /_cluster/settings
{
"persistent": {
"indices.queries.cache.size": "10%", # 查询缓存大小
"indices.fielddata.cache.size": "40%" # 字段数据缓存大小
}
}
段合并触发的缓存重建 #
# 段合并会导致缓存失效
POST /my_index/_forcemerge?max_num_segments=1
# 合并过程中的缓存变化:
1. 旧段的缓存标记为失效
2. 新段产生后重新构建缓存
3. 查询性能短暂下降
4. 缓存重建后性能恢复
2. 缓存更新机制 #
增量更新策略 #
Query Cache 更新 #
# 查询缓存采用懒更新策略
{
"query": {
"term": {"status": "published"}
}
}
# 更新流程:
1. 检查缓存中是否存在相同查询
2. 如果存在且未失效,直接返回缓存结果
3. 如果不存在或已失效,执行查询并更新缓存
4. 缓存结果供后续相同查询使用
Filter Cache 更新 #
# Filter 缓存更新机制
{
"query": {
"bool": {
"filter": [
{"range": {"price": {"gte": 100}}}
]
}
}
}
# 更新过程:
1. 计算 filter 查询的哈希值
2. 检查缓存中是否存在对应的 BitSet
3. 如果不存在,执行 filter 并生成 BitSet
4. 将 BitSet 存储到缓存中
5. 后续相同 filter 直接使用缓存的 BitSet
自动缓存预热 #
# ES 自动预热策略
1. 新段产生时,自动执行热点查询
2. 重建常用 filter 的缓存
3. 预加载热点字段数据
# 手动预热示例
POST /my_index/_cache/clear
# 执行常用查询进行预热
GET /my_index/_search
{
"query": {
"bool": {
"filter": [
{"term": {"category": "electronics"}},
{"range": {"price": {"gte": 100}}}
]
}
}
}
3. 高效数据结构详解 #
Term Dictionary + Term Index 原理 #
整体架构 #
# 双层索引结构
内存层:Term Index(前缀树/FST)
↓ 指向
磁盘层:Term Dictionary(排序词典)
↓ 指向
磁盘层:Posting List(倒排表)
Term Index(词项索引) #
// Term Index 是一个前缀树(Trie)结构
class TermIndex {
// 前缀树节点
class TrieNode {
char character;
Map<Character, TrieNode> children;
long blockPointer; // 指向 Term Dictionary 的块
}
// 查找过程
public long findBlock(String term) {
TrieNode current = root;
for (char c : term.toCharArray()) {
current = current.children.get(c);
if (current == null) return -1;
}
return current.blockPointer;
}
}
查找过程示例 #
# 查找词项 "elasticsearch"
1. Term Index 查找:
e → l → a → s → t → i → c
找到指针:Block 5
2. 加载 Term Dictionary Block 5:
├── "elastic" → PostingList_A
├── "elasticsearch" → PostingList_B ✓
└── "element" → PostingList_C
3. 获取 PostingList_B:
文档列表:[doc1, doc5, doc8, ...]
内存效率对比 #
# 传统方式:所有词项都在内存中
Memory Usage = 所有词项数量 × 平均词项长度
Example: 1,000,000 terms × 8 bytes = 8MB
# Term Index 方式:只存储前缀树
Memory Usage = 前缀节点数量 × 节点大小
Example: 50,000 nodes × 16 bytes = 800KB
# 内存节省:90%以上
FST(Finite State Transducer)详解 #
FST 基本原理 #
FST 是一种有限状态转换器,它将字符串映射到数值,同时实现了极高的压缩率。
// FST 的核心思想
class FST {
class State {
int id;
Map<Character, Arc> arcs; // 出边
boolean isFinal; // 是否为终止状态
long output; // 输出值
}
class Arc {
char label; // 边标签
State target; // 目标状态
long output; // 边输出值
}
}
构建过程详解 #
1. 原始词典 #
词典:
["cat" → 1, "cats" → 2, "dog" → 3, "dogs" → 4]
2. 构建前缀树 #
root
/ \
c d
| |
a o
| |
t g
/ \ | \
[end:1] s [end:3] s
| |
[end:2] [end:4]
3. 状态合并优化 #
# 发现相同的后缀结构可以合并
"cat" 和 "dog" 都有相同的后缀模式:
- 都可以加 "s" 变成复数
- 合并相同的状态
优化后的 FST:
root
/ \
c d
| |
a o
| |
t g
\ /
\ /
state_X # 合并的状态
/ \
[end] s
|
[end]
4. 最终压缩结果 #
// 压缩前:需要存储4个完整字符串
原始存储:
"cat"(3) + "cats"(4) + "dog"(3) + "dogs"(4) = 14 字节
// 压缩后:只需要存储状态转换
FST存储:
States: 6个状态
Arcs: 8条边
Total: 约4字节
// 压缩率:约70%
FST 查找过程 #
// 查找 "cats" 的过程
public long lookup(String word) {
State current = startState;
long output = 0;
for (char c : word.toCharArray()) {
Arc arc = current.getArc(c);
if (arc == null) return -1; // 未找到
output += arc.output; // 累加输出值
current = arc.target; // 转移到下一状态
}
if (current.isFinal) {
return output + current.output;
}
return -1; // 不是完整词
}
// 查找 "cats":
// root --c--> state1 --a--> state2 --t--> state3 --s--> state4(final)
// output = 0 + 0 + 0 + 2 = 2
实际应用效果 #
# Elasticsearch 中的 FST 使用
1. Term Dictionary 压缩:
- 原始大小:100MB 词典
- FST 压缩后:20MB
- 压缩率:80%
2. 内存占用对比:
- 传统 HashMap:占用大量内存
- FST:内存占用极小,大部分数据在磁盘
3. 查找性能:
- 时间复杂度:O(key_length)
- 空间复杂度:O(1) 内存占用
4. 缓存和数据结构的协同工作 #
多层次缓存架构 #
查询请求
↓
1. Query Cache(查询结果缓存)
↓ miss
2. Filter Cache(过滤器缓存)
↓ miss
3. OS Page Cache(操作系统页缓存)
↓ miss
4. FST + Term Dictionary(磁盘读取)
↓
5. Posting List(倒排表)
性能优化策略 #
1. 缓存预热 #
# 系统启动后预热关键查询
POST /my_index/_search
{
"query": {
"bool": {
"filter": [
{"term": {"status": "active"}},
{"range": {"created_at": {"gte": "2025-01-01"}}}
]
}
}
}
2. 缓存监控 #
# 监控缓存命中率
GET /_nodes/stats/indices/query_cache
{
"nodes": {
"node1": {
"indices": {
"query_cache": {
"memory_size_in_bytes": 104857600,
"total_count": 10000,
"hit_count": 8500, # 命中次数
"miss_count": 1500, # 未命中次数
"cache_size": 1000,
"cache_count": 950,
"evictions": 50 # 淘汰次数
}
}
}
}
}
# 缓存命中率 = hit_count / total_count = 85%
3. 缓存调优 #
# 根据业务调整缓存大小
PUT /_cluster/settings
{
"persistent": {
"indices.queries.cache.size": "15%", # 增加查询缓存
"indices.fielddata.cache.size": "30%", # 减少字段缓存
"indices.requests.cache.enable": true # 启用请求缓存
}
}
总结 #
缓存失效和更新机制 #
- 失效触发:索引刷新、段合并、内存压力
- 更新策略:懒更新、增量更新、自动预热
- 淘汰算法:LRU、内存阈值控制
高效数据结构 #
- Term Index + Term Dictionary:两层索引,减少磁盘IO
- FST 压缩:状态机压缩,节省80%内存
- 协同工作:多层缓存 + 高效数据结构 = 极致性能
这些机制的结合使得 Elasticsearch 能够在大数据量下保持毫秒级的查询响应速度。
XIUANGH: elasticsearch在使用分词之后,是否会影响其插入的效率?比如可能插入会很长时间,在刚插入之后,不能马上搜索到这个结果?
GitHub Copilot: # Elasticsearch 分词对插入效率和搜索实时性的影响详解
1. 分词对插入效率的影响 #
分词确实会影响插入效率 #
分词器在文档索引过程中需要进行额外的计算,这会对插入性能产生一定影响:
# 无分词字段(keyword)
PUT /test_index/_doc/1
{
"status": "published", # keyword字段,不分词,快速索引
"id": "12345" # keyword字段,直接建立倒排索引
}
# 有分词字段(text)
PUT /test_index/_doc/2
{
"title": "Elasticsearch是一个强大的搜索引擎", # 需要分词处理
"content": "这是一篇关于ES的详细教程..." # 大量文本需要分词
}
分词处理的性能开销 #
1. CPU 开销 #
// 分词过程的CPU消耗
String text = "Elasticsearch是一个强大的搜索引擎";
// IK分词器处理流程
IK分词器.analyze(text) -> {
1. 字符串预处理 // CPU时间
2. 词典匹配 // 内存访问 + CPU计算
3. 歧义处理 // 算法计算
4. 词性标注(可选) // 额外计算
5. 生成Token列表 // 内存分配
}
// 结果:["elasticsearch", "是", "一个", "强大", "的", "搜索", "引擎"]
2. 内存开销 #
# 分词前
原始文本: "Elasticsearch教程" (20字节)
# 分词后需要存储
{
"tokens": ["elasticsearch", "教程"],
"positions": [0, 13],
"offsets": [(0,13), (13,15)],
"term_vectors": {...}
}
# 内存使用增加到约 100+ 字节
性能测试对比 #
# 性能测试数据
文档类型: 1000条用户记录
# 场景1:纯keyword字段
{
"user_id": "12345", # keyword
"status": "active", # keyword
"created_at": "2025-01-01" # date
}
# 插入速度: ~10,000 docs/sec
# 场景2:包含text分词字段
{
"user_id": "12345", # keyword
"status": "active", # keyword
"bio": "我是一名软件工程师,专注于Elasticsearch开发", # text,需要分词
"description": "长文本内容..." # text,大量分词
}
# 插入速度: ~3,000-5,000 docs/sec(降低50-70%)
2. 插入后的搜索实时性问题 #
Refresh 机制导致的延迟 #
你提到的"插入后不能马上搜索到"确实存在,这主要由 Elasticsearch 的 refresh 机制 造成:
# 文档插入流程
1. 文档写入 -> 内存缓冲区(Memory Buffer)
2. 等待refresh -> 刷新到文件系统缓存
3. 文档变为可搜索 -> 可以被搜索到
默认 Refresh 间隔 #
# 查看当前refresh设置
GET /my_index/_settings
{
"my_index": {
"settings": {
"index": {
"refresh_interval": "1s" # 默认1秒刷新一次
}
}
}
}
实际测试示例 #
# 1. 插入文档
PUT /test_index/_doc/1
{
"title": "Elasticsearch分词测试",
"content": "这是一个测试文档"
}
# 响应: {"result": "created"} - 插入成功
# 2. 立即搜索(可能搜索不到)
GET /test_index/_search
{
"query": {
"match": {
"title": "分词"
}
}
}
# 可能返回: {"hits": {"total": {"value": 0}}} - 搜索不到
# 3. 等待1秒后再搜索
GET /test_index/_search
{
"query": {
"match": {
"title": "分词"
}
}
}
# 返回: 找到文档
3. 分词类型对性能的具体影响 #
不同分词器的性能对比 #
Standard 分词器(英文) #
# 性能较好,逻辑简单
POST /_analyze
{
"analyzer": "standard",
"text": "The quick brown fox jumps over the lazy dog"
}
# 处理速度: 很快
# 结果: ["the", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
IK 分词器(中文) #
# 性能较慢,词典匹配复杂
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "中华人民共和国成立于1949年"
}
# 处理速度: 较慢(需要词典匹配)
# 结果: ["中华人民共和国", "中华人民", "中华", "华人", "人民", "共和国", "成立", "于", "1949", "年"]
性能对比数据 #
分词器性能排序(从快到慢):
1. keyword(无分词) - 10,000+ docs/sec
2. standard - 8,000 docs/sec
3. whitespace - 7,500 docs/sec
4. ik_smart - 4,000 docs/sec
5. ik_max_word - 2,500 docs/sec
6. 自定义复杂分词器 - 1,000-2,000 docs/sec
4. 优化策略 #
插入性能优化 #
1. 批量插入 #
# ❌ 单条插入(慢)
PUT /index/_doc/1 {"title": "doc1"}
PUT /index/_doc/2 {"title": "doc2"}
PUT /index/_doc/3 {"title": "doc3"}
# ✅ 批量插入(快)
POST /_bulk
{"index":{"_index":"test_index"}}
{"title": "doc1", "content": "content1"}
{"index":{"_index":"test_index"}}
{"title": "doc2", "content": "content2"}
{"index":{"_index":"test_index"}}
{"title": "doc3", "content": "content3"}
# 性能提升: 5-10倍
2. 优化刷新间隔 #
# 批量导入时禁用自动刷新
PUT /my_index/_settings
{
"refresh_interval": "-1" # 禁用自动刷新
}
# 执行批量导入
POST /_bulk
{...大量数据...}
# 导入完成后手动刷新
POST /my_index/_refresh
# 恢复自动刷新
PUT /my_index/_settings
{
"refresh_interval": "1s"
}
3. 字段映射优化 #
# 合理设置字段类型
PUT /optimized_index
{
"mappings": {
"properties": {
"id": {
"type": "keyword", # 不需要搜索的字段用keyword
"index": false # 不建立索引,节省时间
},
"title": {
"type": "text",
"analyzer": "ik_smart" # 使用较快的分词器
},
"content": {
"type": "text",
"analyzer": "ik_smart",
"index_options": "docs" # 只索引文档,不索引位置
},
"metadata": {
"type": "keyword",
"store": false # 不存储原始值
}
}
}
}
搜索实时性优化 #
1. 调整刷新间隔 #
# 根据业务需求调整
PUT /my_index/_settings
{
"refresh_interval": "5s" # 5秒刷新,降低CPU消耗
# "refresh_interval": "100ms" # 100毫秒刷新,提高实时性
}
2. 强制刷新 #
# 插入重要文档后立即刷新
PUT /my_index/_doc/important_doc?refresh=true
{
"urgent": true,
"content": "重要内容"
}
# 插入后立即可搜索,但影响性能
3. 等待刷新 #
# 等待下次刷新周期
PUT /my_index/_doc/doc1?refresh=wait_for
{
"content": "等待刷新的文档"
}
# 等待刷新完成后返回,平衡性能和实时性
5. 实际生产环境建议 #
写入密集型场景 #
# 日志系统、监控数据等
{
"settings": {
"refresh_interval": "30s", # 降低刷新频率
"number_of_replicas": 0, # 减少副本数
"translog.flush_threshold_size": "1gb" # 增大translog
},
"mappings": {
"properties": {
"message": {
"type": "text",
"analyzer": "simple" # 使用简单分词器
},
"timestamp": {
"type": "date"
},
"level": {
"type": "keyword"
}
}
}
}
搜索密集型场景 #
# 电商搜索、内容检索等
{
"settings": {
"refresh_interval": "1s", # 保持默认实时性
"number_of_replicas": 2 # 增加副本提升查询性能
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word", # 使用精细分词
"search_analyzer": "ik_smart" # 搜索时用简单分词
}
}
}
}
混合场景优化 #
# 使用索引模板自动应用设置
PUT /_index_template/app_logs
{
"index_patterns": ["app-logs-*"],
"template": {
"settings": {
"refresh_interval": "5s",
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"message": {
"type": "text",
"analyzer": "standard"
}
}
}
}
}
总结 #
分词对性能的影响 #
- 插入效率:分词确实会降低插入速度 50-70%
- 搜索延迟:默认1秒的refresh间隔导致"近实时"搜索
- 资源消耗:分词需要额外的CPU和内存资源
优化策略 #
- 批量操作:使用bulk API批量插入
- 合理映射:只对需要搜索的字段启用分词
- 调整刷新:根据业务需求调整refresh_interval
- 选择分词器:根据性能要求选择合适的分词器
权衡建议 #
- 高实时性要求:使用较短的refresh_interval + 简单分词器
- 高吞吐量要求:使用较长的refresh_interval + 批量操作
- 平衡方案:默认设置 + 批量插入 + 必要时手动刷新
在实际应用中,需要根据具体的业务场景(日志收集、实时搜索、数据分析等)来权衡插入性能和搜索实时性。
XIUANGH: # 查看段信息 GET /_segments
手动强制合并 #
POST /_forcemerge?max_num_segments=1 段合并优化是什么意思?
GitHub Copilot: # Elasticsearch 段合并优化详解
什么是段(Segment)? #
段的基本概念 #
段(Segment)是 Elasticsearch 中数据存储的基本单位,每个段都是一个独立的 Lucene 索引。
# 段的形成过程
文档写入 → 内存缓冲区 → refresh刷新 → 生成新段
时间推移 → 多次refresh → 产生多个小段
段的内部结构 #
# 单个段包含的文件
segment_0/
├── _0.si # 段信息文件
├── _0.cfs # 复合文件(包含多个索引文件)
├── _0.cfe # 复合文件入口
├── _0_Lucene50_0.doc # 文档存储
├── _0_Lucene50_0.tim # 词项索引
├── _0_Lucene50_0.tip # 词项指针
├── _0_Lucene50_0.pos # 位置信息
└── _0_Lucene50_0.del # 删除文档标记
为什么需要段合并? #
问题场景 #
随着时间推移,索引中会产生大量小段:
# 段数量增长示例
时间T1: [segment_0] # 1个段,1000个文档
时间T2: [segment_0, segment_1] # 2个段,各500个文档
时间T3: [segment_0, segment_1, segment_2, segment_3] # 4个段,各250个文档
...
时间Tn: [seg_0, seg_1, seg_2, ..., seg_n] # n个小段
大量小段的问题 #
1. 查询性能下降 #
# 查询执行过程
GET /my_index/_search
{
"query": {"match": {"content": "elasticsearch"}}
}
# ES需要:
1. 在segment_0中搜索 → 找到3个文档
2. 在segment_1中搜索 → 找到2个文档
3. 在segment_2中搜索 → 找到1个文档
4. ...
5. 在segment_n中搜索 → 找到x个文档
6. 合并所有结果 → 返回最终结果
# 段数量越多,搜索开销越大
2. 内存开销增加 #
# 每个段都需要独立的内存结构
segment_0: 词典缓存 + 文件句柄 + 元数据
segment_1: 词典缓存 + 文件句柄 + 元数据
segment_2: 词典缓存 + 文件句柄 + 元数据
...
# 总内存 = 段数量 × 单段内存开销
3. 文件句柄消耗 #
# 每个段占用多个文件句柄
segment_0: 5-10个文件句柄
segment_1: 5-10个文件句柄
...
# 可能导致"Too many open files"错误
段合并的工作原理 #
合并过程详解 #
# 合并前状态
索引包含多个小段:
├── segment_0 (100个文档, 删除标记: 10个)
├── segment_1 (150个文档, 删除标记: 5个)
├── segment_2 (80个文档, 删除标记: 20个)
├── segment_3 (200个文档, 删除标记: 15个)
└── segment_4 (70个文档, 删除标记: 0个)
# 合并过程
1. 选择要合并的段 → [segment_0, segment_1, segment_2]
2. 读取所有段的数据
3. 合并倒排索引
4. 物理删除标记为删除的文档
5. 创建新的大段
6. 删除旧的小段
# 合并后状态
索引包含更少的大段:
├── segment_merged_0 (315个文档, 删除标记: 0个) # 合并后的新段
├── segment_3 (200个文档, 删除标记: 15个)
└── segment_4 (70个文档, 删除标记: 0个)
合并策略 #
TieredMergePolicy(默认策略) #
# 分层合并策略参数
{
"index.merge.policy.max_merge_at_once": 10, # 一次合并的最大段数
"index.merge.policy.segments_per_tier": 10, # 每层的段数目标
"index.merge.policy.max_merged_segment": "5gb", # 合并后段的最大大小
"index.merge.policy.floor_segment": "2mb" # 最小段大小阈值
}
合并触发条件 #
// 自动合并触发逻辑
if (段数量 > segments_per_tier) {
// 选择合适的段进行合并
List<Segment> toMerge = selectSegmentsToMerge();
if (toMerge.size() >= 2) {
startMerge(toMerge);
}
}
手动强制合并详解 #
POST /_forcemerge 命令
#
# 基本语法
POST /index_name/_forcemerge?max_num_segments=1
参数详解 #
max_num_segments
#
# 合并到指定段数
POST /my_index/_forcemerge?max_num_segments=1 # 合并为1个段
POST /my_index/_forcemerge?max_num_segments=5 # 合并为5个段
POST /my_index/_forcemerge # 使用默认策略合并
only_expunge_deletes
#
# 只合并有删除文档的段
POST /my_index/_forcemerge?only_expunge_deletes=true
# 作用:清理删除标记,回收空间,但不减少段数量
flush
#
# 合并前是否执行flush
POST /my_index/_forcemerge?flush=true # 先flush再合并(默认)
POST /my_index/_forcemerge?flush=false # 直接合并
完整示例 #
# 查看合并前的段信息
GET /my_index/_segments
# 返回结果示例
{
"indices": {
"my_index": {
"shards": {
"0": [
{
"routing": {
"state": "STARTED",
"primary": true
},
"segments": {
"_0": {
"generation": 0,
"num_docs": 1000,
"deleted_docs": 50,
"size_in_bytes": 2048576,
"committed": true
},
"_1": {
"generation": 1,
"num_docs": 800,
"deleted_docs": 20,
"size_in_bytes": 1638400,
"committed": true
},
"_2": {
"generation": 2,
"num_docs": 500,
"deleted_docs": 10,
"size_in_bytes": 1024000,
"committed": true
}
}
}
]
}
}
}
}
# 执行强制合并
POST /my_index/_forcemerge?max_num_segments=1&wait_for_completion=true
# 查看合并后的段信息
GET /my_index/_segments
# 合并后结果
{
"indices": {
"my_index": {
"shards": {
"0": [
{
"segments": {
"_3": {
"generation": 3,
"num_docs": 2220, # 合并后的文档数(删除的文档被物理删除)
"deleted_docs": 0, # 删除标记被清理
"size_in_bytes": 4456448,
"committed": true
}
}
}
]
}
}
}
}
段合并的性能影响 #
合并过程中的资源消耗 #
# 资源使用情况
CPU使用率: 高 (数据读取、合并、压缩)
磁盘I/O: 极高 (大量顺序读写)
内存使用: 中等 (合并缓冲区)
网络使用: 低 (主要是本地操作)
# 临时磁盘空间需求
原始段大小: 5GB
合并期间峰值磁盘使用: 10GB (原始 + 新段)
合并完成后: 4GB (删除文档被物理删除)
合并对查询性能的影响 #
# 合并进行时
查询延迟: 可能增加 20-50%
吞吐量: 可能下降 30-40%
# 合并完成后
查询延迟: 显著改善
吞吐量: 显著提升
内存使用: 减少
实际应用场景 #
1. 日志索引优化 #
# 日志轮转后的优化
# 假设日志按天轮转:logs-2025-06-14
# 当天结束后,该索引不再写入
POST /logs-2025-06-14/_forcemerge?max_num_segments=1
# 优化效果:
# - 查询速度提升 50-80%
# - 存储空间减少 10-30%
# - 内存使用减少 60-80%
2. 历史数据归档 #
# 将旧数据移至冷节点前进行合并
PUT /old_logs/_settings
{
"index.routing.allocation.require.box_type": "cold"
}
# 合并优化存储
POST /old_logs/_forcemerge?max_num_segments=1
# 启用最佳压缩
PUT /old_logs/_settings
{
"index.codec": "best_compression"
}
3. 批量导入后优化 #
# 大批量数据导入完成后
POST /_bulk
# ... 大量数据导入
# 关闭刷新以提升导入性能
PUT /my_index/_settings
{
"refresh_interval": "-1"
}
# 导入完成后手动刷新和合并
POST /my_index/_refresh
POST /my_index/_forcemerge?max_num_segments=1
# 恢复正常刷新间隔
PUT /my_index/_settings
{
"refresh_interval": "1s"
}
合并优化最佳实践 #
1. 合适的合并时机 #
# ✅ 适合强制合并的情况
- 停止写入的索引(如历史日志)
- 批量导入完成后
- 业务低峰期
- 索引即将归档时
# ❌ 避免强制合并的情况
- 正在高频写入的索引
- 业务高峰期
- 磁盘空间不足时
- 集群负载很高时
2. 合并策略配置 #
# 针对写入密集型索引
PUT /write_heavy_index/_settings
{
"index.merge.policy.max_merge_at_once": 5,
"index.merge.policy.segments_per_tier": 5,
"index.merge.scheduler.max_thread_count": 1
}
# 针对查询密集型索引
PUT /read_heavy_index/_settings
{
"index.merge.policy.max_merge_at_once": 10,
"index.merge.policy.segments_per_tier": 20,
"index.merge.scheduler.max_thread_count": 3
}
3. 监控合并过程 #
# 查看合并统计信息
GET /my_index/_stats/merge
# 返回合并指标
{
"indices": {
"my_index": {
"total": {
"merges": {
"current": 1, # 当前进行的合并数
"current_docs": 50000, # 当前合并的文档数
"current_size_in_bytes": 100000000,
"total": 25, # 总合并次数
"total_time_in_millis": 15000, # 总合并时间
"total_docs": 500000,
"total_size_in_bytes": 1000000000
}
}
}
}
}
# 监控合并进度
GET /_cat/segments/my_index?v&s=segment
自动合并 vs 手动合并 #
自动合并(后台进行) #
# 优点:
- 自动触发,无需人工干预
- 不会阻塞写入操作
- 渐进式合并,影响较小
# 缺点:
- 合并策略可能不是最优的
- 无法控制合并时机
- 可能在业务高峰期执行
手动合并(主动触发) #
# 优点:
- 可控制合并时机
- 可指定合并程度
- 适合特定场景优化
# 缺点:
- 需要人工干预
- 可能阻塞其他操作
- 需要充分的磁盘空间
总结 #
段合并优化是 Elasticsearch 性能优化的重要手段:
核心概念 #
- 段:Lucene 索引的基本单位
- 合并:将多个小段合并为少数大段
- 目的:提升查询性能、减少资源消耗
主要收益 #
- 查询性能:减少段数量,提升搜索速度
- 存储优化:物理删除已标记删除的文档
- 资源节省:减少内存使用和文件句柄
使用建议 #
- 定期合并:对停止写入的索引进行合并
- 合理时机:选择业务低峰期执行
- 监控资源:确保有足够的磁盘空间和I/O能力
- 分步执行:大索引可以分批合并
通过合理的段合并策略,可以显著提升 Elasticsearch 集群的整体性能!
XIUANGH: # 2. 优化映射设置 { “mappings”: { “properties”: { “id”: { “type”: “keyword”, “index”: false // 不需要搜索的字段禁用索引 }, “content”: { “type”: “text”, “analyzer”: “standard”, “store”: false // 不存储原始值节省空间 } } } }
不需要搜索的字段禁用索引:那么还可以搜索这个字段吗?搜索的时候是否会比较慢? “store”: false // 不存储原始值节省空间:这个是什么意思?还可以搜索这个字段吗?
GitHub Copilot: # Elasticsearch 字段索引和存储详解
1. "index": false 详解
#
基本概念 #
{
"id": {
"type": "keyword",
"index": false // 禁用索引
}
}
禁用索引后的影响 #
❌ 不能进行的操作 #
# 以下查询都会失败
GET /my_index/_search
{
"query": {
"term": {"id": "12345"} // ❌ 报错:Cannot search on field [id]
}
}
GET /my_index/_search
{
"query": {
"range": {"id": {"gte": "100"}} // ❌ 报错
}
}
# 聚合也无法使用
{
"aggs": {
"id_terms": {
"terms": {"field": "id"} // ❌ 报错
}
}
}
✅ 仍然可以进行的操作 #
# 1. 通过文档ID获取
GET /my_index/_doc/doc_123 // ✅ 正常
# 2. 在返回结果中显示字段值
GET /my_index/_search
{
"query": {"match_all": {}},
"_source": ["id", "title"] // ✅ 可以返回id字段值
}
# 3. 脚本中使用字段值
{
"script": {
"source": "doc['id'].value" // ❌ 无法使用,因为没有doc_values
}
}
实际应用场景 #
1. 纯展示字段 #
{
"mappings": {
"properties": {
"internal_id": {
"type": "keyword",
"index": false // 内部ID,只用于展示,不用于搜索
},
"thumbnail_url": {
"type": "keyword",
"index": false // 缩略图URL,只用于展示
},
"created_by_system": {
"type": "keyword",
"index": false // 系统创建标识,不参与搜索
}
}
}
}
2. 优化存储空间 #
# 禁用索引前
索引大小: 100MB
├── 倒排索引: 60MB
├── 文档存储: 30MB
└── 其他: 10MB
# 禁用不必要字段索引后
索引大小: 70MB
├── 倒排索引: 30MB (减少50%)
├── 文档存储: 30MB
└── 其他: 10MB
2. "store": false 详解
#
基本概念 #
在 Elasticsearch 中,字段数据有两种存储方式:
1. _source 存储:原始JSON文档的完整存储
2. store 存储:单独存储的字段值
store 的工作机制 #
默认行为 ("store": false)
#
# 文档写入
PUT /my_index/_doc/1
{
"title": "Elasticsearch教程",
"content": "这是一篇关于ES的详细教程...",
"author": "张三"
}
# 存储结构
_source: {
"title": "Elasticsearch教程",
"content": "这是一篇关于ES的详细教程...",
"author": "张三"
}
# 没有单独的字段存储
启用存储 ("store": true)
#
{
"mappings": {
"properties": {
"title": {
"type": "text",
"store": true // 单独存储
},
"content": {
"type": "text",
"store": false // 不单独存储(默认)
}
}
}
}
# 存储结构
_source: {"title": "...", "content": "..."}
stored_fields: {
"title": "Elasticsearch教程" // 单独存储的字段
}
性能影响对比 #
场景1:只需要部分字段 #
# store: false(从_source提取)
GET /my_index/_search
{
"_source": ["title"] // 需要读取完整_source,然后提取title
}
# 性能:读取完整文档 → 解析JSON → 提取字段
# store: true(直接读取)
GET /my_index/_search
{
"stored_fields": ["title"] // 直接读取存储的字段
}
# 性能:直接读取字段值,更快
场景2:大文档优化 #
# 假设文档很大
{
"title": "短标题",
"content": "非常长的内容...(几KB或几MB)",
"metadata": "大量元数据..."
}
# 如果经常只需要title:
{
"title": {
"type": "text",
"store": true // 避免读取整个大文档
},
"content": {
"type": "text",
"store": false // 内容很大,不单独存储
}
}
搜索能力不受影响 #
重要:store 设置不影响搜索能力,只影响字段值的检索方式。
# 无论store是true还是false,都可以正常搜索
GET /my_index/_search
{
"query": {
"match": {
"content": "elasticsearch" // ✅ 正常搜索
}
}
}
# 倒排索引独立于store存储
倒排索引: "elasticsearch" → [doc1, doc3, doc5]
字段存储: 独立的存储机制
3. 实际应用策略 #
性能优化配置 #
1. 日志索引优化 #
PUT /logs
{
"mappings": {
"properties": {
"log_id": {
"type": "keyword",
"index": false, // ID不用于搜索
"store": false // 从_source获取即可
},
"timestamp": {
"type": "date",
"index": true, // 需要时间范围搜索
"store": false
},
"level": {
"type": "keyword",
"index": true, // 需要按级别过滤
"store": false
},
"message": {
"type": "text",
"index": true, // 需要全文搜索
"store": false
},
"full_stack_trace": {
"type": "text",
"index": true, // 需要搜索错误信息
"store": true // 单独存储,避免读取大文档
}
}
}
}
2. 电商商品索引 #
PUT /products
{
"mappings": {
"properties": {
"sku": {
"type": "keyword",
"index": true, // 需要精确搜索SKU
"store": false
},
"title": {
"type": "text",
"index": true, // 标题搜索
"store": true // 经常单独显示,提高性能
},
"description": {
"type": "text",
"index": true, // 描述搜索
"store": false // 描述很长,不单独存储
},
"internal_notes": {
"type": "text",
"index": false, // 内部备注不参与搜索
"store": false
},
"thumbnail_urls": {
"type": "keyword",
"index": false, // 图片URL不搜索
"store": false
}
}
}
}
查询性能对比 #
1. 大文档场景测试 #
# 文档大小:每个文档 50KB
# 测试:获取1000个文档的title字段
# store: false
GET /large_docs/_search
{
"size": 1000,
"_source": ["title"]
}
# 耗时:200ms(需要读取和解析50MB数据)
# store: true
GET /large_docs/_search
{
"size": 1000,
"stored_fields": ["title"]
}
# 耗时:50ms(直接读取存储的字段)
2. 小文档场景 #
# 文档大小:每个文档 1KB
# store设置影响很小,可能store还会略慢(额外存储开销)
4. 最佳实践建议 #
设置原则 #
index 设置原则 #
✅ index: true
- 需要搜索的字段
- 需要聚合的字段
- 需要排序的字段
❌ index: false
- 纯展示字段(ID、URL、元数据)
- 不参与业务查询的字段
- 大量重复值且不需要搜索的字段
store 设置原则 #
✅ store: true
- 经常单独获取的字段
- 文档很大但某些字段经常访问
- 高频访问的小字段
❌ store: false(默认)
- 一般情况下使用默认值
- 文档不大的场景
- 不经常单独访问的字段
实际配置示例 #
新闻网站索引 #
PUT /news
{
"mappings": {
"properties": {
"news_id": {
"type": "keyword",
"index": false // 不搜索,通过URL访问
},
"title": {
"type": "text",
"index": true,
"store": true // 经常单独显示在列表页
},
"summary": {
"type": "text",
"index": true,
"store": true // 摘要经常单独显示
},
"content": {
"type": "text",
"index": true,
"store": false // 内容很长,从_source读取
},
"author": {
"type": "keyword",
"index": true // 需要按作者搜索
},
"publish_date": {
"type": "date",
"index": true // 需要时间范围过滤
},
"internal_tags": {
"type": "keyword",
"index": false // 内部标签,不参与搜索
}
}
}
}
总结 #
关键要点 #
index: false:- 完全无法搜索该字段
- 节省索引空间和构建时间
- 适用于纯展示字段
store: false:- 不影响搜索能力
- 影响字段值获取性能
- 默认设置,适用于大多数场景
store: true:- 搜索能力不变
- 提高大文档中小字段的访问速度
- 增加存储空间开销
选择建议 #
# 常见组合
1. 搜索字段:index: true, store: false(默认)
2. 展示字段:index: false, store: false
3. 热点字段:index: true, store: true
4. 大文档优化:重要字段 store: true,其他 store: false
合理配置这些参数可以显著提升 Elasticsearch 的性能和存储效率!合理配置这些参数可以显著提升 Elasticsearch 的性能和存储效率!
Elasticsearch 副本的作用和使用场景 #
Elasticsearch 的副本(Replica)主要有以下几个用途,您提到的"提供给用户查询但不更新使用"是其中一种常见用法,但副本的作用不止于此:
副本的核心作用 #
高可用性(High Availability)
- 当主分片(Primary Shard)不可用时,副本可以自动提升为主分片,确保服务不中断
- 防止数据丢失,提供故障转移能力
提高查询性能
- 副本可以处理搜索请求,分担主分片的查询负载
- 读操作可以在所有副本间并行执行,提高吞吐量
数据冗余
- 提供数据冗余备份,防止单节点故障导致数据丢失
常见使用模式 #
读写分离模式(您提到的情况)
- 主分片处理所有写请求和部分读请求
- 副本专门用于处理读请求
- 这种模式能有效提高查询性能,同时保持写入一致性
负载均衡模式
- 所有分片(主分片和副本)平等地处理查询请求
- Elasticsearch 会自动在可用分片间路由请求
副本与更新的关系 #
- 写入流程:所有写操作(包括更新)首先在主分片上执行,然后同步到所有副本
- 一致性保证:ES默认采用"quorum"机制,确保写操作在多数副本上成功后才返回响应
- 读取偏好:可以配置搜索请求优先从副本读取(但不影响数据一致性)
实际应用建议 #
生产环境通常设置至少1个副本
PUT /my_index { "settings": { "number_of_replicas": 1 } }对于读多写少的场景:
- 可以增加副本数量(如2-3个)来提高查询能力
- 但会占用更多存储空间和内存
对于写密集场景:
- 可以临时减少副本数量以提高写入性能
- 完成大量写入后再恢复副本数量
副本不是完全"只读"的,它们会接收来自主分片的更新,但在查询负载分配上确实常被用作专门的查询节点。
XIUANGH: 在elasticsearch中:避免深度分页,深度分页是什么意思?为什么用“”“ “search_after”: [1234567890] // 使用search_after替代深度分页”这个代替,这个是什么意思
GitHub Copilot:
Elasticsearch 深度分页问题解析 #
什么是深度分页? #
深度分页是指在大量数据中查询很靠后的页面,比如查询第10000页的数据。在传统的分页方式中,使用 from 和 size 参数:
{
"from": 99990, // 跳过前99990条记录
"size": 10 // 返回10条记录
}
深度分页的问题 #
- 性能问题:ES需要在每个分片上获取
from + size条记录,然后排序并丢弃前面的记录 - 内存消耗:需要在内存中保存大量临时数据
- 默认限制:ES默认限制
from + size不能超过10000
search_after 的工作原理 #
search_after 使用游标式分页,基于上一页最后一条记录的排序值来获取下一页:
// 第一次查询
{
"size": 10,
"sort": [
{ "timestamp": { "order": "desc" } },
{ "_id": { "order": "desc" } } // 确保唯一性
]
}
// 后续查询
{
"size": 10,
"sort": [
{ "timestamp": { "order": "desc" } },
{ "_id": { "order": "desc" } }
],
"search_after": [1234567890, "doc_id_123"] // 上一页最后一条记录的排序值
}
优势对比 #
| 方式 | 性能 | 内存消耗 | 适用场景 |
|---|---|---|---|
| from/size | 随页数增加急剧下降 | 高 | 浅分页、随机跳转 |
| search_after | 稳定 | 低 | 深度分页、顺序遍历 |
search_after 通过记录位置而不是计算偏移量,避免了深度分页的性能问题,特别适合大数据集的顺序遍历场景。