第 203 章:ES 高级搜索与优化
学习目标
- 掌握复杂查询(Query DSL)
- 学会相关性打分优化
- 理解分片与索引优化
- 学会 ES 集群监控
一、复合查询
1.1 multi_match(多字段)
bash
POST /articles/_search
{
"query": {
"multi_match": {
"query": "NestJS 教程",
"fields": ["title^3", "content", "tags^2"],
"type": "best_fields"
}
}
}^3 = 字段权重 3 倍。
类型:
best_fields:取单个字段最高分most_fields:多个字段分数相加cross_fields:跨字段匹配phrase:短语匹配phrase_prefix:短语前缀
1.2 短语匹配
bash
POST /articles/_search
{
"query": {
"match_phrase": {
"title": "NestJS 教程"
}
}
}slop 允许间隔:
bash
{
"match_phrase": {
"title": { "query": "NestJS 教程", "slop": 2 }
}
}1.3 prefix / wildcard / fuzzy
bash
# 前缀
{ "prefix": { "title": "nest" } }
# 通配符(* 多个, ? 一个)
{ "wildcard": { "title": "nes*" } }
# 模糊(容错)
{ "fuzzy": { "title": { "value": "nedtjs", "fuzziness": 2 } } }1.4 function_score(自定义打分)
bash
POST /articles/_search
{
"query": {
"function_score": {
"query": { "match": { "title": "NestJS" } },
"functions": [
{
"filter": { "term": { "author": "Tom" } },
"weight": 2
},
{
"field_value_factor": {
"field": "viewCount",
"factor": 0.1,
"modifier": "log1p",
"missing": 0
}
}
],
"score_mode": "sum",
"boost_mode": "multiply"
}
}
}二、深分页
2.1 search_after
bash
# 第一页
POST /articles/_search
{
"size": 10,
"sort": [
{ "createdAt": "desc" },
{ "_id": "asc" }
]
}
# 后续页(用上次的 sort 值)
POST /articles/_search
{
"size": 10,
"sort": [
{ "createdAt": "desc" },
{ "_id": "asc" }
],
"search_after": [1700000000000, "abc123"]
}2.2 PIT(Point In Time)
bash
# 创建 PIT
POST /articles/_pit?keep_alive=1m
# 返回 {"id": "..."}
# 用 PIT 搜索
POST /_search
{
"pit": { "id": "...", "keep_alive": "1m" },
"size": 10,
"sort": [{ "createdAt": "desc" }]
}三、相关性打分(BM25)
3.1 评分要素
python
_score(q, d) = boost * TF * IDF * fieldLength
# TF:词频
# IDF:逆文档频率
# fieldLength:字段越短分数越高3.2 explain 调试
bash
GET /articles/_explain/1
{
"query": { "match": { "title": "NestJS" } }
}四、聚合深入
4.1 nested 聚合
bash
{
"aggs": {
"by_comment": {
"nested": { "path": "comments" },
"aggs": {
"top_users": {
"terms": { "field": "comments.user", "size": 5 }
}
}
}
}
}4.2 pipeline 聚合
bash
{
"aggs": {
"sales": {
"date_histogram": { "field": "date", "calendar_interval": "month" },
"aggs": {
"monthly_sales": { "sum": { "field": "amount" } }
}
},
"total": {
"sum_bucket": {
"buckets_path": "sales>monthly_sales"
}
}
}
}五、Mapping 优化
5.1 字段类型选型
| 类型 | 场景 | 例子 |
|---|---|---|
text | 全文搜索 | title, content |
keyword | 精确 / 聚合 | author, tags, status |
integer | 整数 | age, viewCount |
date | 日期 | createdAt |
boolean | 布尔 | isActive |
nested | 嵌套数组 | comments |
geo_point | 经纬度 | location |
5.2 禁止 _source
json
{
"mappings": {
"_source": { "enabled": false }
}
}节省存储,但不能 reindex / update。
5.3 字段动态模板
json
{
"mappings": {
"dynamic_templates": [
{
"strings_as_keyword": {
"match_mapping_type": "string",
"mapping": { "type": "keyword", "ignore_above": 256 }
}
}
]
}
}六、索引优化
6.1 分片设计
单分片: 10-50GB
分片数 = 期望吞吐 / 单分片吞吐
节点数 = 分片数 * (副本数 + 1)6.2 Routing 减少搜索范围
bash
PUT /orders/_doc/1?routing=user:123查询时:
bash
POST /orders/_search?routing=user:123
{
"query": { "match_all": {} }
}6.3 Index Aliases(零停机切换)
bash
# 创建 v1 索引
PUT /articles_v1
# 创建别名
POST /_aliases
{
"actions": [
{ "add": { "index": "articles_v1", "alias": "articles" } }
]
}
# 创建 v2 索引
PUT /articles_v2
# 切换别名(零停机)
POST /_aliases
{
"actions": [
{ "remove": { "index": "articles_v1", "alias": "articles" } },
{ "add": { "index": "articles_v2", "alias": "articles" } }
]
}七、性能优化
7.1 查询优化
| 优化 | 说明 |
|---|---|
| 避免 wildcard 前缀 | *foo 会扫全索引 |
| 用 keyword 不分词 | 聚合 / 排序 |
| 限制 size | 深分页用 search_after |
| filter 不算分 | 不用算分的放 filter |
| 关闭 _source 字段 | 只取需要的字段 |
7.2 索引优化
bash
# 强制合并(减少 segment)
POST /articles/_forcemerge?max_num_segments=1
# 关闭不必要的索引
"index.refresh_interval": "30s" # 默认 1s,批量导入可设 30s
# 索引重建
POST /_reindex
{
"source": { "index": "articles_v1" },
"dest": { "index": "articles_v2" }
}7.3 JVM 调优
bash
# heap 不超过物理内存 50%,最大 32GB
ES_JAVA_OPTS="-Xms16g -Xmx16g"
# 关闭 swap
swapoff -a
# 内存锁定
bootstrap.memory_lock=true八、ES 监控
8.1 关键指标
| 指标 | 含义 | 告警阈值 |
|---|---|---|
cluster_status | 集群状态 | 黄色>1h |
jvm_heap_used_percent | JVM 堆使用率 | >80% |
search_rate | 查询速率 | - |
index_rate | 写入速率 | - |
query_latency | 查询延迟 | >1s |
8.2 慢查询
yaml
# elasticsearch.yml
index.search.slowlog.threshold.query.warn: 10s
index.search.slowlog.threshold.fetch.warn: 1s8.3 Profile API
bash
POST /articles/_search
{
"profile": true,
"query": { "match": { "title": "NestJS" } }
}返回每个阶段耗时。
九、ELK 日志聚合
yaml
# logstash.conf
input {
beats { port => 5044 }
}
filter {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:msg}" }
}
date {
match => [ "timestamp", "ISO8601" ]
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
}十、本章小结
| 优化 | 收益 |
|---|---|
| 合理分片 | 提高并行度 |
| routing | 减少搜索范围 |
| alias | 零停机切换 |
| filter context | 跳过打分 |
| force merge | 减少 segment |
| 关闭 _source | 节省存储 |
动手练习
- 实现 multi_match + 打分权重
- 用 search_after 实现深分页
- 用聚合统计作者文章数 + 阅读量
- 用 IK 分词器优化中文搜索
推荐阅读
下一章:第 204 章:MongoDB 入门与建模 →