找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3073

积分

0

好友

415

主题
发表于 4 小时前 | 查看: 2| 回复: 0

Elasticsearch 是一个基于 Apache Lucene 搜索引擎库构建的、可扩展的分布式搜索与分析引擎。它具备近实时的全文搜索、分析结构化数据以及处理大规模数据的能力。对于刚接触 Elasticsearch 的开发者而言,理解其基本概念和操作是入门的第一步。如果想了解更多系统性的后端架构知识,欢迎访问 云栈社区 的相关板块。

为了方便理解,我们可以将其核心概念与关系型数据库做一个类比:

数据库 Elasticsearch
数据库 (Database) 索引 (Index)
表 (Table) 索引 (Index) (注:7.0后类比更接近表)
行 (Row) 文档 (Document)
列 (Column) 字段 (Field)
表结构定义 (Schema) 映射 (Mapping)

数据类型

如上表所示,一个索引包含多个字段,每个字段需要定义其数据类型。Elasticsearch 提供了丰富的数据类型,以下是几种常用的核心类型:

Keyword
用于存储字符类型数据,不会被分词器处理,在存储上做了优化。它非常适合用于精确值匹配、排序、聚合(分组)以及 term 查询。keyword 类型下还有几个子类型:

  • keyword: 最常用的类型,适用于身份证号、状态码、标签等具有标识性的数据。(数字类型适合范围查询,而 keyword 类型则对 term 精确匹配查询效率更高,实际使用时需要根据场景选择)。
  • constant_keyword: 适用于索引中所有文档在该字段的值都相同的场景。设计目的是为了节约存储空间,因为该值只存储一份。
  • wildcard: keyword 的另一种优化实现,通常在字段值内容区分度高,且计划使用 wildcard(通配符)或 regexp(正则表达式)查询时使用。

Text
用于存储需要被全文检索的文本数据。在保存时,文本会被指定的分析器(analyzer)分词,然后对分词结果建立倒排索引。

  • text: 标准的文本类型,适用于文章内容、商品描述等非结构化文本。
  • match_only_text: 对 text 类型的优化版本,它不记录词项的位置和频率信息,仅记录词项是否存在。适用于日志内容或仅需判断关键词是否存在的场景,能显著节省磁盘空间。

数字类型
Elasticsearch 支持多种数值类型:long, integer, short, byte, double, float, half_float, scaled_float, unsigned_long。选择原则是:在满足业务需求的前提下,尽量选择范围最小的类型,这有助于提升存储和查询效率。
特别说明一下 scaled_float:它通过一个 scaling_factor(缩放因子)将浮点数转换为整数进行存储和计算,效率比直接存浮点数高。例如,对于数值 2.34,设置 scaling_factor100,实际存储为 234。缩放因子必须设置得足够大,以避免数据精度丢失。

二进制 (Binary)
用于存储 Base64 编码的二进制数据。注意,编码后的字符串不应包含换行符 \n

布尔 (Boolean)
布尔类型。值 true"true" 会被解释为 true;值 false"false" 或空字符串 "" 会被解释为 false

日期 (Date)
Elasticsearch 提供了 datedate_nanos 两种日期类型。写入时,可以传入格式化的日期字符串或时间戳数字,ES 会将其统一转换为对应的内部表示(date 为毫秒,date_nanos 为纳秒)。查询时,内部表示会再转换回指定的格式。格式可以通过字段映射中的 format 属性来定义。

