← 返回
后端开发 2026.03.07

Go工程师体系课 011

后端开发

1. 什么是倒排索引?

倒排索引(Inverted Index)是一种数据结构,用于快速查找包含特定词汇的文档。它是搜索引擎的核心技术之一。

1.1 基本概念

  • 正排索引:文档 ID → 文档内容(词列表)
  • 倒排索引:词 → 包含该词的文档 ID 列表

1.2 为什么叫”倒排”?

倒排索引将传统的”文档包含哪些词”的关系倒转为”词出现在哪些文档中”,因此称为”倒排”。

2. 倒排索引的结构

2.1 基本结构

词项 → 文档频率 → 文档列表

2.2 详细结构

词项 → {
    文档频率: N,
    文档列表: [
        {文档ID: 1, 词频: 2, 位置: [0, 5]},
        {文档ID: 3, 词频: 1, 位置: [2]}
    ]
}

3. 倒排索引的工作原理

3.1 构建过程

  1. 文档预处理:分词、去停用词、词干提取
  2. 词项统计:统计每个词在文档中的出现频率和位置
  3. 索引构建:建立词项到文档的映射关系

3.2 查询过程

  1. 查询解析:将查询字符串分词
  2. 索引查找:在倒排索引中查找每个词项
  3. 结果合并:合并多个词项的文档列表
  4. 排序返回:按相关性排序返回结果

4. Go 语言实现倒排索引

4.1 数据结构定义

package main

import (
    "fmt"
    "sort"
    "strings"
)

// 文档信息
type Document struct {
    ID   int
    Text string
}

// 词项在文档中的位置信息
type Posting struct {
    DocID     int
    Frequency int
    Positions []int
}

// 倒排索引项
type InvertedIndexItem struct {
    Term      string
    DocFreq   int
    Postings  []Posting
}

// 倒排索引
type InvertedIndex struct {
    Index map[string]*InvertedIndexItem
}

// 创建新的倒排索引
func NewInvertedIndex() *InvertedIndex {
    return &InvertedIndex{
        Index: make(map[string]*InvertedIndexItem),
    }
}

4.2 索引构建

// 添加文档到索引
func (idx *InvertedIndex) AddDocument(docID int, text string) {
    // 简单的分词(实际应用中需要更复杂的分词算法)
    words := strings.Fields(strings.ToLower(text))

    for pos, word := range words {
        if idx.Index[word] == nil {
            idx.Index[word] = &InvertedIndexItem{
                Term:     word,
                DocFreq:  0,
                Postings: make([]Posting, 0),
            }
        }

        // 查找是否已存在该文档的posting
        var posting *Posting
        for i := range idx.Index[word].Postings {
            if idx.Index[word].Postings[i].DocID == docID {
                posting = &idx.Index[word].Postings[i]
                break
            }
        }

        if posting == nil {
            // 创建新的posting
            newPosting := Posting{
                DocID:     docID,
                Frequency: 1,
                Positions: []int{pos},
            }
            idx.Index[word].Postings = append(idx.Index[word].Postings, newPosting)
            idx.Index[word].DocFreq++
        } else {
            // 更新现有posting
            posting.Frequency++
            posting.Positions = append(posting.Positions, pos)
        }
    }
}

4.3 查询实现

// 单词查询
func (idx *InvertedIndex) Search(term string) []int {
    term = strings.ToLower(term)
    if item, exists := idx.Index[term]; exists {
        docIDs := make([]int, len(item.Postings))
        for i, posting := range item.Postings {
            docIDs[i] = posting.DocID
        }
        return docIDs
    }
    return []int{}
}

// 多词查询(AND操作)
func (idx *InvertedIndex) SearchAnd(terms []string) []int {
    if len(terms) == 0 {
        return []int{}
    }

    // 获取第一个词的结果
    result := idx.Search(terms[0])

    // 与其他词的结果求交集
    for i := 1; i < len(terms); i++ {
        otherResult := idx.Search(terms[i])
        result = intersect(result, otherResult)
    }

    return result
}

