Elasticsearch 实战指南
Elasticsearch 是搜索、日志分析领域的事实标准,也是 CSDN 后端与大数据板块的高流量话题。本文从核心概念讲起,覆盖索引与文档、分词器、查询 DSL、聚合分析、Spring Boot 集成、日志平台(ELK)、性能优化与常见坑,全程带命令和代码。
一、Elasticsearch 是什么
1.1 定位
基于 Lucene 的分布式搜索引擎------本质是一个"存文档 + 全文检索 + 聚合分析"的 NoSQL 数据库。
和 MySQL 的区别:
| 维度 | MySQL | Elasticsearch |
|---|---|---|
| 查询 | 精确匹配、关系型 | 全文检索、模糊搜索 |
| 性能 | 千万级还行 | 亿级毫秒响应 |
| 扩展 | 分库分表麻烦 | 天然分布式 |
| 一致性 | 强一致(事务) | 近实时(秒级可见) |
| 典型场景 | 业务数据存储 | 搜索、日志、分析 |
1.2 典型场景
- 站内搜索:电商商品搜索、文档搜索、论坛;
- 日志分析:ELK(Elasticsearch + Logstash + Kibana);
- 指标监控:APM、业务指标;
- 推荐/补全:搜索建议、纠错。
1.3 核心概念
Elasticsearch = MySQL 的"实例/集群"
Index(索引) = 数据库(database)
Type(类型,已废弃) = 表(7.x 移除)
Document(文档) = 一行记录(JSON)
Field(字段) = 一列
Mapping(映射) = 表结构(字段类型定义)
一条文档示例:
json
{
"_index": "products",
"_id": "1001",
"name": "iPhone 16 Pro",
"price": 7999.00,
"tags": ["手机", "苹果"],
"created_at": "2026-09-30T10:00:00"
}
二、安装与快速上手
2.1 安装
bash
# Docker 最快
docker run -d --name es \
-p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
docker.elastic.co/elasticsearch/elasticsearch:8.13.0
验证:
bash
curl http://localhost:9200
2.2 索引操作
bash
# 创建索引
PUT /products
{
"settings": { "number_of_shards": 3, "number_of_replicas": 1 }
}
# 查看索引
GET /products
# 删除索引(慎用!)
DELETE /products
2.3 文档 CRUD
bash
# 新增/覆盖文档
PUT /products/_doc/1001
{
"name": "iPhone 16 Pro",
"price": 7999,
"tags": ["手机", "苹果"]
}
# 查询
GET /products/_doc/1001
# 更新(局部)
POST /products/_update/1001
{
"doc": { "price": 7499 }
}
# 删除
DELETE /products/_doc/1001
三、Mapping:字段类型设计
3.1 核心类型
| 类型 | 用途 |
|---|---|
| text | 全文检索(分词、模糊匹配) |
| keyword | 精确匹配(等值、排序、聚合) |
| integer / long | 整数 |
| float / double | 浮点 |
| boolean | 布尔 |
| date | 日期 |
| object / nested | 嵌套对象 |
3.2 为什么要设计 Mapping
ES 会自动推断类型,但默认把字符串设为 text + keyword------text 不能排序和聚合,keyword 不能全文检索。生产必须显式定义:
json
PUT /products
{
"mappings": {
"properties": {
"name": {
"type": "text",
"fields": {
"keyword": { "type": "keyword" } // name.keyword 可精确匹配/排序
}
},
"price": { "type": "double" },
"tags": { "type": "keyword" },
"description": { "type": "text" },
"created_at": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }
}
}
}
关键点:
- 加字段容易,改字段类型要重建索引(reindex);
- 线上先想清楚字段类型,别依赖自动映射。
3.3 重建索引(reindex)
bash
# 建新索引(新 mapping)
PUT /products_v2 { ... }
# 数据搬迁
POST /_reindex
{
"source": { "index": "products" },
"dest": { "index": "products_v2" }
}
# 改别名
POST /_aliases
{
"actions": [
{ "remove": { "index": "products", "alias": "products_alias" } },
{ "add": { "index": "products_v2", "alias": "products_alias" } }
]
}
生产规范:业务永远用别名访问,重建索引无感切换。
四、分词器(Analyzer)
4.1 为什么需要分词
搜索"苹果手机",如果文档是"iPhone",不分词就匹配不上。分词器把文本拆成词元(token):
"苹果手机很好用" → [苹果, 手机, 很好, 用]
4.2 中文分词(IK)
ES 自带 standard 分词器对中文是逐字拆,效果差------中文场景必须装 IK 分词器:
bash
./bin/elasticsearch-plugin install analysis-ik
测试:
bash
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "中华人民共和国成立了"
}
# 输出: [中华人民共和国, 中华人民, 中华, 华人, 人民共和国, 人民, 共和国, 共和, 国, 成立, 了]
IK 两种模式:
ik_max_word:最细粒度(索引用,召回全);ik_smart:最粗粒度(搜索用,精确)。
4.3 自定义分词
json
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": ["lowercase"]
}
}
}
}
}
五、查询 DSL(重点)
5.1 基本查询结构
json
GET /products/_search
{
"query": { ... }, // 查询条件
"from": 0, "size": 10, // 分页
"sort": [ { "price": "desc" } ], // 排序
"_source": ["name", "price"] // 只返回指定字段
}
5.2 match(全文检索)
json
{ "query": { "match": { "name": "苹果手机" } } }
match 与 term 的区别(必考):
match:先分词再匹配,适合 text 字段;term:精确匹配(不分词),适合 keyword 字段:
json
{ "query": { "term": { "tags": "苹果" } } }
坑 :term 查 text 字段基本查不到(text 被分词了)。
5.3 组合查询 bool
json
{
"query": {
"bool": {
"must": [ { "match": { "name": "手机" } } ], // AND
"filter": [ { "term": { "tags": "苹果" } } ], // 过滤(不算分,快)
"should": [ { "match": { "description": "拍照" } } ], // OR(加分)
"must_not": [ { "term": { "status": "下架" } } ] // 排除
}
}
}
filter vs must:filter 不参与相关度评分,可缓存,性能更好------纯过滤条件用 filter。
5.4 范围与多值
json
{ "query": { "range": { "price": { "gte": 1000, "lte": 8000 } } } }
{ "query": { "terms": { "tags": ["苹果", "华为"] } } }
5.5 分页与深分页
json
{ "from": 0, "size": 20 }
深分页问题(同 MySQL):
from+size超过 1 万条会被拒绝(默认 max_result_window=10000);- 深度翻页用 search_after(游标):
json
POST /products/_search
{
"size": 20,
"sort": [ { "price": "asc" }, { "_id": "asc" } ],
"search_after": [ 4999, "1001" ] // 上一页最后一条的值
}
5.6 高亮
json
{
"query": { "match": { "name": "苹果" } },
"highlight": {
"fields": { "name": {} },
"pre_tags": ["<em>"], "post_tags": ["</em>"]
}
}
六、聚合分析(Aggregation)
6.1 三种聚合
- 桶(bucket):分组,类似 GROUP BY;
- 指标(metric):统计,类似 MAX/AVG/SUM;
- 管道(pipeline):聚合结果再聚合。
6.2 实战:电商统计
json
POST /orders/_search
{
"size": 0, // 只要聚合结果
"aggs": {
"by_category": { // 按分类分组
"terms": { "field": "category", "size": 10 },
"aggs": {
"avg_price": { "avg": { "field": "price" } }, // 每组均价
"total_sales": { "sum": { "field": "amount" } }, // 每组总额
"by_status": { // 每组内再按状态分
"terms": { "field": "status" }
}
}
}
}
}
6.3 日期直方图(按时间统计)
json
{
"size": 0,
"aggs": {
"sales_per_day": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "day"
}
}
}
}
七、Spring Boot 集成
7.1 依赖与配置
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
yaml
spring:
elasticsearch:
uris: http://localhost:9200
7.2 实体与 Repository
java
@Document(indexName = "products")
public class Product {
@Id
private String id;
private String name;
private Double price;
private List<String> tags;
// getter/setter
}
java
public interface ProductRepository
extends ElasticsearchRepository<Product, String> {
// 方法名自动生成查询
List<Product> findByNameContaining(String keyword);
// 复杂查询用 @Query(Query DSL 字符串)
@Query("{\"bool\": {\"must\": [{\"match\": {\"name\": \"?0\"}}]}}")
List<Product> searchByName(String keyword);
}
7.3 自定义查询
java
@RestController
public class ProductSearchController {
@Autowired
private ElasticsearchOperations esOps;
public List<Product> search(String keyword, double minPrice) {
Query query = NativeQuery.builder()
.withQuery(q -> q.bool(b -> b
.must(m -> m.match(t -> t.field("name").query(keyword)))
.filter(f -> f.range(r -> r
.field("price").gte(JsonData.of(minPrice))))
))
.build();
return esOps.search(query, Product.class).stream()
.map(hit -> hit.getContent()).toList();
}
}
八、ELK 日志平台
8.1 架构
应用日志 → Filebeat(采集)→ Logstash(处理)→ Elasticsearch(存储索引)
↓
Kibana(可视化)
- Filebeat:轻量采集器,读日志文件发到 ES/Logstash;
- Logstash:解析、过滤、转换(grok 解析日志格式);
- Kibana:搜索、图表、仪表盘。
8.2 Logstash 配置示例
ruby
input {
beats { port => 5044 }
}
filter {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:log_time} %{LOGLEVEL:level} %{GREEDYDATA:msg}" }
}
date {
match => ["log_time", "yyyy-MM-dd HH:mm:ss,SSS"]
}
}
output {
elasticsearch {
hosts => ["http://es:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
}
8.3 日志索引生命周期
日志量大,按天建索引,配合索引生命周期管理(ILM):
- 3 天内:热节点(读写快);
- 30 天内:温节点(压缩);
- 之后:删除。
九、性能优化
9.1 写入优化
- 批量写入(_bulk):一次几千条,别一条一条写;
- 调大 refresh_interval:默认 1s 刷新,可设 30s(日志场景);
- 关闭副本再写:导入数据时 replicas=0,导完恢复。
json
PUT /logs/_settings
{ "index": { "refresh_interval": "30s" } }
9.2 查询优化
- 用 filter 不用 must(纯过滤可缓存);
- 不要深分页(用 search_after);
- 只返回需要的字段(_source 裁剪);
- 避免 wildcard 模糊查询(性能差);
- 大聚合限制 size。
9.3 分片设计
- 单分片 30-50GB 为宜;
- 分片数 = 节点数 × 1~2(每节点 3-5 个分片合适);
- 分片太多:资源浪费、查询慢。
9.4 内存设置
yaml
# jvm.options
-Xms4g
-Xmx4g
红线:堆不要超过物理内存一半,留一半给操作系统页缓存(ES 大量用 OS cache)。
十、常见坑速查
| 现象 | 原因 | 解决 |
|---|---|---|
| 中文搜不到 | 没装 IK 分词器 | 装 IK,字段用 ik 分词 |
| term 查 text 查不到 | text 被分词 | 用 keyword 字段或 match |
| 聚合报错 | text 字段不能聚合 | 用 .keyword 字段 |
| 深分页报错 | 超过 max_result_window | search_after |
| 索引变黄/红 | 副本分配失败/磁盘满 | 看集群健康、加磁盘 |
| 写入慢 | 单条写入/refresh 频繁 | 批量 + 调 refresh_interval |
| 数据不实时 | 近实时(1s 刷新) | 接受或调 refresh |
| 字段类型错了 | 依赖自动映射 | 显式 mapping,reindex |
本章小结
Elasticsearch 的实战核心:Mapping 设计(text/keyword)、查询 DSL(match/term/bool/range)、聚合分析、分页策略(search_after)、性能优化。先学会用 curl 玩转 DSL,再套框架(Spring Data/ELK)。
下一篇讲设计模式------Java 面试与代码质量的常青话题。
(全文完)