Go工程师体系课 011【学习笔记】

Table of Contents

查询的倒排索引

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 使用知识图谱。

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://joyjs.cn/archives/4784

(0)
Walker的头像Walker
上一篇 2025年11月25日 11:00
下一篇 2025年11月25日 09:00

相关推荐

  • 向世界挥手,拥抱无限可能 🌍✨

    站得更高,看到更远 生活就像一座座高楼,我们不断向上攀登,不是为了炫耀高度,而是为了看到更广阔的风景。图中的两位女孩站在城市之巅,伸展双手,仿佛在迎接世界的无限可能。这不仅是一次俯瞰城市的旅程,更是对自由和梦想的礼赞。 勇敢探索,突破边界 每个人的生活都是一场冒险,我们生而自由,就该去探索未知的风景,去经历更多的故事。或许路途中会有挑战,但正是那些攀爬的瞬间…

    个人 2025年2月26日
    1.2K00
  • Go工程师体系课 001【学习笔记】

    转型 想在短时间系统转到Go工程理由 提高CRUD,无自研框架经验 拔高技术深度,做专、做精需求的同学 进阶工程化,拥有良好开发规范和管理能力的 工程化的重要性 高级开的期望 良好的代码规范 深入底层原理 熟悉架构 熟悉k8s的基础架构 扩展知识广度,知识的深度,规范的开发体系 四个大的阶段 go语言基础 微服务开发的(电商项目实战) 自研微服务 自研然后重…

    个人 2025年11月25日
    24400
  • 无畏前行,拳释力量 🥊💪

    拼搏,是一种态度 生活就像一场比赛,没有捷径可走,只有不断训练、突破、超越,才能站上属于自己的舞台。这不仅是一种对抗,更是一种自我的觉醒——敢于迎战,敢于挑战,敢于成为更强的自己。 运动中的拼搏精神 无论是拳击、跑步,还是力量训练,每一次出拳、每一次挥汗、每一次咬牙坚持,都是对身体与心灵的磨炼。拼搏不是单纯的对抗,而是一种态度——面对挑战,不退缩;面对失败,…

    个人 2025年2月26日
    1.2K00
  • 热爱运动,挑战极限,拥抱自然

    热爱 在这个快节奏的时代,我们被工作、生活的压力所包围,常常忽略了身体的需求。而运动,不仅仅是一种健身方式,更是一种释放自我、挑战极限、与自然共舞的生活态度。无论是滑雪、攀岩、冲浪,还是跑步、骑行、瑜伽,每一种运动都能让我们找到内心的激情,感受到生命的跃动。 运动是一场自我挑战 挑战极限,不仅仅是职业运动员的专属,而是每一个热爱运动的人都可以追求的目标。它可…

    个人 2025年2月26日
    1.3K00
  • 深入理解ES6 006【学习笔记】

    Symbol和Symbol属性 第6种原始数据类型:Symbol。私有名称原本是为了让开发者们创建非字符串属性名称而设计的,但是一般的技术无法检测这些属性的私有名称 创建Symbol let firstName = Symbol(); let person = {} person[firstName] = "Nicholas"; cons…

    个人 2025年3月8日
    1.2K00

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
欢迎🌹 Coding never stops, keep learning! 💡💻 光临🌹