// 多词查询(OR操作)
func (idx *InvertedIndex) SearchOr(terms []string) []int {
    if len(terms) == 0 {
        return []int{}
    }

    resultSet := make(map[int]bool)

    for _, term := range terms {
        docIDs := idx.Search(term)
        for _, docID := range docIDs {
            resultSet[docID] = true
        }
    }

    result := make([]int, 0, len(resultSet))
    for docID := range resultSet {
        result = append(result, docID)
    }

    sort.Ints(result)
    return result
}

// 求两个切片的交集
func intersect(a, b []int) []int {
    set := make(map[int]bool)
    for _, x := range a {
        set[x] = true
    }

    result := make([]int, 0)
    for _, x := range b {
        if set[x] {
            result = append(result, x)
        }
    }

    return result
}

4.4 完整示例

func main() {
    // 创建倒排索引
    index := NewInvertedIndex()

    // 添加文档
    documents := []Document{
        {ID: 1, Text: "Go is a programming language"},
        {ID: 2, Text: "Go is fast and efficient"},
        {ID: 3, Text: "Programming in Go is fun"},
        {ID: 4, Text: "Go language is simple"},
    }

    // 构建索引
    for _, doc := range documents {
        index.AddDocument(doc.ID, doc.Text)
    }

    // 查询示例
    fmt.Println("搜索 'go':", index.Search("go"))
    fmt.Println("搜索 'programming':", index.Search("programming"))
    fmt.Println("搜索 'go' AND 'language':", index.SearchAnd([]string{"go", "language"}))
    fmt.Println("搜索 'go' OR 'fast':", index.SearchOr([]string{"go", "fast"}))

    // 打印索引结构
    fmt.Println("\n倒排索引结构:")
    for term, item := range index.Index {
        fmt.Printf("词项: %s, 文档频率: %d\n", term, item.DocFreq)
        for _, posting := range item.Postings {
            fmt.Printf("  文档ID: %d, 词频: %d, 位置: %v\n",
                posting.DocID, posting.Frequency, posting.Positions)
        }
    }
}

5. 倒排索引的优化

5.1 压缩技术

  • 变长编码:使用变长编码压缩文档 ID
  • 差分编码:存储文档 ID 的差值而不是绝对值
  • 位图压缩:使用位图表示文档集合

5.2 查询优化

  • 跳跃表:在长列表中快速定位
  • 缓存机制:缓存热门查询结果
  • 并行查询:多线程处理查询

6. 实际应用场景

6.1 搜索引擎

  • Google、百度等搜索引擎的核心技术
  • 网页内容索引和检索

6.2 数据库系统

  • 全文搜索功能
  • 文本字段的快速查询

6.3 代码搜索

  • GitHub 代码搜索
  • IDE 中的代码导航

6.4 日志分析

  • 日志文件的快速检索
  • 错误日志的定位

7. 性能分析

7.1 时间复杂度

  • 构建索引:O(N×M),N 为文档数,M 为平均词数
  • 单词查询:O(1) 平均情况
  • 多词查询:O(k×log(n)),k 为结果数,n 为文档数

7.2 空间复杂度

  • 存储空间:O(V×D),V 为词汇量,D 为平均文档频率

7.3 优缺点

优点

  • 查询速度快
  • 支持复杂查询
  • 易于实现

缺点

  • 构建索引耗时
  • 存储空间较大
  • 更新索引复杂

8. 总结

倒排索引是信息检索领域的核心技术,通过将”文档-词”的关系倒转为”词-文档”的关系,实现了高效的文本搜索。在 Go 语言中,我们可以使用 map 和切片等基本数据结构来实现倒排索引,为应用程序提供强大的搜索功能。

multi_match 使用说明

multi_match 是 ES 中在多个字段上同时进行搜索的查询类型,本质上是对 match 查询在多字段上的扩展。适合标题、描述、标签等多个文本字段联合检索,常配合字段权重、不同查询类型与分词器使用。

1. 基本用法

