MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

ElasticSearch 映射数据类型的扩展应用

2021-03-027.5k 阅读

ElasticSearch 映射数据类型基础回顾

在深入探讨 ElasticSearch 映射数据类型的扩展应用之前,我们先来简要回顾一下其基础数据类型。ElasticSearch 支持多种核心数据类型,这些类型是构建复杂数据结构和高效搜索的基石。

核心数据类型

  1. 字符串类型:在 ElasticSearch 5.x 及之前版本,字符串类型分为 string 类型,之后版本被细分为 textkeyword 类型。text 类型用于全文搜索,会对输入的文本进行分词处理,适合处理长文本内容,例如文章正文。而 keyword 类型则主要用于精确匹配,如产品编号、邮政编码等,它不会对值进行分词,而是将整个值作为一个词条进行索引。
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      },
      "product_id": {
        "type": "keyword"
      }
    }
  }
}
  1. 数值类型:ElasticSearch 提供了丰富的数值类型,包括 long(长整型)、integer(整型)、shortbytedouble(双精度浮点型)、float 等。不同的数值类型适用于不同范围和精度要求的数据。例如,对于表示用户年龄的字段,integer 类型通常就足够了;而对于需要高精度的金融数据,可能会选择 double 类型。
{
  "mappings": {
    "properties": {
      "age": {
        "type": "integer"
      },
      "price": {
        "type": "double"
      }
    }
  }
}
  1. 日期类型:日期类型 date 用于存储日期和时间信息。ElasticSearch 支持多种日期格式的输入,既可以是标准的 ISO 8601 格式,如 2023 - 10 - 05T14:30:00Z,也可以是自定义的日期格式。日期类型在搜索和聚合操作中非常有用,比如按日期范围筛选数据或者统计每天的访问量。
{
  "mappings": {
    "properties": {
      "create_date": {
        "type": "date"
      }
    }
  }
}
  1. 布尔类型boolean 类型用于表示真或假的值,常用于存储逻辑判断相关的数据,如用户是否激活、产品是否上架等。
{
  "mappings": {
    "properties": {
      "is_active": {
        "type": "boolean"
      }
    }
  }
}
  1. 二进制类型binary 类型用于存储二进制数据,如图片、文件等。不过需要注意的是,ElasticSearch 本身并不擅长处理二进制数据的检索,通常将二进制数据存储在外部存储系统(如 S3),然后在 ElasticSearch 中存储其元数据和引用。
{
  "mappings": {
    "properties": {
      "file_content": {
        "type": "binary"
      }
    }
  }
}

复杂数据类型

  1. 数组类型:在 ElasticSearch 中,数组类型并不是一种显式定义的类型,任何字段都可以包含零个或多个值,从而形成数组。例如,一个产品文档可能包含多个类别,或者一个用户可能有多个联系方式。
{
  "mappings": {
    "properties": {
      "categories": {
        "type": "keyword"
      },
      "phone_numbers": {
        "type": "keyword"
      }
    }
  }
}

在索引文档时,可以这样写入数据:

{
  "categories": ["electronics", "smartphones"],
  "phone_numbers": ["123 - 456 - 7890", "098 - 765 - 4321"]
}
  1. 对象类型object 类型用于表示 JSON 对象结构,允许在一个文档中嵌套多个字段。例如,一个用户文档可能包含基本信息(姓名、年龄)和地址信息(城市、街道等),可以将地址信息定义为一个对象类型。
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "age": {
        "type": "integer"
      },
      "address": {
        "type": "object",
        "properties": {
          "city": {
            "type": "text"
          },
          "street": {
            "type": "text"
          }
        }
      }
    }
  }
}
  1. 嵌套类型:嵌套类型 nested 是对象类型的一种特殊形式,用于处理对象数组的情况。当一个文档中的某个字段是对象数组,并且需要对数组中的每个对象进行独立的搜索和聚合时,就需要使用嵌套类型。例如,一个订单文档可能包含多个订单项,每个订单项是一个对象,包含产品信息、数量、价格等。
{
  "mappings": {
    "properties": {
      "order_items": {
        "type": "nested",
        "properties": {
          "product_name": {
            "type": "text"
          },
          "quantity": {
            "type": "integer"
          },
          "price": {
            "type": "double"
          }
        }
      }
    }
  }
}

映射数据类型的扩展应用场景

地理空间数据处理