对象类型 (Object Types)
用于处理包含嵌套属性的复杂结构。主要分为以下几类:

  • Object: 标准的对象类型。写入后,对象的每个子字段会被单独索引和存储。例如以下映射:
    "manager": {
        "properties": {
            "age": { "type": "integer" },
            "name": {
                "properties": {
                    "first": { "type": "text" },
                    "last": { "type": "text" }
                }
            }
        }
    }

    实际上会创建三个可被独立查询的字段:manager.age, manager.name.first, manager.name.last

  • Flattened: 将整个对象的所有子字段值“扁平化”处理,合并为一个数组,并作为 keyword 类型存储。它适合子字段不需要被独立查询,只需进行存在性检查或简单匹配的场景。例如数据 {"manager": {"sex": "man", "name": {"first": "John", "last": "Smith"}}} 会被存储为 manager: ["man", "John", "Smith"]
  • Nested: 为了解决 Object 类型在处理对象数组时丢失内部对象间关联的问题而设计。当字段值是对象数组时,如果使用 Object 类型,每个对象的属性会被拆分合并,导致对象间的边界消失。使用 Nested 类型,每个内部对象会被独立索引和存储,保持了对象内部的关联性。但查询时必须使用专门的 nested 查询,且开销相对较大。
    举例说明区别:
    "user" : [
        { "first" : "John", "last" : "Smith" },
        { "first" : "Alice", "last" : "White" }
    ]
    • 如果是 Object 类型,最终存储为:user.first: ["John", "Alice"], user.last: ["Smith", "White"]。此时查询 user.first=John AND user.last=White 也会命中,这显然不符合原始数据中对象的对应关系。
    • 如果是 Nested 类型,则两个对象会分别被索引,上述查询不会命中,准确反映了数据关系。

索引与映射操作

与关系型数据库类似,在存入数据前最好先定义索引的映射(Mapping)。虽然 Elasticsearch 支持动态映射(根据写入的数据自动推断类型),但这可能导致推断出的类型不符合预期,因此推荐手动明确定义。

创建映射
使用 RESTful API 创建索引并定义映射。

PUT /your_index_name
{
  "mappings": {
    "properties": {
      "field1":    { "type": "keyword" },
      "email":  { "type": "keyword" },
      "name":   { "type": "text" }
    }
  }
}

增加映射
为已存在的索引添加新的字段。

PUT /your_index_name/_mapping
{
  "properties": {
    "employee-id": {
      "type": "keyword"
    }
  }
}

修改映射
注意:Elasticsearch 不允许直接修改已有字段的映射定义(如更改数据类型、修改分词器等)。如果需要调整,通常的做法是创建一个新索引并定义正确的映射,然后将数据从旧索引迁移(Reindex)到新索引。另一种折中方案是为字段设置别名(Alias)。

查看映射

# 查看整个索引的映射
GET /your_index_name/_mapping
# 查看索引中特定字段的映射详情
GET /your_index_name/_mapping/field/field_name

文档数据操作 (CRUD)

定义好索引映射后,即可进行文档的增删改查。

新增文档

## 仅当指定文档ID不存在时创建(如果存在则失败)
PUT your_index_name/_create/document_id
{
   "field": "value",
  "message": "GET /search HTTP/1.1 200 1070000",
  "user": {
    "id": "kimchy"
  }
}

## 使用指定ID,如果ID不存在则创建,如果已存在则执行全量替换更新
PUT your_index_name/_doc/document_id
{
  "field": "value",
  "message": "GET /search HTTP/1.1 200 1070000",
  "user": {
    "id": "kimchy"
  }
}

修改文档

## 部分更新:只更新请求体中给出的字段
POST /your_index_name/_update/document_id
{
  "doc": {
    "field": "new_value",
    "age": 121
  }
}

## 使用脚本更新(可以完成更复杂的逻辑)
POST your_index_name/_update/document_id
{
  "script": {
    "source": "ctx._source.age = params.count",
    "lang": "painless",
    "params": {
      "count": 4
    }
  }
}

## 更新或插入(upsert):如果文档存在则更新,否则插入新文档
POST /your_index_name/_update/document_id
{
  "doc": {
    "product_price": 100
  },
  "upsert": {
    "product_price": 50
  }
}

删除文档


DELETE /your_index_name/_doc/document_id



上一篇:Go defer 深度避坑指南:六大高频陷阱与实战解决方案
下一篇:基于bert-as-service快速构建本地问答搜索引擎的Python实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-8 08:56 , Processed in 0.582789 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表