POST /index/_search
{
  "query": {
    "multi_match": {
      "query": "iPhone 15",
      "fields": ["title", "description", "tags"]
    }
  }
}

2. 字段权重(boost)

POST /index/_search
{
  "query": {
    "multi_match": {
      "query": "iPhone 15",
      "fields": ["title^3", "description^1.5", "tags"]
    }
  }
}

说明:title^3 表示为 title 字段的匹配结果乘以 3 的权重,从而在排序时提升该字段命中结果的分数。

3. type 选项与适用场景

  • best_fields(默认):在所有字段中挑选最匹配的字段分数作为主分数,可配合 tie_breaker
POST /index/_search
{
  "query": {
    "multi_match": {
      "query": "apple phone",
      "fields": ["title", "description", "tags"],
      "type": "best_fields",
      "tie_breaker": 0.2
    }
  }
}
  • most_fields:多个字段得分叠加,适合同一语义分布在多个字段的情况(如同一文本拆分存储在不同字段)
POST /index/_search
{
  "query": {
    "multi_match": {
      "query": "iphone",
      "fields": ["title", "title.ngram", "description"],
      "type": "most_fields"
    }
  }
}
  • cross_fields:将多个字段当作一个大字段进行匹配,适合将词语分布在不同字段的场景(如 first_name + last_name)
POST /index/_search
{
  "query": {
    "multi_match": {
      "query": "tim cook",
      "fields": ["first_name", "last_name"],
      "type": "cross_fields",
      "operator": "and"
    }
  }
}
  • phrase:短语匹配,要求词序与距离严格,适合精确短语搜索
POST /index/_search
{
  "query": {
    "multi_match": {
      "query": "iphone 15 pro",
      "fields": ["title", "description"],
      "type": "phrase"
    }
  }
}
  • phrase_prefix:短语前缀匹配,适合输入法联想/搜索建议
POST /index/_search
{
  "query": {
    "multi_match": {
      "query": "iph 15",
      "fields": ["title", "description"],
      "type": "phrase_prefix",
      "max_expansions": 50
    }
  }
}

4. 操作符与最小匹配

POST /index/_search
{
  "query": {
    "multi_match": {
      "query": "apple flagship phone",
      "fields": ["title", "description"],
      "operator": "and",
      "minimum_should_match": "75%"
    }
  }
}

说明:

  • operator: and 要求查询词全部匹配;or(默认)为匹配任意一个
  • minimum_should_match 控制最少匹配词的比例或数量,如 23<75%75%

5. 模糊匹配(fuzziness)与纠错

POST /index/_search
{
  "query": {
    "multi_match": {
      "query": "iphine",
      "fields": ["title", "description"],
      "fuzziness": "AUTO",
      "prefix_length": 1
    }
  }
}

说明:fuzziness: AUTO 对常见拼写错误具备容错能力;prefix_length 指定前缀必须精确匹配的长度。

6. 分词器与字段选择

POST /index/_search
{
  "query": {
    "multi_match": {
      "query": "苹果 手机",
      "fields": ["title", "title.keyword^5", "description"],
      "analyzer": "ik_smart"
    }
  }
}

建议:

  • 多用于 text 字段进行全文检索;精确匹配与聚合/排序使用 keyword 字段(可配合 boost)
  • 中文检索可使用 ik_smartik_max_word 等分词器(需安装插件)

7. 组合示例(综合字段、权重、过滤与排序)

POST /products/_search
{
  "_source": ["id", "title", "price", "brand"],
  "from": 0,
  "size": 20,
  "sort": [
    {"_score": "desc"},
    {"price": "asc"}
  ],
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "iphone 15 pro",
            "fields": ["title^4", "subtitle^2", "description", "tags"],
            "type": "best_fields",
            "tie_breaker": 0.3,
            "minimum_should_match": "66%"
          }
        }
      ],
      "filter": [
        {"term": {"brand": "apple"}},
        {"range": {"price": {"gte": 3000, "lte": 10000}}}
      ]
    }
  },
  "highlight": {
    "fields": {
      "title": {},
      "description": {}
    }
  }
}