在现代应用中,地理空间数据的处理越来越重要,如物流、地图服务、位置社交等应用场景。ElasticSearch 提供了专门的地理空间数据类型来满足这些需求。

  1. 地理点类型(geo_point)geo_point 类型用于存储地理位置的经纬度信息。它支持多种格式的输入,包括数组 [lon, lat]、对象 {"lat": lat_value, "lon": lon_value} 以及字符串形式的 lat,lon
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_point"
      }
    }
  }
}

在索引文档时,可以这样写入地理点数据:

{
  "location": [116.4074, 39.9042]
}

地理点类型支持丰富的查询操作,如距离查询,可以查找距离某个指定地点一定范围内的所有文档。例如,查找距离北京天安门(经度 116.3975,纬度 39.9083)10 公里范围内的所有店铺:

{
  "query": {
    "geo_distance": {
      "distance": "10km",
      "location": {
        "lat": 39.9083,
        "lon": 116.3975
      }
    }
  }
}
  1. 地理形状类型(geo_shape)geo_shape 类型用于存储更复杂的地理形状,如多边形、线等。例如,在城市规划应用中,可以使用地理形状类型存储区域边界、道路走向等信息。
{
  "mappings": {
    "properties": {
      "city_boundary": {
        "type": "geo_shape"
      }
    }
  }
}

索引地理形状数据时,例如定义一个多边形:

{
  "city_boundary": {
    "type": "polygon",
    "coordinates": [
      [
        [116.38, 39.91],
        [116.42, 39.91],
        [116.42, 39.89],
        [116.38, 39.89],
        [116.38, 39.91]
      ]
    ]
  }
}

查询时,可以使用 geo_shape 查询来判断某个点是否在多边形内,或者两个地理形状是否相交等。

机器学习相关数据处理

随着机器学习在各个领域的广泛应用,ElasticSearch 也开始支持与机器学习相关的数据类型和功能扩展。

  1. 向量数据类型:在机器学习中,向量数据常用于表示文本、图像、音频等数据的特征。ElasticSearch 从 7.10 版本开始支持向量数据类型,通过 dense_vector 类型可以存储和检索向量数据。例如,在图像搜索应用中,可以将图像经过特征提取后得到的向量存储在 ElasticSearch 中。
{
  "mappings": {
    "properties": {
      "image_vector": {
        "type": "dense_vector",
        "dims": 128,
        "index": true,
        "similarity": "l2_norm"
      }
    }
  }
}

这里 dims 表示向量的维度,similarity 定义了用于计算相似度的方法,如 l2_norm 表示欧几里得距离。在索引文档时,将图像向量作为数组存储:

{
  "image_vector": [0.1, 0.2, 0.3, ..., 0.128]
}

查询时,可以使用 knn(K - 近邻)查询来找到与给定向量最相似的文档,这在图像检索、推荐系统等场景中非常有用。

{
  "query": {
    "knn": {
      "image_vector": {
        "vector": [0.1, 0.2, 0.3, ..., 0.128],
        "k": 10
      }
    }
  }
}
  1. 时间序列数据处理:在工业监控、物联网等场景中,经常会产生大量的时间序列数据。ElasticSearch 通过 date_histogram 聚合和相关的数据类型支持,能够有效地处理和分析时间序列数据。例如,监控工厂设备的运行状态,每分钟记录一次设备的温度、压力等指标。
{
  "mappings": {
    "properties": {
      "timestamp": {
        "type": "date"
      },
      "temperature": {
        "type": "double"
      },
      "pressure": {
        "type": "double"
      }
    }
  }
}

可以使用 date_histogram 聚合来按时间间隔统计数据,例如按小时统计设备温度的平均值:

{
  "aggs": {
    "hourly_temperature": {
      "date_histogram": {
        "field": "timestamp",
        "interval": "hour"
      },
      "aggs": {
        "avg_temperature": {
          "avg": {
            "field": "temperature"
          }
        }
      }
    }
  }
}

文本数据的高级处理

除了基本的文本类型,ElasticSearch 还提供了一些扩展功能来处理更复杂的文本数据需求。

  1. 分词器的扩展应用:分词器是 ElasticSearch 对文本进行分词处理的关键组件。默认的分词器可能无法满足所有的业务需求,因此需要扩展应用自定义分词器。例如,在处理中文文本时,标准分词器可能无法很好地对中文词汇进行切分,这时可以使用 IK 分词器。IK 分词器有两种模式:ik_smart(智能切分)和 ik_max_word(最细粒度切分)。 首先,需要安装 IK 分词器插件,然后在映射中定义使用 IK 分词器:
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

