目录
- [一、ElasticSearch 概述](#一、ElasticSearch 概述)
- [1.1 什么是 ElasticSearch](#1.1 什么是 ElasticSearch)
- [1.2 核心优势](#1.2 核心优势)
- [1.3 Elastic Stack 生态](#1.3 Elastic Stack 生态)
- [1.4 典型应用场景](#1.4 典型应用场景)
- 二、快速安装与上手
- [2.1 Windows 安装](#2.1 Windows 安装)
- [2.2 Linux 安装](#2.2 Linux 安装)
- [2.3 Kibana 可视化客户端](#2.3 Kibana 可视化客户端)
- [2.4 中文分词插件 IK](#2.4 中文分词插件 IK)
- 三、索引操作详解
- [3.1 索引的基本操作](#3.1 索引的基本操作)
- [3.2 索引别名](#3.2 索引别名)
- [四、文档 CRUD 操作](#四、文档 CRUD 操作)
- [4.1 新增文档](#4.1 新增文档)
- [4.2 查询文档](#4.2 查询文档)
- [4.3 删除文档](#4.3 删除文档)
- [4.4 更新文档](#4.4 更新文档)
- [4.5 并发更新与乐观锁](#4.5 并发更新与乐观锁)
- 五、数据建模最佳实践
- [5.1 多表关联方案](#5.1 多表关联方案)
- [5.2 建模实践建议](#5.2 建模实践建议)
- 六、实战案例:理财产品检索
- 总结
一、ElasticSearch 概述
1.1 什么是 ElasticSearch
ElasticSearch(简称 ES)是一个开源的分布式搜索和数据分析引擎,基于 Java 开发,是目前最流行的企业级搜索引擎。它专门设计用于处理大规模文本数据,能够实现近实时的搜索性能,通过倒排索引等核心技术提供高效的全文检索能力。
1.2 核心优势
作为 DB-Engines 搜索引擎排名榜首的产品,ElasticSearch 具备以下核心优势:
- 分布式架构:支持水平扩展,轻松处理大规模数据,具备高可扩展性和容错性
- 全文检索:强大的文本搜索和分析能力,支持复杂查询语法和自定义分析器
- 多语言支持:满足不同语言环境下的数据处理和检索需求
- 高性能:采用倒排索引技术,实现高效的搜索和数据处理性能
- 近实时性:从数据写入到可搜索的延迟通常在秒级
- 易用性:丰富的 RESTful API 和插件生态,查询语法简洁明了
1.3 Elastic Stack 生态
Elastic Stack 由四大核心产品组成,提供从数据采集到可视化的一体化解决方案:
| 产品 | 角色 | 说明 |
|---|---|---|
| Elasticsearch | 核心引擎 | 高度可扩展的全文搜索与分析引擎,处理 PB 级数据量 |
| Logstash | 数据处理管道 | 灵活的服务器端数据采集管道,支持从多个源采集、转换数据 |
| Beats | 轻量级采集器 | 包含 Filebeat、Metricbeat、Heartbeat 等,覆盖日志、指标、可用性监控 |
| Kibana | 可视化平台 | 强大的数据可视化和管理界面,支持仪表板、图表及交互式查询 |
1.4 典型应用场景
ElasticSearch 广泛应用于以下三大场景:
- 全文检索:电商平台搜索(淘宝、京东)、应用市场搜索、文档全文检索等。阿里巴巴、腾讯、字节跳动等知名企业都在使用
- 日志分析:用户行为日志、应用日志、系统日志的实时分析。58集团、唯品会等企业用于实时监控和故障排查
- 商业智能:大数据的个性化分析和可视化,辅助业务决策,挖掘商业价值
二、快速安装与上手
2.1 Windows 安装
初学者建议直接安装 Windows 版本,步骤如下:
1. 下载并解压
从官网下载 ES 安装包并解压。ES 目录结构包含 bin(脚本文件)、config(配置文件)、data(数据目录)、logs(日志目录)等核心目录。
2. 配置 JDK 环境
ES 7.0 开始内置 Java 环境。JDK 环境变量生效优先级:ES_JAVA_HOME > ES_HOME。建议虚拟机分配 4G 以上内存,JVM 分配 1G 以上。
3. 修改配置文件
编辑 config/elasticsearch.yml,初学者建议关闭 Security 安全认证以快速上手。
4. 启动服务
进入 bin 目录,执行 elasticsearch.bat 启动 ES。注意 9300 端口为集群通信端口,9200 端口为 HTTP RESTful 访问端口。浏览器访问 http://localhost:9200 验证。
2.2 Linux 安装
Linux 安装与 Windows 类似,但需要注意以下关键差异:
注意: ES 不允许使用 root 账号启动,需要创建专有用户。如果用 root 解压了安装包,需要通过
chown -R user:user更改文件归属。
核心配置项(elasticsearch.yml):
yaml
# 开启远程访问
network.host: 0.0.0.0
# 单节点开发模式(绕过引导检查)
discovery.type: single-node
# 关闭安全认证
xpack.security.enabled: false
开发模式 vs 生产模式
- 开发模式 :使用
discovery.type=single-node配置,绕过引导检查,适合学习使用 - 生产模式:修改集群相关配置后触发,启动前会进行严格的引导检查(JVM 大小、内存锁、虚拟内存、最大线程数等),配置不合理则拒绝启动
生产环境常见启动错误
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
max file descriptors [4096] for elasticsearch process is too low |
系统最大打开文件数限制过低 | 修改 /etc/security/limits.conf,设置 nofile 65536 |
max number of threads [1024] is too low |
用户最大可创建线程数太小 | 修改 20-nproc.conf,设置 nproc 4096 |
max virtual memory areas vm.max_map_count [65530] is too low |
最大虚拟内存太小 | 修改 /etc/sysctl.conf,设置 vm.max_map_count=262144 |
default discovery settings are unsuitable for production |
缺少集群发现配置 | 配置 discovery.seed_hosts 或使用 single-node |
2.3 Kibana 可视化客户端
Kibana 是 Elastic Stack 的可视化和管理平台,与 Elasticsearch 协同工作。
安装步骤:
- 下载并解压 Kibana(版本需与 ES 匹配)
- 修改
config/kibana.yml配置文件
yaml
server.port: 5601
server.host: "0.0.0.0"
elasticsearch.hosts: ["http://localhost:9200"]
i18n.locale: "zh-CN"
启动后访问 http://localhost:5601 即可使用。Kibana 提供了常用的 Cat API 快捷命令:
bash
_cat/health # 查看集群健康状态(红/黄/绿)
_cat/nodes # 查看所有节点信息
_cat/indices # 查看所有索引信息
_cat/shards # 查看分片详情
_cat/count # 查看文档数量
2.4 中文分词插件 IK
Elasticsearch 默认的 standard 分词器对中文支持不佳(单字拆分),需要安装 IK 分词插件。
安装方式
- 在线安装 :
bin/elasticsearch-plugin install analysis-icu - 离线安装 :下载插件包解压到
plugins目录,重启 ES
注意: IK 分词器版本必须与 ES 版本一一对应,否则会导致启动失败。可从 release.infinilabs.com 下载对应版本。
IK 分词器两种模式
| 模式 | 说明 | 示例("中华人民共和国") |
|---|---|---|
ik_smart |
最粗粒度拆分 | 中华人民共和国 |
ik_max_word |
最细粒度拆分 | 中华人民共和国/中华人民/中华/华人/人民共和国/人民/共和国/共和/国 |
推荐做法:索引时使用 ik_max_word 提高召回率,搜索时使用 ik_smart 提高精确率。
json
// 创建索引时指定字段级分词器
PUT /index
POST /index/_mapping
{
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
}
三、索引操作详解
3.1 索引的基本操作
创建索引
json
PUT /student_index
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"name": { "type": "text" },
"age": { "type": "integer" },
"enrolled_date": { "type": "date" }
}
}
}
核心参数说明:
number_of_shards:主分片数,决定索引的并行度和数据分布number_of_replicas:副本数,提高数据可用性和容错能力mappings.properties:字段映射定义,常用类型有text、keyword、integer、float、date等
修改索引
ES 允许动态更新 settings(如副本数)和添加新的 mapping 字段:
json
// 更新副本数
PUT /student_index/_settings
{
"index": {
"number_of_replicas": 2
}
}
// 添加新字段
PUT /student_index/_mapping
{
"properties": {
"grade": { "type": "integer" }
}
}
注意: 已存在的字段不能修改或删除,如需变更需要通过 Reindex 重建数据。因此 Mapping 的设计需要慎重考虑。
删除索引
bash
DELETE /myindex
3.2 索引别名
索引别名是 ES 中非常实用的功能,可以解决以下业务场景:
- 多索引统一检索:面对按日期切分的多个索引,通过别名统一访问
- 零停机索引切换:设计不合理的索引需要更换时,通过别名透明切换
- 数据视图:缩小检索范围,提升效率
创建和使用别名
json
// 创建索引时指定别名
PUT myindex
{
"aliases": {
"myindex_alias": {}
}
}
// 为已有索引添加别名
POST /_aliases
{
"actions": [
{ "add": { "index": "tlmall_logs_202401", "alias": "tlmall_logs_2024" } },
{ "add": { "index": "tlmall_logs_202402", "alias": "tlmall_logs_2024" } },
{ "add": { "index": "tlmall_logs_202403", "alias": "tlmall_logs_2024" } }
]
}
// 通过别名检索(等同于同时检索三个索引)
POST tlmall_logs_2024/_search
提示: 索引别名只是物理索引的软链接名称,别名检索和直接索引检索效率一致。推荐在检索时使用别名,写入时使用物理索引。
四、文档 CRUD 操作
4.1 新增文档
单个文档新增
ES 8.x 中,使用 POST 或 PUT 请求新增文档:
json
// 指定 ID 新增(PUT 或 POST 均可)
PUT /employee/_doc/1
{
"name": "张三",
"sex": 1,
"age": 25,
"address": "广州天河公园",
"remark": "java developer"
}
// 不指定 ID 新增(仅 POST,ES 自动生成 ID)
POST /employee/_doc
{
"name": "李四",
"sex": 1,
"age": 28,
"address": "广州荔湾大厦",
"remark": "java assistant"
}
| 特性 | PUT | POST |
|---|---|---|
| 文档 ID | 必须指定 | 可选(不指定则自动生成) |
| 幂等性 | 幂等 | 非幂等 |
| 更新行为 | 替换整个文档 | 可使用 _update 部分更新 |
批量新增(Bulk API)
json
POST /employee/_bulk
{"index":{"_index":"employee","_id":"1"}}
{"name":"张三","sex":1,"age":25,"address":"广州天河公园","remark":"java developer"}
{"index":{"_index":"employee","_id":"2"}}
{"name":"李四","sex":1,"age":28,"address":"广州荔湾大厦","remark":"java assistant"}
{"index":{"_index":"employee","_id":"3"}}
{"name":"王五","sex":0,"age":26,"address":"广州白云山公园","remark":"php developer"}
Bulk API 支持四种操作类型:Index(创建/替换)、Create(不存在则创建)、Update(更新)、Delete(删除)。
4.2 查询文档
根据 ID 查询
json
// 单个文档
GET /employee/_doc/1
// 批量查询(Multi GET)
GET /employee/_mget
{
"ids": ["1", "2", "3"]
}
条件查询(Query DSL)
json
// 匹配所有文档
GET /employee/_search
{
"query": { "match_all": {} }
}
// 全文检索(match 查询)
GET /employee/_search
{
"query": {
"match": { "address": "广州白云山" }
}
}
// 精确匹配(term 查询)
GET /employee/_search
{
"query": {
"term": { "name": "张三" }
}
}
// 范围查询
GET /employee/_search
{
"query": {
"range": {
"age": { "gte": 20, "lte": 26 }
}
}
}
4.3 删除文档
json
// 删除单个文档
DELETE /employee/_doc/1
// 批量删除(Bulk API)
POST _bulk
{"delete":{"_index":"employee","_id":3}}
{"delete":{"_index":"employee","_id":4}}
// 按条件删除
POST /employee/_delete_by_query
{
"query": {
"match": { "address": "广州" }
}
}
4.4 更新文档
json
// 更新单个文档(部分更新)
POST /employee/_update/1
{
"doc": { "age": 28 }
}
// 批量更新(Bulk API)
POST _bulk
{"update":{"_index":"employee","_id":3}}
{"doc":{"age":29}}
{"update":{"_index":"employee","_id":4}}
{"doc":{"age":27}}
// 按条件更新(使用 Painless 脚本)
POST /employee/_update_by_query
{
"query": {
"term": { "name": "张三" }
},
"script": {
"source": "ctx._source.age = 30",
"lang": "painless"
}
}
4.5 并发更新与乐观锁
在 ES 7.x 及以后版本中,使用 _seq_no 和 _primary_term 实现乐观锁机制,取代了旧版的 _version 字段:
json
// 带乐观锁的更新
POST /employee/_doc/1?if_seq_no=13&if_primary_term=1
{
"name": "张三xxxx",
"sex": 1,
"age": 25
}
如果 _seq_no 和 _primary_term 不匹配,ES 会返回 version_conflict_engine_exception(HTTP 409),确保高并发环境下文档的一致性。
五、数据建模最佳实践
5.1 多表关联方案
Elasticsearch 不擅长处理关联关系,一般采用以下四种方案:
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 嵌套对象(Nested) | 一对少量、子文档偶尔更新、查询频繁 | 文档存储在一起,读取性能高 | 更新子文档需重建整个文档,查询较慢 |
| Join 父子文档 | 一对多量、子文档更新频繁 | 父子文档可独立更新,互不影响 | 维护关系耗费内存,查询性能比 Nested 更差 |
| 宽表冗余存储 | 一对多或多对多关联 | 以空间换时间,速度快 | 字段冗余造成存储浪费,聚合结果可能不准确 |
| 业务端关联 | 数据量少的多表关联 | 数据量少时用户体验好 | 数据量多时两次查询耗时影响体验 |
Nested 嵌套对象示例
当使用普通 Object 类型存储对象数组时,JSON 会被扁平化处理,导致跨字段查询产生意外结果。Nested 类型可以让数组中的每个对象被独立索引:
json
// 创建 Nested Mapping
PUT /my_movies
{
"mappings": {
"properties": {
"actors": {
"type": "nested",
"properties": {
"first_name": { "type": "keyword" },
"last_name": { "type": "keyword" }
}
},
"title": {
"type": "text",
"fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }
}
}
}
}
// Nested 查询
POST /my_movies/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Speed" } },
{
"nested": {
"path": "actors",
"query": {
"bool": {
"must": [
{ "match": { "actors.first_name": "Keanu" } },
{ "match": { "actors.last_name": "Reeves" } }
]
}
}
}
}
]
}
}
}
Join 父子文档示例
json
// 设定 Parent/Child Mapping
PUT /my_blogs
{
"mappings": {
"properties": {
"blog_comments_relation": {
"type": "join",
"relations": { "blog": "comment" }
},
"content": { "type": "text" },
"title": { "type": "keyword" }
}
}
}
// 索引父文档
PUT /my_blogs/_doc/blog1
{
"title": "Learning Elasticsearch",
"content": "learning ELK",
"blog_comments_relation": { "name": "blog" }
}
// 索引子文档(必须指定 routing 保证同分片)
PUT /my_blogs/_doc/comment1?routing=blog1
{
"comment": "I am learning ELK",
"username": "Jack",
"blog_comments_relation": {
"name": "comment",
"parent": "blog1"
}
}
// Has Child 查询(返回满足条件的父文档)
POST /my_blogs/_search
{
"query": {
"has_child": {
"type": "comment",
"query": { "match": { "username": "Jack" } }
}
}
}
设计建议: 尽量突破关系型数据库的思维定式,优先使用扁平的宽表文档模型,在文档建模处多下功夫以提升检索效率。
5.2 建模实践建议
关联关系处理策略
- Object:优先考虑反范式(Denormalization)
- Nested:数据包含多数值对象,且有查询需求时使用
- Child/Parent:关联文档更新非常频繁时使用
避免过多字段
文档字段过多不易维护,Mapping 信息保存在 Cluster State 中会影响集群性能。默认最大字段数为 1000。生产环境中建议关闭 Dynamic Mapping:
json
PUT /user
{
"mappings": {
"dynamic": "strict",
"properties": {
"name": { "type": "text" },
"address": {
"type": "object",
"dynamic": "true"
}
}
}
}
"dynamic": true(默认):未知字段自动加入"dynamic": false:新字段不索引,但保存在_source"dynamic": strict:新增字段会导致文档写入失败
避免正则/通配符/前缀查询
这类查询性能较差,特别是通配符开头的查询会导致性能灾难。建议将字符串转为结构化对象存储(如版本号拆分为 major/minor/hot_fix 字段)。
避免空值引起聚合不准
json
PUT /scores
{
"mappings": {
"properties": {
"score": {
"type": "float",
"null_value": 0
}
}
}
}
为 Mapping 加入 Meta 信息
建议对 Mapping 加入版本信息,便于版本管理,可以考虑将 Mapping 文件上传 Git:
json
PUT /my_index
{
"mappings": {
"_meta": {
"index_version_mapping": "1.1"
}
}
}
六、实战案例:理财产品检索
以某金融企业理财产品检索功能为例,演示完整的 ES 使用流程。
1. 创建索引
json
PUT /product_info
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"productName": {
"type": "text",
"analyzer": "ik_smart"
},
"annual_rate": { "type": "keyword" },
"describe": {
"type": "text",
"analyzer": "ik_smart"
}
}
}
}
2. 批量插入数据
json
POST /product_info/_bulk
{"index":{}}
{"productName":"理财产品A","annual_rate":"3.2200%","describe":"180天定期理财,最低20000起投,收益稳定,可以自助选择消息推送"}
{"index":{}}
{"productName":"理财产品B","annual_rate":"3.1100%","describe":"90天定投产品,最低10000起投,每天收益到账消息推送"}
{"index":{}}
{"productName":"理财产品C","annual_rate":"3.3500%","describe":"270天定投产品,最低40000起投,每天收益立即到账消息推送"}
3. 搜索数据
json
// 全文搜索:描述包含"每天收益到账消息推送"的产品
GET /product_info/_search
{
"query": {
"match": { "describe": "每天收益到账消息推送" }
}
}
// 范围查询:年化率在 3.0000% 到 3.1300% 之间的产品
GET /product_info/_search
{
"query": {
"range": {
"annual_rate": {
"gte": "3.0000%",
"lte": "3.1300%"
}
}
}
}
通过以上案例可以看到,ElasticSearch 从索引创建、数据写入到搜索查询的完整流程非常简洁高效,配合 IK 分词器和合理的 Mapping 设计,可以快速构建强大的搜索功能。
总结
本文从 ElasticSearch 的基本概念出发,详细介绍了安装配置、索引管理、文档 CRUD 操作以及数据建模的最佳实践。核心要点回顾:
- Elastic Stack 提供了从数据采集到可视化的一体化方案
- IK 分词器 是中文搜索的必备插件,索引时用
ik_max_word,搜索时用ik_smart - 索引别名 可以实现零停机切换和多索引统一检索
- Bulk API 是批量操作的高效方式
- 乐观锁 机制通过
_seq_no和_primary_term保证并发安全 - 数据建模 应优先使用宽表模型,避免在 ES 中做多表关联
- Mapping 设计 需要前瞻性考虑,加入 Meta 信息并纳入版本管理
希望这篇指南能帮助你快速上手 ElasticSearch,在实际项目中构建高效的搜索服务!