8. 常见问题与建议

  • 相关性不理想:
  • 为核心字段设置更高权重(如 title^N
  • 选择合适的 type:跨字段词分布用 cross_fields,综合得分用 most_fields
  • 使用同义词、拼写纠错(fuzziness)与领域词典
  • 性能问题:
  • 控制返回字段(_source 过滤)与 size
  • 将过滤条件放入 filter,命中缓存且不参与评分
  • 避免在巨量字段上使用 wildcard/phrase_prefix 进行前缀扩展
  • 精确 vs 全文:
  • 精确匹配与聚合使用 keyword;全文检索使用 text + 分词器
  • 可为同一业务字段建 multi-fieldstext + keyword

term 查询详解

term 查询是 ES 中用于精确匹配的查询类型,不会对查询词进行分词处理,直接与索引中的词项进行精确匹配。适用于 keyword 类型字段、数值字段、日期字段等。(没有进行分词,小写化处理)

1. 基本用法

POST /products/_search
{
  "query": {
    "term": {
      "status": "active"
    }
  }
}

2. 多字段 term 查询

POST /products/_search
{
  "query": {
    "bool": {
      "must": [
        {"term": {"status": "active"}},
        {"term": {"category": "electronics"}},
        {"term": {"brand": "apple"}}
      ]
    }
  }
}

3. 数值字段精确匹配

POST /products/_search
{
  "query": {
    "term": {
      "price": 5999
    }
  }
}

4. 日期字段精确匹配

POST /products/_search
{
  "query": {
    "term": {
      "created_date": "2025-01-18"
    }
  }
}

5. 数组字段匹配

POST /products/_search
{
  "query": {
    "term": {
      "tags": "phone"
    }
  }
}

6. 使用 boost 提升权重

POST /products/_search
{
  "query": {
    "term": {
      "status": {
        "value": "active",
        "boost": 2.0
      }
    }
  }
}

7. terms 查询(多值匹配)

POST /products/_search
{
  "query": {
    "terms": {
      "status": ["active", "pending", "review"]
    }
  }
}

8. 与 filter 结合使用

POST /products/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"title": "iPhone"}}
      ],
      "filter": [
        {"term": {"status": "active"}},
        {"term": {"category": "electronics"}}
      ]
    }
  }
}

term vs match 查询对比

1. 核心区别

特性term 查询match 查询
分词处理不进行分词,精确匹配对查询词进行分词处理
匹配方式精确匹配索引中的词项模糊匹配,支持相关性评分
适用字段keyword、数值、日期等text 类型字段
性能更快(不计算相关性)较慢(需要计算评分)
缓存结果可被缓存结果通常不被缓存

2. 实际示例对比

2.1 相同查询词的不同结果

# 数据准备
POST /test/_doc/1
{
  "title": "iPhone 15 Pro Max",
  "title.keyword": "iPhone 15 Pro Max",
  "status": "active"
}

# term 查询 - 精确匹配
POST /test/_search
{
  "query": {
    "term": {
      "title.keyword": "iPhone 15 Pro Max"
    }
  }
}
# 结果:匹配成功

# term 查询 - 对 text 字段使用 term(通常不匹配)
POST /test/_search
{
  "query": {
    "term": {
      "title": "iPhone 15 Pro Max"
    }
  }
}
# 结果:不匹配(因为 title 被分词为 ["iphone", "15", "pro", "max"])

# match 查询 - 对 text 字段使用 match
POST /test/_search
{
  "query": {
    "match": {
      "title": "iPhone 15 Pro Max"
    }
  }
}
# 结果:匹配成功,有相关性评分

2.2 部分匹配对比

# term 查询 - 部分词不匹配
POST /test/_search
{
  "query": {
    "term": {
      "title.keyword": "iPhone 15"
    }
  }
}
# 结果:不匹配(需要完全一致)

# match 查询 - 部分词匹配
POST /test/_search
{
  "query": {
    "match": {
      "title": "iPhone 15"
    }
  }
}
# 结果:匹配成功,相关性评分较低