这样,在对包含中文的 content 字段进行索引和搜索时,就会使用 IK 分词器进行分词处理,提高中文文本搜索的准确性。 2. 同义词处理:在文本搜索中,同义词的处理可以提高搜索的召回率。ElasticSearch 支持通过同义词字典来定义同义词。例如,在一个电影搜索应用中,“影片”和“电影”是同义词。可以在配置文件中定义同义词字典,然后在映射中引用:

# synonyms.txt
影片, 电影
{
  "mappings": {
    "properties": {
      "movie_title": {
        "type": "text",
        "analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["synonym"]
        }
      }
    }
  }
}

这里通过自定义分析器,将同义词过滤器 synonym 应用到 movie_title 字段的分词过程中,使得搜索“影片”时也能匹配到包含“电影”的文档。

动态映射与显式映射的权衡及扩展应用

动态映射的原理与特点

  1. 动态映射的工作机制:当 ElasticSearch 接收到一个新文档时,如果该文档的索引不存在,ElasticSearch 会自动创建索引,并根据文档中的字段类型进行动态映射。例如,如果文档中某个字段的值是整数,ElasticSearch 会将该字段映射为 integer 类型;如果是字符串,会根据字符串的内容和配置决定是映射为 text 还是 keyword 类型。
{
  "name": "John Doe",
  "age": 30,
  "is_active": true
}

在这个文档被索引时,ElasticSearch 会动态创建如下映射:

{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "age": {
        "type": "integer"
      },
      "is_active": {
        "type": "boolean"
      }
    }
  }
}
  1. 动态映射的优势与不足:动态映射的优势在于其便捷性和灵活性,特别适合快速开发和数据探索阶段。它可以让开发者无需预先定义复杂的映射结构,直接开始索引数据。然而,动态映射也存在一些不足。例如,由于它是基于文档内容来推断字段类型,可能会导致类型推断错误。比如,一个包含数字的字符串字段,可能会被错误地映射为数值类型。另外,动态映射可能会导致映射结构变得复杂和难以维护,特别是在处理大量不同结构的文档时。

显式映射的应用场景与扩展

  1. 显式映射的定义与使用:显式映射是指开发者手动定义索引的映射结构,明确指定每个字段的类型、分析器、索引设置等。显式映射可以提供更精确的控制,确保数据按照预期的方式进行索引和搜索。例如,在一个电商产品索引中,我们明确知道 product_name 字段需要进行全文搜索,product_id 用于精确匹配,price 是数值类型,我们可以这样定义显式映射:
{
  "mappings": {
    "properties": {
      "product_name": {
        "type": "text",
        "analyzer": "standard"
      },
      "product_id": {
        "type": "keyword"
      },
      "price": {
        "type": "double"
      }
    }
  }
}
  1. 显式映射的扩展应用:在一些复杂的业务场景中,显式映射可以进行更多的扩展。比如,对于需要进行多语言搜索的文档,可以为文本字段定义多个子字段,每个子字段使用不同的分析器来处理不同语言的文本。假设我们有一个多语言的产品描述字段,包含英文和中文:
{
  "mappings": {
    "properties": {
      "product_description": {
        "type": "text",
        "fields": {
          "en": {
            "type": "text",
            "analyzer": "english"
          },
          "zh": {
            "type": "text",
            "analyzer": "ik_max_word"
          }
        }
      }
    }
  }
}

这样,在搜索时可以根据语言选择相应的子字段进行查询,提高多语言搜索的准确性。

动态映射与显式映射的结合使用

在实际应用中,通常会结合动态映射和显式映射的优点。对于一些已知结构和关键的字段,使用显式映射来确保数据的正确处理;而对于一些可能会动态变化的字段或者不太关键的字段,可以利用动态映射的灵活性。例如,在一个日志索引中,日志的基本结构(时间、级别、消息等)是固定的,可以使用显式映射定义这些字段;而对于日志中可能会出现的一些额外的自定义字段,可以让动态映射来处理。

{
  "mappings": {
    "properties": {
      "timestamp": {
        "type": "date"
      },
      "log_level": {
        "type": "keyword"
      },
      "message": {
        "type": "text"
      }
    },
    "dynamic": "strict"
  }
}

这里通过设置 dynamicstrict,表示除了显式定义的字段外,其他字段如果在文档中出现,会抛出异常,防止意外的字段被动态映射。如果希望允许新字段动态映射,但可以控制映射规则,可以设置 dynamictruefalse,并结合 dynamic_templates 来定义动态模板,对新字段的映射进行更细粒度的控制。

