Elasticsearch 基础
Elasticsearch 是一个基于 Apache Lucene 的分布式、RESTful 风格的搜索和分析引擎,能够解决不断涌现的数据用例。作为 Elastic Stack(ELK)的核心组件,它以 JSON 文档形式存储数据,提供近实时的全文搜索能力,广泛应用于日志分析、搜索引擎、数据分析等场景。
核心概念
什么是 Elasticsearch
Elasticsearch(简称 ES)是一个高度可扩展的开源全文搜索和分析引擎。它允许用户快速地存储、搜索和分析大量数据。ES 的核心特点包括:
- 分布式架构:天然支持横向扩展,数据自动分布到多个节点
- RESTful API:使用标准 HTTP 方法(GET、POST、PUT、DELETE)操作数据
- 近实时搜索:数据写入后通常在 1 秒内即可被搜索到
- JSON 文档模型:数据以 JSON 格式存储,结构灵活,支持动态映射
- 全文搜索:基于 Lucene 提供强大的全文检索、模糊搜索、高亮等功能
- 多语言支持:内置分词器支持中文、英文等多种语言的文本分析
与传统数据库对比
为了便于理解,可以将 Elasticsearch 与关系型数据库做如下类比:
| Elasticsearch | MySQL | 说明 |
|---|---|---|
| Index | Database | 索引(数据库) |
| Type | Table | 类型(表),ES 7.x 后已废弃,8.x 中完全移除 |
| Document | Row | 文档(行),JSON 格式 |
| Field | Column | 字段(列) |
| Mapping | Schema | 映射(表结构定义) |
| Query DSL | SQL | 查询语言 |
| Node | Server instance | 节点(服务器实例) |
| Shard | Partition | 分片(数据分区) |
| Replica | Replica | 副本(数据备份) |
注意
从 Elasticsearch 7.x 开始,Type 概念已被标记为废弃。在 8.x 中完全移除了 Type 相关 API。每个 Index 只能包含一个 Mapping 类型(_doc),类似于每个数据库只有一张表的概念被取消。设计索引时需要合理规划,避免将不同类型的文档混在同一个索引中。
倒排索引原理
正排索引与倒排索引
理解 Elasticsearch 的搜索能力,核心在于理解倒排索引(Inverted Index)。
正排索引(Forward Index)是传统数据库使用的方式——以文档为基本单位,存储每个文档包含哪些词:
文档ID → [词1, 词2, 词3, ...]倒排索引(Inverted Index)以词为基本单位,记录哪些文档包含该词:
词 → [文档ID1, 文档ID2, 文档ID3, ...]倒排索引结构
倒排索引由以下三个核心部分组成:
- Term Dictionary(词典):存储经过分词器处理后的所有唯一词项(Term),并按字典序排列
- Term Index(词典索引):为词典建立的前缀索引(类似字典的目录页),帮助快速定位词项在词典中的位置,通常存储在内存中
- Posting List(倒排列表):记录包含某个词项的所有文档 ID、词频(TF)、位置信息等
用户搜索 "PHP 教程"
↓
1. 分词器将查询拆分为 ["php", "教程"]
↓
2. 在 Term Dictionary 中查找 "php" 和 "教程"
↓
3. Term Index 辅助快速定位
↓
4. 获取对应的 Posting List
"php" → [Doc1, Doc3, Doc5, Doc7]
"教程" → [Doc3, Doc5, Doc8]
↓
5. 取交集:[Doc3, Doc5]
↓
6. 根据相关性评分排序,返回结果分词器(Analyzer)
分词器是文本分析的核心组件,负责将一段文本拆分成独立的词项。一个完整的分词器包含三个部分:
- Character Filters(字符过滤器):在分词之前对文本进行预处理,如去除 HTML 标签、替换特殊字符
- Tokenizer(分词器):将文本按照规则切分为词项
- Token Filters(词项过滤器):对分词结果进行后处理,如转小写、去除停用词、同义词转换
// 分词器处理流程示例
原文:"Hello World, PHP教程"
↓ Character Filter(去除标点)
"Hello World PHP教程"
↓ Tokenizer(按空格和字符切分)
["Hello", "World", "PHP教程"]
↓ Token Filter(转小写)
["hello", "world", "php教程"]Elasticsearch 内置了多种分词器:
| 分词器 | 说明 | 适用场景 |
|---|---|---|
standard | 默认分词器,基于 Unicode 文本分段算法 | 通用英文文本 |
simple | 按非字母字符分词,转小写 | 简单英文处理 |
whitespace | 仅按空格分词 | 需要精确匹配的场景 |
keyword | 不分词,整个文本作为一个词项 | ID、精确值字段 |
pattern | 使用正则表达式分词 | 特殊分隔符文本 |
icu_collation | ICU 排序规则分词 | 多语言排序 |
ik_max_word | IK 分词器(插件),最细粒度切分 | 中文搜索 |
ik_smart | IK 分词器(插件),智能切分 | 中文粗粒度搜索 |
中文分词
Elasticsearch 默认的分词器对中文支持不好(会将每个汉字作为独立词项)。生产环境中,中文分词通常使用 IK Analysis Plugin:
# 安装 IK 分词器(版本需与 ES 版本一致)
./bin/elasticsearch-plugin install analysis-ikik_max_word 会将文本做最细粒度的拆分,ik_smart 会做最粗粒度的拆分。
集群架构
节点类型
Elasticsearch 集群由多个节点组成,不同节点承担不同角色:
| 节点角色 | 配置 | 职责 |
|---|---|---|
| Master-eligible node | node.master: true(默认) | 参与集群管理、索引创建、分片分配 |
| Data node | node.data: true(默认) | 存储数据、执行 CRUD 和搜索操作 |
| Coordinating node | node.master: false, node.data: false | 协调节点,分发请求、合并结果 |
| Ingest node | node.ingest: true(默认) | 数据预处理管道 |
| Machine learning node | xpack.ml.enabled: true | 运行机器学习任务 |
# elasticsearch.yml 节点角色配置示例
# 专用 Master 节点(不存数据)
node.master: true
node.data: false
node.ingest: false
node.ml: false
# 专用 Data 节点
node.master: false
node.data: true
node.ingest: false
# 专用 Coordinating 节点
node.master: false
node.data: false
node.ingest: false生产环境建议
- 至少 3 个 Master-eligible 节点,避免脑裂(Split-brain)
- 数据节点和主节点分离部署
- 使用 Coordinating 节点分担查询压力
- 每个节点单一角色,避免职责混乱
分片与副本
Elasticsearch 将每个索引划分为多个分片(Shard),每个分片是一个独立的 Lucene 实例。
- Primary Shard(主分片):数据的原始存储,索引创建后主分片数不可更改
- Replica Shard(副本分片):主分片的复制品,用于提高查询吞吐量和容灾
Index: products (3 primary shards, 1 replica)
Node 1 Node 2 Node 3
┌───────────┐ ┌───────────┐ ┌───────────┐
│ P0 R1 │ │ P1 R0 │ │ P2 R2 │
│ │ │ │ │ │
│ data shard│ │ data shard│ │ data shard│
│ │ │ │ │ │
└───────────┘ └───────────┘ └───────────┘
P0-P2 = Primary Shards (主分片)
R0-R2 = Replica Shards (副本分片)路由机制:文档通过 routing 参数决定存储到哪个分片,默认使用文档 _id 进行路由计算:
shard = hash(routing) % num_primary_shards集群状态
Elasticsearch 集群有三种健康状态:
| 状态 | 颜色 | 说明 |
|---|---|---|
| Green | 绿色 | 所有主分片和副本分片都正常分配 |
| Yellow | 黄色 | 所有主分片正常,但有副本分片未分配 |
| Red | 红色 | 有主分片未分配,部分数据不可用 |
# 查看集群健康状态
curl -X GET "localhost:9200/_cluster/health?pretty"
# 响应示例
{
"cluster_name" : "my-application",
"status" : "green",
"number_of_nodes" : 3,
"number_of_data_nodes" : 2,
"active_primary_shards" : 10,
"active_shards" : 20,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 0,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0
}安装与配置
系统要求
在部署 Elasticsearch 之前,需要确认系统满足以下要求:
| 项目 | 最低要求 | 推荐配置 |
|---|---|---|
| JDK | OpenJDK 17+ | OpenJDK 21(ES 8.x 内置,无需单独安装) |
| 内存 | 最低 2GB | 每个节点 16GB+ |
| CPU | 2 核 | 8 核+ |
| 磁盘 | SSD 推荐 | NVMe SSD,预留 50% 空间 |
| 操作系统 | Linux / macOS / Windows | Linux(CentOS / Ubuntu LTS) |
内存配置关键原则
- 将 JVM 堆内存(
-Xmx)设置为物理内存的 50%,但不超过 32GB - 不要超过物理内存的 50%,因为 ES 还需要内存给 Lucene 的文件系统缓存
- 如果堆内存恰好是 31GB,JVM 会启用压缩指针(Compressed Oops),比 32GB 更高效
- 使用
swapoff -a禁用交换分区,或设置vm.swappiness=1
安装方式
方式一:使用 Docker 安装(推荐开发环境)
# 创建 Docker 网络
docker network create es-network
# 单节点模式
docker run -d \
--name elasticsearch \
--network es-network \
-p 9200:9200 \
-p 9300:9300 \
-e "discovery.type=single-node" \
-e "ES_JAVA_OPTS=-Xms1g -Xmx1g" \
-e "xpack.security.enabled=false" \
-e "xpack.security.enrollment.enabled=false" \
docker.elastic.co/elasticsearch/elasticsearch:8.12.0
# 验证安装
curl http://localhost:9200方式二:Docker Compose 三节点集群
# docker-compose.yml
version: '3.8'
services:
es-node1:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
container_name: es-node1
environment:
- node.name=es-node1
- cluster.name=php-es-cluster
- discovery.seed_hosts=es-node2,es-node3
- cluster.initial_master_nodes=es-node1,es-node2,es-node3
- ES_JAVA_OPTS=-Xms1g -Xmx1g
- xpack.security.enabled=false
- bootstrap.memory_lock=true
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es-data1:/usr/share/elasticsearch/data
ports:
- "9200:9200"
networks:
- es-network
es-node2:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
container_name: es-node2
environment:
- node.name=es-node2
- cluster.name=php-es-cluster
- discovery.seed_hosts=es-node1,es-node3
- cluster.initial_master_nodes=es-node1,es-node2,es-node3
- ES_JAVA_OPTS=-Xms1g -Xmx1g
- xpack.security.enabled=false
- bootstrap.memory_lock=true
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es-data2:/usr/share/elasticsearch/data
networks:
- es-network
es-node3:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
container_name: es-node3
environment:
- node.name=es-node3
- cluster.name=php-es-cluster
- discovery.seed_hosts=es-node1,es-node2
- cluster.initial_master_nodes=es-node1,es-node2,es-node3
- ES_JAVA_OPTS=-Xms1g -Xmx1g
- xpack.security.enabled=false
- bootstrap.memory_lock=true
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es-data3:/usr/share/elasticsearch/data
networks:
- es-network
kibana:
image: docker.elastic.co/kibana/kibana:8.12.0
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://es-node1:9200
ports:
- "5601:5601"
networks:
- es-network
depends_on:
- es-node1
volumes:
es-data1:
es-data2:
es-data3:
networks:
es-network:
driver: bridge# 启动集群
docker compose up -d
# 查看集群状态
curl -X GET "http://localhost:9200/_cluster/health?pretty"方式三:Linux 直接安装
# 1. 下载并安装 GPG 密钥
rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
# 2. 添加 Elasticsearch 仓库
cat > /etc/yum.repos.d/elasticsearch.repo << 'EOF'
[elasticsearch]
name=Elasticsearch repository for 8.x packages
baseurl=https://artifacts.elastic.co/packages/8.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
EOF
# 3. 安装
yum install -y elasticsearch
# 4. 配置 JVM 内存
vim /etc/elasticsearch/jvm.options
# 修改以下行
# -Xms4g
# -Xmx4g
# 5. 启动并设置开机自启
systemctl daemon-reload
systemctl enable elasticsearch
systemctl start elasticsearch
# 6. 验证
curl http://localhost:9200核心配置文件
Elasticsearch 的主配置文件是 elasticsearch.yml:
# ==================================== 集群配置 ====================================
# 集群名称,同一集群的所有节点必须相同
cluster.name: php-es-cluster
# 节点名称
node.name: node-1
# 节点角色
node.roles: [ master, data ]
# 数据和日志存储路径
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
# ==================================== 网络配置 ====================================
# 绑定的网络地址
network.host: 0.0.0.0
# HTTP 端口
http.port: 9200
# 传输端口(节点间通信)
transport.port: 9300
# ==================================== 发现与集群管理 ====================================
# 集群中发现其他节点
discovery.seed_hosts: ["host1:9300", "host2:9300"]
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
# ==================================== 内存配置 ====================================
# 启动时锁定内存(防止被 swap)
bootstrap.memory_lock: true
# ==================================== 索引配置 ====================================
# 默认分片数
index.number_of_shards: 3
index.number_of_replicas: 1
# ==================================== 安全配置 ====================================
# 开启安全认证(生产环境必须)
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: trueJVM 配置
# /etc/elasticsearch/jvm.options
# 堆内存大小(建议不超过物理内存的 50%,且不超过 32GB)
-Xms4g
-Xmx4g
# GC 配置(ES 8.x 默认使用 G1GC)
# G1GC 适用于大堆内存场景
-XX:+UseG1GC
# GC 日志
-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,uptime,pid,tags:filecount=32,filesize=64m
# 堆外内存限制
-XX:MaxDirectMemorySize=2g核心操作 API
索引操作
# 创建索引
curl -X PUT "localhost:9200/products" -H 'Content-Type: application/json' -d '{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "ik_max_word" },
"description": { "type": "text", "analyzer": "ik_smart" },
"price": { "type": "float" },
"category": { "type": "keyword" },
"created_at": { "type": "date" },
"is_active": { "type": "boolean" }
}
}
}'
# 查看索引信息
curl -X GET "localhost:9200/products?pretty"
# 查看索引映射
curl -X GET "localhost:9200/products/_mapping?pretty"
# 删除索引
curl -X DELETE "localhost:9200/products"
# 关闭 / 打开索引
curl -X POST "localhost:9200/products/_close"
curl -X POST "localhost:9200/products/_open"
# 查看所有索引
curl -X GET "localhost:9200/_cat/indices?v"文档操作
# 创建文档(指定 ID)
curl -X PUT "localhost:9200/products/_doc/1" -H 'Content-Type: application/json' -d '{
"title": "PHP 高级编程",
"description": "深入理解 PHP 核心特性与设计模式",
"price": 89.90,
"category": "编程书籍",
"created_at": "2024-01-15",
"is_active": true
}'
# 创建文档(自动生成 ID)
curl -X POST "localhost:9200/products/_doc" -H 'Content-Type: application/json' -d '{
"title": "Laravel 从入门到精通",
"description": "Laravel 框架全面教程",
"price": 69.90,
"category": "编程书籍",
"created_at": "2024-02-20",
"is_active": true
}'
# 获取文档
curl -X GET "localhost:9200/products/_doc/1?pretty"
# 更新文档(部分更新)
curl -X POST "localhost:9200/products/_update/1" -H 'Content-Type: application/json' -d '{
"doc": {
"price": 79.90
}
}'
# 删除文档
curl -X DELETE "localhost:9200/products/_doc/1"
# 批量操作(_bulk API)
curl -X POST "localhost:9200/_bulk" -H 'Content-Type: application/json' -d '
{"index": {"_index": "products", "_id": "2"}}
{"title": "Redis 实战", "description": "Redis 深度学习指南", "price": 59.90, "category": "编程书籍"}
{"index": {"_index": "products", "_id": "3"}}
{"title": "Docker 容器化部署", "description": "Docker 实践教程", "price": 49.90, "category": "运维"}
{"delete": {"_index": "products", "_id": "2"}}
'基础查询
# 匹配所有文档
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '{
"query": {
"match_all": {}
}
}'
# 全文搜索(match)
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '{
"query": {
"match": {
"title": "PHP 编程"
}
}
}'
# 精确匹配(term)
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '{
"query": {
"term": {
"category": "编程书籍"
}
}
}'
# 范围查询
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '{
"query": {
"range": {
"price": {
"gte": 50.0,
"lte": 100.0
}
}
}
}'
# 布尔查询
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"must": [
{ "match": { "description": "教程" } }
],
"filter": [
{ "term": { "category": "编程书籍" } },
{ "range": { "price": { "gte": 50 } } }
]
}
}
}'数据类型详解
Elasticsearch 支持丰富的数据类型,正确选择数据类型对性能和功能至关重要。
核心数据类型
{
"mappings": {
"properties": {
"string_text": { "type": "text" },
"string_keyword": { "type": "keyword" },
"integer_field": { "type": "integer" },
"long_field": { "type": "long" },
"float_field": { "type": "float" },
"double_field": { "type": "double" },
"boolean_field": { "type": "boolean" },
"date_field": { "type": "date" },
"binary_field": { "type": "binary" }
}
}
}text vs keyword
这是 Elasticsearch 中最容易混淆的两个字符串类型:
| 特性 | text | keyword |
|---|---|---|
| 用途 | 全文搜索 | 精确匹配、排序、聚合 |
| 分词 | 会分词 | 不分词 |
| 索引 | 建立倒排索引 | 建立正排索引(Doc Values) |
| 查询 | match、match_phrase | term、terms |
| 长度限制 | 无严格限制 | 默认最大 256 字符(可调整 ignore_above) |
// 同时支持全文搜索和精确匹配
{
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
},
"autocomplete": {
"type": "search_as_you_type"
}
},
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
// 搜索全文:title
// 精确匹配:title.keyword
// 自动补全:title.autocomplete数值类型
| 类型 | 范围 | 字节数 |
|---|---|---|
byte | -128 ~ 127 | 1 |
short | -32768 ~ 32767 | 2 |
integer | -2^31 ~ 2^31-1 | 4 |
long | -2^63 ~ 2^63-1 | 8 |
float | 单精度 32 位 IEEE 754 | 4 |
double | 双精度 64 位 IEEE 754 | 8 |
half_float | 半精度 16 位 IEEE 754 | 2 |
scaled_float | 缩放浮点数(如价格) | 可变 |
价格字段建议
对于价格字段,推荐使用 scaled_float 配合 scaling_factor,或直接使用 long 类型存储分为单位(如 ¥89.90 存为 8990),避免浮点精度问题:
{ "price_cents": { "type": "long" } }
// 或
{ "price": { "type": "scaled_float", "scaling_factor": 100 } }日期类型
{
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}支持的日期格式:
epoch_millis:毫秒时间戳(推荐,最精确)epoch_second:秒时间戳- 自定义格式,用
||分隔多种格式 - 内置格式:
strict_date_optional_time、basic_date等
复杂数据类型
{
"properties": {
// 对象类型(嵌套 JSON 对象)
"author": {
"properties": {
"name": { "type": "keyword" },
"email": { "type": "keyword" }
}
},
// 数组类型(同类型数组,直接使用即可)
"tags": { "type": "keyword" },
// 嵌套类型(独立对象,解决对象数组扁平化问题)
"comments": {
"type": "nested",
"properties": {
"content": { "type": "text" },
"rating": { "type": "integer" },
"user": { "type": "keyword" }
}
},
// 地理坐标类型
"location": { "type": "geo_point" },
// 地理形状类型
"area": { "type": "geo_shape" },
// IP 类型
"ip_address": { "type": "ip" },
// 自动补全类型
"suggest": { "type": "completion" }
}
}nested vs object
object类型会将嵌套对象扁平化存储,导致对象间的关联关系丢失- 例如
comments数组中两条评论的user和content会失去关联 nested类型将每个嵌套对象作为独立文档存储,可保持对象内部字段的关系- 但
nested查询性能较差,非必要不要使用
实战示例:构建商品搜索引擎
以下是一个完整的商品搜索引擎初始化示例,展示如何从零开始设计索引。
# Step 1: 创建商品索引
curl -X PUT "localhost:9200/products" -H 'Content-Type: application/json' -d '{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"pinyin_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": ["lowercase", "my_pinyin"]
}
},
"filter": {
"my_pinyin": {
"type": "pinyin",
"keep_first_letter": true,
"keep_separate_first_letter": false,
"keep_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"lowercase": true
}
}
}
},
"mappings": {
"properties": {
"product_id": {
"type": "keyword"
},
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 128 },
"pinyin": {
"type": "text",
"analyzer": "pinyin_analyzer"
},
"completion": {
"type": "completion",
"analyzer": "ik_max_word"
}
}
},
"description": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"brand": {
"type": "keyword"
},
"category_path": {
"type": "keyword"
},
"price": {
"type": "scaled_float",
"scaling_factor": 100
},
"original_price": {
"type": "scaled_float",
"scaling_factor": 100
},
"sales_count": {
"type": "integer"
},
"rating": {
"type": "float"
},
"stock": {
"type": "integer"
},
"is_on_sale": {
"type": "boolean"
},
"tags": {
"type": "keyword"
},
"attributes": {
"type": "nested",
"properties": {
"name": { "type": "keyword" },
"value": { "type": "keyword" }
}
},
"shop_id": {
"type": "keyword"
},
"location": {
"type": "geo_point"
},
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
},
"updated_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
}
}
}
}'
# Step 2: 插入测试数据
curl -X POST "localhost:9200/products/_bulk" -H 'Content-Type: application/json' -d '
{"index":{"_id":"1001"}}
{"product_id":"SKU-1001","title":"PHP高级编程指南","description":"涵盖PHP8新特性、设计模式、性能优化的全面指南","brand":"技术出版社","category_path":["编程","PHP"],"price":89.90,"original_price":129.90,"sales_count":1523,"rating":4.8,"stock":500,"is_on_sale":true,"tags":["PHP","编程","后端"],"shop_id":"SHOP-001","location":{"lat":39.9087,"lon":116.3975},"created_at":"2024-01-15 10:30:00","updated_at":"2024-06-01 15:20:00"}
{"index":{"_id":"1002"}}
{"product_id":"SKU-1002","title":"Laravel框架实战教程","description":"从零开始学习Laravel框架,包含项目实战案例","brand":"技术出版社","category_path":["编程","Laravel"],"price":69.90,"original_price":99.90,"sales_count":2341,"rating":4.9,"stock":800,"is_on_sale":true,"tags":["Laravel","PHP","框架"],"shop_id":"SHOP-001","location":{"lat":39.9087,"lon":116.3975},"created_at":"2024-02-20 14:00:00","updated_at":"2024-05-28 09:10:00"}
{"index":{"_id":"1003"}}
{"product_id":"SKU-1003","title":"MySQL数据库优化手册","description":"MySQL索引优化、SQL调优、主从复制全解析","brand":"数据库出版社","category_path":["编程","数据库","MySQL"],"price":79.90,"original_price":109.90,"sales_count":987,"rating":4.7,"stock":300,"is_on_sale":true,"tags":["MySQL","数据库","优化"],"shop_id":"SHOP-002","location":{"lat":31.2304,"lon":121.4737},"created_at":"2024-03-10 08:45:00","updated_at":"2024-06-05 11:30:00"}
'
# Step 3: 测试搜索
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"must": [
{ "match": { "title": "PHP" } }
],
"filter": [
{ "range": { "price": { "gte": 50, "lte": 100 } } }
],
"should": [
{ "term": { "tags": "编程" } },
{ "range": { "rating": { "gte": 4.5 } } }
]
}
},
"sort": [
{ "_score": "desc" },
{ "sales_count": "desc" }
],
"highlight": {
"fields": {
"title": {}
}
}
}'注意事项
近实时机制
Elasticsearch 默认每 1 秒执行一次 refresh 操作,将内存中的索引数据写入新的 Lucene Segment。这意味着写入的数据不会立刻被搜索到:
# 修改 refresh 间隔(需要权衡搜索实时性和写入性能)
# 创建索引时设置
curl -X PUT "localhost:9200/products" -H 'Content-Type: application/json' -d '{
"settings": {
"index.refresh_interval": "30s"
}
}'
# 临时禁用 refresh(大批量导入时使用)
curl -X PUT "localhost:9200/products/_settings" -H 'Content-Type: application/json' -d '{
"index": {
"refresh_interval": "-1"
}
}'
# 导入完成后恢复
curl -X PUT "localhost:9200/products/_settings" -H 'Content-Type: application/json' -d '{
"index": {
"refresh_interval": "1s"
}
}'写入优化
# 批量写入时的优化配置
curl -X PUT "localhost:9200/products/_settings" -H 'Content-Type: application/json' -d '{
"index": {
"refresh_interval": "-1",
"number_of_replicas": 0,
"translog.durability": "async",
"translog.sync_interval": "5s"
}
}'
# 写入完成后恢复
curl -X PUT "localhost:9200/products/_settings" -H 'Content-Type: application/json' -d '{
"index": {
"refresh_interval": "1s",
"number_of_replicas": 1,
"translog.durability": "request"
}
}'最佳实践
- 索引设计:根据业务场景合理规划分片数量,单个分片建议不超过 50GB
- 数据类型:精确匹配使用
keyword,全文搜索使用text;价格使用scaled_float或long - 映射预定义:生产环境禁止使用动态映射(
dynamic: false或strict) - 批量操作:写入时使用
_bulkAPI,每批 1000~5000 条文档 - 资源规划:JVM 堆内存不超过物理内存 50%,预留空间给文件系统缓存
下一节
继续学习:PHP 客户端操作