3. 使用场景对比

3.1 term 查询适用场景

# 1. 状态过滤
POST /products/_search
{
  "query": {
    "bool": {
      "filter": [
        {"term": {"status": "active"}}
      ]
    }
  }
}

# 2. 分类筛选
POST /products/_search
{
  "query": {
    "bool": {
      "filter": [
        {"term": {"category": "electronics"}}
      ]
    }
  }
}

# 3. 标签匹配
POST /products/_search
{
  "query": {
    "bool": {
      "filter": [
        {"term": {"tags": "premium"}}
      ]
    }
  }
}

# 4. 聚合统计
POST /products/_search
{
  "size": 0,
  "aggs": {
    "status_count": {
      "terms": {
        "field": "status"
      }
    }
  }
}

3.2 match 查询适用场景

# 1. 全文搜索
POST /products/_search
{
  "query": {
    "match": {
      "title": "iPhone 15 Pro"
    }
  }
}

# 2. 描述搜索
POST /products/_search
{
  "query": {
    "match": {
      "description": "最新款手机"
    }
  }
}

# 3. 多字段搜索
POST /products/_search
{
  "query": {
    "multi_match": {
      "query": "苹果手机",
      "fields": ["title", "description", "tags"]
    }
  }
}

4. 性能对比

4.1 查询性能

# term 查询 - 高性能
POST /products/_search
{
  "query": {
    "bool": {
      "filter": [
        {"term": {"status": "active"}},
        {"term": {"category": "electronics"}}
      ]
    }
  }
}
# 特点:不计算相关性,结果可缓存

# match 查询 - 相对较慢
POST /products/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"title": "iPhone"}},
        {"match": {"description": "手机"}}
      ]
    }
  }
}
# 特点:需要计算相关性评分,结果通常不缓存

4.2 混合使用优化

# 最佳实践:term 用于过滤,match 用于搜索
POST /products/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"title": "iPhone 15"}}
      ],
      "filter": [
        {"term": {"status": "active"}},
        {"term": {"category": "electronics"}},
        {"range": {"price": {"gte": 1000, "lte": 10000}}}
      ]
    }
  }
}

5. 常见错误与解决方案

5.1 对 text 字段使用 term 查询

# 错误用法
POST /products/_search
{
  "query": {
    "term": {
      "title": "iPhone"  # title text 字段,会被分词
    }
  }
}

# 正确用法
POST /products/_search
{
  "query": {
    "term": {
      "title.keyword": "iPhone"  # 使用 keyword 字段
    }
  }
}

# 或者使用 match
POST /products/_search
{
  "query": {
    "match": {
      "title": "iPhone"
    }
  }
}

5.2 大小写敏感问题

# term 查询大小写敏感
POST /products/_search
{
  "query": {
    "term": {
      "status": "Active"  # 如果索引中是 "active",则不匹配
    }
  }
}

# 解决方案:使用 match 或确保大小写一致
POST /products/_search
{
  "query": {
    "match": {
      "status": "Active"  # match 会进行分词和标准化
    }
  }
}

6. 最佳实践建议

6.1 字段映射设计

# 创建支持两种查询的映射
PUT /products
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_smart",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "status": {
        "type": "keyword"
      },
      "price": {
        "type": "double"
      }
    }
  }
}

6.2 查询组合策略

# 推荐:精确过滤 + 模糊搜索
POST /products/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"title": "用户搜索词"}}
      ],
      "filter": [
        {"term": {"status": "active"}},
        {"term": {"category": "electronics"}},
        {"range": {"price": {"gte": 1000}}}
      ]
    }
  },
  "sort": [
    {"_score": "desc"},
    {"price": "asc"}
  ]
}

7. 总结

  • term 查询:适用于精确匹配、过滤、聚合,性能更好,结果可缓存
  • match 查询:适用于全文搜索、模糊匹配,支持相关性评分
  • 最佳实践:term 用于过滤条件,match 用于搜索内容,两者结合使用
  • 字段设计:为需要精确匹配的字段创建 keyword 子字段
  • 性能优化:将精确匹配条件放在 filter 中,避免不必要的评分计算