{
  "mappings": {
    "properties": {
      "timestamp": {
        "type": "date"
      },
      "log_level": {
        "type": "keyword"
      },
      "message": {
        "type": "text"
      }
    },
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}

在这个例子中,通过 dynamic_templates 定义了一个规则,所有动态映射的字符串类型字段都将被映射为 keyword 类型。

映射数据类型扩展应用中的性能优化

数据类型选择对性能的影响

  1. 数值类型的性能考量:在选择数值类型时,应根据数据的实际范围和精度需求来选择合适的类型。例如,如果数据范围在 0 到 100 之间,使用 byte 类型(范围 -128 到 127)比使用 integer 类型更节省空间,同时在索引和搜索时也可能有更好的性能。因为较小的数据类型占用的存储空间少,在磁盘 I/O 和内存使用上都更高效。但是,如果数据范围不确定且可能超出 byte 类型范围,使用 integer 类型可以避免数据溢出问题。
  2. 文本类型的性能优化:对于 text 类型,选择合适的分析器对性能有很大影响。简单的分析器(如 standard 分析器)在分词时速度较快,但对于复杂的文本处理可能效果不佳。而像 IK 分词器这样的复杂分析器,虽然在处理中文等复杂文本时效果好,但分词速度相对较慢。因此,在选择分析器时,需要在准确性和性能之间进行权衡。对于搜索性能要求极高且文本内容相对简单的场景,可以选择简单的分析器;对于需要高精度搜索的复杂文本场景,即使性能稍有牺牲,也可能需要选择更强大的分析器。

索引结构优化与性能提升

  1. 嵌套类型与对象类型的性能差异:在处理对象数组时,嵌套类型和对象类型的性能表现有所不同。对象类型将整个对象数组作为一个整体进行索引,在搜索时无法对数组中的单个对象进行精确匹配和聚合。而嵌套类型则将数组中的每个对象独立索引,能够满足更细粒度的搜索和聚合需求,但在存储和查询性能上相对对象类型会有一定的开销。因此,如果不需要对对象数组中的单个对象进行独立操作,使用对象类型可以获得更好的性能;如果需要对每个对象进行精确搜索和聚合,虽然嵌套类型性能开销大一些,但能满足业务需求。
  2. 索引分片与副本的性能优化:合理设置索引的分片和副本数量对性能至关重要。分片是 ElasticSearch 分布式存储和处理数据的基本单位,适当增加分片数量可以提高索引的并行处理能力,加快数据的索引速度。但是,过多的分片会增加管理开销和网络通信成本,降低性能。副本则用于提高数据的可用性和读取性能,增加副本数量可以提高读性能,但也会增加写操作的开销,因为每次写操作都需要同步到所有副本。在实际应用中,需要根据数据量、读写负载等因素来动态调整分片和副本数量。例如,对于读多写少的应用场景,可以适当增加副本数量;对于写操作频繁的场景,要谨慎增加副本,并确保分片数量设置合理。

缓存与预热机制在扩展应用中的应用

  1. 查询结果缓存:ElasticSearch 提供了查询结果缓存机制,可以将经常查询的结果缓存起来,减少重复查询的开销。在处理地理空间查询、复杂的聚合查询等性能开销较大的查询时,启用查询结果缓存可以显著提高性能。通过设置 request.cache.enabletrue,可以在查询时启用缓存。例如:
{
  "query": {
    "geo_distance": {
      "distance": "10km",
      "location": {
        "lat": 39.9083,
        "lon": 116.3975
      }
    }
  },
  "request": {
    "cache": {
      "enable": true
    }
  }
}
  1. 索引预热:在启动 ElasticSearch 节点或者索引数据量发生较大变化时,可以使用索引预热机制来提高后续查询的性能。索引预热是指在后台预先执行一些查询操作,将相关的数据块加载到内存中,这样在实际查询时可以直接从内存中获取数据,减少磁盘 I/O。可以通过 _warmup API 来执行索引预热操作。例如,预热一个地理空间索引:
POST /my_geo_index/_warmup
{
  "query": {
    "geo_distance": {
      "distance": "50km",
      "location": {
        "lat": 39.9083,
        "lon": 116.3975
      }
    }
  }
}

通过合理应用缓存和预热机制,可以在 ElasticSearch 映射数据类型的扩展应用中进一步提升性能,满足不同业务场景下的高效运行需求。