ES Mapping 概念与使用

1. 什么是 Mapping

Mapping 是索引的“结构定义”,类似关系型数据库的表结构 schema,用于声明每个字段的类型与索引方式,决定:

  • 字段的数据类型与存储形式(text、keyword、numeric、date、boolean、geo、nested 等)
  • 是否参与倒排索引与如何分词(indexanalyzer
  • 是否可用于聚合/排序(doc_values
  • 多字段定义(multi-fields):同一业务字段以多种方式建索引
  • 动态字段处理策略(dynamic

自 ES 7 起,一个索引仅有一个 type(内部 _doc),建模直接面向“索引 + 映射”。

2. 常用字段类型与场景

  • text:分词,用于全文检索;不适合聚合/排序
  • keyword:不分词,适合精确匹配、聚合、排序;默认有 doc_values
  • 数值与日期:integer/long/double/date 等,适合范围过滤、聚合和排序
  • 结构化:object(同文档扁平对象)、nested(数组中每个对象独立建模,支持独立子查询)
  • 地理:geo_point/geo_shape

典型 multi-fields(既要全文又要精确):

"title": {
  "type": "text",
  "analyzer": "ik_smart",
  "fields": {
    "keyword": { "type": "keyword", "ignore_above": 256 }
  }
}

3. 创建索引并显式设置映射

PUT /products
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings": {
    "dynamic": "true",
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "standard",
        "fields": {
          "keyword": { "type": "keyword", "ignore_above": 256 }
        }
      },
      "price": { "type": "double" },
      "status": { "type": "keyword" },
      "createdAt": { "type": "date" },
      "tags": { "type": "keyword" },
      "attrs": { "type": "object" },
      "specs": { "type": "nested" }
    }
  }
}

4. 查看/更新映射

  • 查看映射
GET /products/_mapping
  • 新增字段(只能新增,不能改变已存在字段类型)
PUT /products/_mapping
{
  "properties": {
    "brand": { "type": "keyword" }
  }
}

5. 修改字段类型的正确做法(重建索引)

  1. 创建新索引并定义正确映射 products_v2
  2. 迁移数据
POST /_reindex
{
  "source": { "index": "products" },
  "dest":   { "index": "products_v2" }
}
  1. 用别名切换流量
POST /_aliases
{
  "actions": [
    { "remove": { "index": "products", "alias": "products_read" }},
    { "add":    { "index": "products_v2", "alias": "products_read" }}
  ]
}

6. 动态映射策略

"mappings": {
  "dynamic": "strict",
  "properties": { /* 显式列出字段,未知字段将被拒绝 */ }
}

建议在核心索引使用 strict,避免脏数据自动推断成错误类型(例如把数值当 text)。

7. 性能与实践要点

  • 只为需要搜索/过滤的字段开启 index;纯展示字段可 index: false
  • 需要聚合/排序的字段保持 doc_values: truetext 无 doc_values)
  • 中文场景安装 IK 分词器,并在 text 字段指定 analyzer
  • 嵌套数组使用 nested,避免 object 造成交叉匹配
  • 使用 multi-fields 同时支持全文与精确匹配

一句话:Mapping 决定“字段如何被存、被索引、被查”,建索引前先明确查询与聚合需求,再设计映射,才能拿到正确且高性能的检索效果。

ES Analyzer(分词器)使用与说明

1. 什么是 Analyzer

Analyzer 是写入/搜索时对文本字段进行“标准化 → 分词 → 过滤”的组件,通常由三部分组成:

  • char_filter:字符级预处理(如去掉 HTML 标签)
  • tokenizer:将文本切分为 token(词元),如 standardwhitespaceik_smart
  • filter:对 token 再加工(小写化、去停用词、同义词、词干提取等)

写入阶段使用字段的 analyzer,搜索阶段默认使用同一个 analyzer,可通过 search_analyzer 单独指定。

2. 内置常用 Analyzer

  • standard(默认):通用英文分词,小写化
  • simple:按非字母切分,小写化
  • whitespace:仅按空白切分,不改变大小写
  • stop:在 simple 基础上去停用词
  • keyword:不分词,整体作为一个 token(多用于 normalizer 对 keyword 字段)
  • pattern:基于正则表达式分割

中文常用:需要安装插件的 ik_smartik_max_word

3. 使用 _analyze 测试分词效果

POST /_analyze
{
  "analyzer": "standard",
  "text": "iPhone 15 Pro Max"
}

POST /_analyze
{
  "analyzer": "ik_smart",
  "text": "苹果手机保护壳"
}

4. 字段上设置 analyzersearch_analyzer

PUT /docs
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_smart",
        "search_analyzer": "ik_max_word"
      }
    }
  }
}

说明:

  • 写入时使用 ik_smart,查询时使用更细粒度的 ik_max_word 提升召回。

5. 查询时临时指定 analyzer(不改映射)

POST /docs/_search
{
  "query": {
    "match": {
      "title": {
        "query": "苹果手机",
        "analyzer": "ik_max_word"
      }
    }
  }
}

6. 自定义 Analyzer(含同义词/停用词)

PUT /articles
{
  "settings": {
    "analysis": {
      "filter": {
        "my_synonyms": {
          "type": "synonym",
          "synonyms": ["iphone,苹果手机", "notebook,笔记本"]
        }
      },
      "analyzer": {
        "my_zh_analyzer": {
          "type": "custom",
          "char_filter": ["html_strip"],
          "tokenizer": "ik_smart",
          "filter": ["lowercase", "my_synonyms"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": { "type": "text", "analyzer": "my_zh_analyzer" }
    }
  }
}

7. Normalizer(针对 keyword 的标准化)

keyword 字段不分词,无法使用 analyzer;若需大小写归一、去标点,可使用 normalizer:

PUT /users
{
  "settings": {
    "analysis": {
      "normalizer": {
        "lowercase_normalizer": {
          "type": "custom",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "email": { "type": "keyword", "normalizer": "lowercase_normalizer" }
    }
  }
}

8. IK 分词器安装与字段示例(简要)

  1. 安装(根据版本):bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/... 重启 ES
  2. 使用:
PUT /goods
{
  "mappings": {
    "properties": {
      "title": { "type": "text", "analyzer": "ik_smart", "search_analyzer": "ik_max_word" }
    }
  }
}

9. 变更 analyzer 的注意事项

  • 已存在字段的 analyzer 基本不可直接修改;需走“重建索引(reindex)”流程
  • 不同 analyzer 会影响倒排索引结构,变更后注意重新验证查询语义与相关性

10. 性能与实践

  • 选择尽可能简单的写入分词器(如 ik_smart),查询端更细粒度(ik_max_word)提升召回
  • _analyze 验证分词是否符合预期;频繁的过滤条件应使用 keyword + normalizer
  • 控制字段数量与分词粒度,避免索引爆炸;同义词表外置管理便于更新

ES 术语与专有名词速查(Glossary)

以下概念按主题归类,便于快速理解与查阅。

索引与文档建模

  • Index(索引):文档集合的逻辑容器,类似数据库的库。内部由多个分片组成
  • Document(文档):一条记录,以 JSON 存储,通过 _id 唯一标识
  • Field(字段):文档属性,决定了可用的查询与聚合方式
  • Mapping(映射):字段类型与索引策略的定义,等同表结构 schema
  • Type:6.x 及以下存在的逻辑“表”概念。7.x 起固定 _doc,8.x 对外隐藏
  • Text:会分词的字段类型,用于全文检索,不适合聚合/排序
  • Keyword:不分词,适合精确匹配、聚合/排序,通常有 doc_values
  • Multi-fields:同一字段以多种方式建索引,如 titletitle.keyword
  • Object:对象字段,属性扁平合并到同一文档
  • Nested:嵌套对象,每个数组元素独立索引,避免交叉匹配,可独立子查询
  • Dynamic mapping:未知字段出现时的策略(true/false/strict)

分词与标准化

  • Analyzer:分词器,含 char_filtertokenizerfilter 三阶段
  • Tokenizer:切分为 token 的组件,如 standardwhitespaceik_smart
  • Token(词元/项):倒排索引中的基本单位
  • Char filter:字符级预处理,如 html_strip
  • Token filter:对 token 再加工,如 lowercasesynonymstop
  • Normalizer:面向 keyword 的标准化(小写化、去重音等),不分词

倒排索引与评分

  • Inverted index(倒排索引):term → 文档列表(postings)的索引结构
  • Term:索引中的词项(已标准化/分词后的 token)
  • Posting:文档出现信息,包含 docID、频次、位置等
  • Relevance score:相关性评分,用于排序
  • BM25:默认相关性模型(取代 TF-IDF)
  • Query vs Filter:Query 参与评分,Filter 只做布尔过滤且可缓存
  • Bool query:must/should/must_not/filter 组合查询

存储与段(Segment)

  • Segment:不可变数据段,写入追加产生;合并(merge)减少段数
  • Refresh:将内存中的增量刷新为新段,默认周期 1s,刷新后可见
  • Flush:将 translog 持久化并创建新的提交点(commit)
  • Translog:写入日志,用于崩溃恢复
  • Doc values:列式存储,支撑聚合/排序/脚本,text 无 doc values
  • _source:原始 JSON 文档,默认存储,用于重取与 reindex
  • Stored fields:单独存储的字段(不常用),与 _source 区分
  • Norms:字段级长度归一化等评分因子,可关闭以省空间

集群与分片

  • Cluster:由多个节点组成的 ES 集群
  • Node:集群中的实例,常见角色:masterdataingestcoordinating
  • Shard(主分片):索引的物理分片单位,创建时确定数量
  • Replica(副本):主分片的拷贝,提升高可用与查询吞吐
  • Routing:根据路由值决定文档落在哪个主分片,默认基于 _id hash
  • Alias:别名,可指向一个或多个索引,便于无缝切换

写入与批处理

  • Bulk API:批量写入/更新/删除
  • Update by query:按条件批量更新
  • Delete by query:按条件批量删除
  • Reindex:从源索引复制到目标索引(常用于变更映射)
  • Ingest pipeline:写入前处理管道(grok、rename、set、script 等)
  • Painless:ES 内置脚本语言,用于脚本更新、脚本排序等

搜索与分页

  • Match:全文查询,会分词
  • Term/Terms:精确匹配,不分词
  • Range:范围查询(数值/日期)
  • Multi-match:多字段全文查询
  • Nested query:对 nested 字段的子查询
  • Aggregation:聚合分析(terms、stats、date_histogram、range 等)
  • Highlight:高亮显示命中片段
  • Suggesters:搜索建议(term/phrase/completion)
  • From/size:基础分页,深分页代价高
  • Search after:游标式分页,替代深分页
  • Scroll:大批量导出快照式游标,非实时查询
  • PIT(Point in time):时间点一致性快照,用于稳定分页

生命周期与索引管理

  • ILM(Index Lifecycle Management):热/温/冷/删 生命周期策略
  • Rollover:基于大小/文档数/时间切换新索引
  • Snapshot/Restore:快照与恢复(仓库可对接 S3、HDFS 等)

运维与性能

  • Cluster health:集群健康(green/yellow/red)
  • Refresh interval:刷新周期,写多场景可调大以提速写入
  • Replicas:副本数影响查询吞吐与写入成本
  • Force merge:对只读索引合并段,减少文件数提高查询性能
  • Slow logs:慢查询与慢索引日志,用于排障与优化

以上术语覆盖 ES 日常建模、写入、检索、聚合与运维优化的高频概念,结合前文 Mapping、Analyzer、term/match 与 multi_match 等章节,可形成完整的 ES 使用知识图谱。