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

ElasticSearch索引映射机制

2023-02-093.1k 阅读

什么是 ElasticSearch 索引映射机制

在 ElasticSearch 中,索引映射(Index Mapping)是定义文档及其包含的字段如何存储和索引的一种机制。简单来说,它就像是数据库表结构的定义,但在 ElasticSearch 中更加灵活和动态。通过索引映射,我们可以控制字段的数据类型(如文本、数字、日期等)、是否分词、是否存储等关键属性。这对于确保数据的正确索引和检索至关重要。

例如,假设我们有一个博客文章的索引。其中文章标题字段我们希望它能被全文检索,并且分词处理,而文章发布日期字段我们希望按日期类型进行存储和检索,方便后续按时间范围查询。这时,索引映射就可以帮我们实现这些需求。

索引映射的核心概念

  1. 字段类型:ElasticSearch 支持多种字段类型,常见的如 text(用于全文搜索,会进行分词)、keyword(用于精确匹配,不分词)、longintegershortbyte 等数值类型,以及 date 日期类型等。不同的字段类型决定了数据的存储方式和检索方式。
    • text 类型:适用于需要进行全文搜索的文本字段,如文章内容、产品描述等。当一个字段被定义为 text 类型时,ElasticSearch 会使用分词器将文本拆分成一个个词项(terms)进行索引。例如,对于 “ElasticSearch is a great search engine” 这样的文本,可能会被分词为 “elasticsearch”、“is”、“a”、“great”、“search”、“engine” 等词项。
    • keyword 类型:用于精确匹配的场景,如产品 ID、邮政编码等。它不会对输入的文本进行分词,而是将整个文本作为一个词项进行索引。如果我们存储一个产品 ID “P001”,那么在检索时必须精确匹配 “P001” 才能找到对应的文档。
    • 数值类型:根据数据的范围和精度,选择合适的数值类型。比如,如果我们要存储年龄,integer 类型就足够了;但如果要存储股票价格这种需要高精度小数的,可能就需要 floatdouble 类型。
    • date 类型:用于存储日期和时间。ElasticSearch 支持多种日期格式,既可以是标准的 ISO 8601 格式(如 “2023 - 10 - 01T12:00:00Z”),也可以自定义日期格式。日期类型方便我们进行日期范围查询、排序等操作。
  2. 分词器:分词器(Analyzer)是将文本转换为词项的工具。ElasticSearch 提供了多种内置的分词器,如 standard 分词器(默认分词器,按词切分,去除停用词等)、simple 分词器(按非字母字符切分)、whitespace 分词器(按空白字符切分)等。此外,我们还可以自定义分词器,以满足特定的需求。例如,如果我们处理中文文本,可能需要使用专门的中文分词器,如 ik_max_word(将文本按最大词长切分)或 ik_smart(智能切分)。
  3. 字段属性:除了字段类型,我们还可以设置其他一些字段属性。
    • index:该属性决定字段是否被索引,默认值为 true。如果设置为 false,则该字段无法被搜索,但仍会存储在文档中。例如,一些内部使用的字段,我们可能不希望用户搜索到,但又需要存储其值,就可以将 index 设置为 false
    • store:决定字段是否单独存储。默认情况下,ElasticSearch 会将文档的原始内容存储在 _source 字段中。如果将 store 设置为 true,则该字段会被额外存储,可以直接从存储中获取,而不需要从 _source 中解析。不过,这会增加存储空间的开销,一般只有在需要频繁访问该字段且不希望从 _source 解析时才使用。
    • doc_values:主要用于排序、聚合和脚本中访问字段值。对于某些字段类型(如 keyword、数值类型、日期类型等),默认会启用 doc_values。它以列存储的方式存储字段值,提高了这些操作的效率。

创建索引映射

在 ElasticSearch 中,我们可以在创建索引时定义索引映射,也可以在索引创建后动态添加或修改映射。

  1. 创建索引时定义映射:我们可以使用 ElasticSearch 的 REST API 来创建索引并定义映射。以下是一个使用 PUT 请求创建索引并定义映射的示例:
PUT /my_index
{
    "mappings": {
        "properties": {
            "title": {
                "type": "text",
                "analyzer": "standard"
            },
            "content": {
                "type": "text",
                "analyzer": "ik_max_word"
            },
            "product_id": {
                "type": "keyword"
            },
            "price": {
                "type": "float"
            },
            "publish_date": {
                "type": "date",
                "format": "yyyy - MM - dd"
            }
        }
    }
}

在上述示例中,我们创建了一个名为 my_index 的索引,并定义了几个字段的映射。title 字段使用标准分词器进行全文搜索,content 字段使用 ik_max_word 分词器(假设已安装 ik 插件)处理中文文本,product_id 作为精确匹配的 keyword 字段,price 为浮点型数值字段,publish_date 为日期类型字段,并指定了日期格式。 2. 动态添加或修改映射:有时候,我们可能需要在索引已经创建后添加新的字段或修改现有字段的映射。ElasticSearch 支持动态映射,默认情况下,如果我们索引一个文档包含新的字段,ElasticSearch 会自动为该字段添加映射。例如:

POST /my_index/_doc
{
    "new_field": "This is a new field value"
}

在上述操作后,my_index 索引会自动添加一个 new_field 的映射,其类型会根据值的类型自动推断。不过,对于一些复杂的映射需求,我们可能需要手动更新映射。例如,要添加一个新的 text 类型字段 description,并指定分词器:

PUT /my_index/_mapping
{
    "properties": {
        "description": {
            "type": "text",
            "analyzer": "standard"
        }
    }
}

需要注意的是,对于已经存在数据的字段,修改其类型可能会导致数据丢失或索引重建。因此,在生产环境中修改映射需要谨慎操作。

复杂类型的索引映射

  1. 对象类型(Object Type):在 ElasticSearch 中,一个文档可以包含对象类型的字段,用于表示复杂的数据结构。例如,假设我们有一个用户文档,其中包含用户的基本信息和地址信息,地址信息可以作为一个对象字段。
PUT /user_index
{
    "mappings": {
        "properties": {
            "name": {
                "type": "text"
            },
            "age": {
                "type": "integer"
            },
            "address": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "text"
                    },
                    "street": {
                        "type": "text"
                    },
                    "zip_code": {
                        "type": "keyword"
                    }
                }
            }
        }
    }
}

在上述示例中,address 字段是一个对象类型,它包含了 citystreetzip_code 三个子字段。在索引文档时,我们可以这样表示:

POST /user_index/_doc
{
    "name": "John Doe",
    "age": 30,
    "address": {
        "city": "New York",
        "street": "123 Main St",
        "zip_code": "10001"
    }
}
  1. 嵌套类型(Nested Type):当对象类型的字段需要进行独立的搜索和聚合时,我们可以使用嵌套类型。嵌套类型本质上是将每个嵌套对象作为一个独立的文档进行索引,从而允许对嵌套对象进行更灵活的操作。例如,假设我们有一个订单文档,每个订单包含多个订单项,订单项需要独立进行搜索和统计。
PUT /order_index
{
    "mappings": {
        "properties": {
            "order_number": {
                "type": "keyword"
            },
            "order_date": {
                "type": "date",
                "format": "yyyy - MM - dd"
            },
            "items": {
                "type": "nested",
                "properties": {
                    "product_name": {
                        "type": "text"
                    },
                    "quantity": {
                        "type": "integer"
                    },
                    "price": {
                        "type": "float"
                    }
                }
            }
        }
    }
}

索引文档示例:

POST /order_index/_doc
{
    "order_number": "O12345",
    "order_date": "2023 - 10 - 01",
    "items": [
        {
            "product_name": "Laptop",
            "quantity": 1,
            "price": 1000.0
        },
        {
            "product_name": "Mouse",
            "quantity": 2,
            "price": 50.0
        }
    ]
}

在查询时,我们可以使用嵌套查询来针对嵌套对象进行精确搜索。例如,查找包含 “Laptop” 订单项的订单:

GET /order_index/_search
{
    "query": {
        "nested": {
            "path": "items",
            "query": {
                "match": {
                    "items.product_name": "Laptop"
                }
            }
        }
    }
}
  1. 数组类型(Array Type):ElasticSearch 中不需要专门定义数组类型,任何字段都可以包含多个值,即数组。例如,一个文章可能有多个标签,我们可以这样定义映射:
PUT /article_index
{
    "mappings": {
        "properties": {
            "title": {
                "type": "text"
            },
            "tags": {
                "type": "keyword"
            }
        }
    }
}

索引文档时:

POST /article_index/_doc
{
    "title": "ElasticSearch Basics",
    "tags": ["elasticsearch", "search", "database"]
}

在查询时,我们可以使用 terms 查询来匹配数组中的多个值。例如,查找标签包含 “elasticsearch” 和 “search” 的文章:

GET /article_index/_search
{
    "query": {
        "terms": {
            "tags": ["elasticsearch", "search"]
        }
    }
}

索引映射与搜索相关性

索引映射对于搜索的相关性有着重要的影响。正确的字段类型定义和分词器选择可以提高搜索结果的质量。

  1. 文本字段与相关性:对于 text 类型字段,分词器的选择直接影响到搜索相关性。例如,如果我们使用 standard 分词器处理英文文本,它会将文本按词切分并去除停用词。但如果我们处理的是一些特定领域的文本,可能需要自定义分词器,以保留一些关键术语。比如在医学领域,一些专业术语可能不应该被拆分。此外,字段的权重设置也会影响相关性。我们可以通过 boost 属性为不同的字段设置权重,权重越高的字段在搜索相关性计算中所占的比重越大。例如:
PUT /medical_index
{
    "mappings": {
        "properties": {
            "title": {
                "type": "text",
                "boost": 2,
                "analyzer": "standard"
            },
            "content": {
                "type": "text",
                "analyzer": "standard"
            }
        }
    }
}

在上述示例中,title 字段的权重是 content 字段的两倍,这意味着在搜索时,匹配到 title 字段的文档会有更高的相关性得分。 2. 数值和日期字段与相关性:数值和日期字段虽然不涉及分词,但它们在范围查询和排序中的表现也会影响搜索结果的相关性。例如,在电商搜索中,如果用户搜索价格在某个范围内的产品,准确的数值类型定义和索引结构可以快速筛选出符合条件的产品。对于日期字段,按时间顺序排序可以让最新发布的文档排在前面,这对于新闻、博客等应用场景非常重要。

索引映射的优化策略

  1. 合理选择字段类型:避免过度使用 text 类型,对于不需要全文搜索的字段,尽量使用 keyword 或数值类型等。例如,产品分类字段如果只是用于精确筛选,使用 keyword 类型可以减少索引空间和提高查询效率。同时,根据数据的实际范围选择合适的数值类型,避免使用过大的类型造成空间浪费。
  2. 优化分词器:对于中文文本,选择合适的中文分词器(如 ik 系列分词器)。并且可以根据业务需求对分词器进行定制,如添加自定义词典。对于英文文本,根据文本特点选择合适的内置分词器或进行定制。例如,对于代码相关的文本,可能需要保留一些特殊的标识符,这时就需要调整分词规则。
  3. 控制动态映射:虽然动态映射很方便,但在生产环境中,应尽量避免无限制的动态映射。因为自动推断的映射可能不符合我们的业务需求,而且过多的动态映射会增加索引的复杂性和维护成本。可以通过设置 index.mapping.dynamic 参数来控制动态映射的行为,例如设置为 strict,当遇到新字段时会抛出异常,强制我们手动定义映射。
  4. 定期清理和优化映射:随着业务的发展,可能会有一些不再使用的字段留在索引映射中。定期检查和清理这些无用的字段,可以减少索引的存储空间和提高查询性能。同时,如果发现某些字段的映射不合理,如分词器选择错误或字段类型不当,应及时进行调整和优化。

索引映射在不同场景下的应用

  1. 电商搜索:在电商平台中,索引映射需要满足商品的各种搜索需求。商品名称字段可以定义为 text 类型,并使用适合商品描述的分词器,以支持全文搜索。商品 ID、品牌等字段使用 keyword 类型,方便精确匹配。价格字段使用数值类型,支持范围查询和排序。商品的分类字段可以使用 keyword 类型,并且可以通过父子关系或嵌套关系来处理多级分类结构。例如:
PUT /product_index
{
    "mappings": {
        "properties": {
            "product_name": {
                "type": "text",
                "analyzer": "ik_max_word"
            },
            "product_id": {
                "type": "keyword"
            },
            "brand": {
                "type": "keyword"
            },
            "price": {
                "type": "float"
            },
            "category": {
                "type": "keyword"
            },
            "sub_category": {
                "type": "keyword"
            }
        }
    }
}
  1. 日志管理:在日志管理系统中,日志记录可能包含时间戳、日志级别、日志消息等字段。时间戳字段使用 date 类型,方便按时间范围查询日志。日志级别字段可以使用 keyword 类型,用于筛选特定级别的日志。日志消息字段使用 text 类型,以便进行全文搜索。例如:
PUT /log_index
{
    "mappings": {
        "properties": {
            "timestamp": {
                "type": "date",
                "format": "yyyy - MM - dd HH:mm:ss"
            },
            "log_level": {
                "type": "keyword"
            },
            "message": {
                "type": "text"
            }
        }
    }
}
  1. 企业知识管理:企业内部的知识库可能包含文档标题、文档内容、作者、标签等字段。文档标题和内容使用 text 类型,并且可以根据文档语言选择合适的分词器。作者字段可以使用 keyword 类型,标签字段可以使用 keyword 数组类型。例如:
PUT /knowledge_index
{
    "mappings": {
        "properties": {
            "title": {
                "type": "text",
                "analyzer": "standard"
            },
            "content": {
                "type": "text",
                "analyzer": "ik_max_word"
            },
            "author": {
                "type": "keyword"
            },
            "tags": {
                "type": "keyword"
            }
        }
    }
}

索引映射的性能考虑

  1. 索引性能:复杂的索引映射,如过多的嵌套对象或复杂的分词器设置,可能会降低索引性能。因为 ElasticSearch 在索引文档时需要对字段进行解析、分词等操作,复杂的映射会增加这些操作的复杂度和时间。为了提高索引性能,可以尽量简化映射结构,避免不必要的嵌套和复杂分词逻辑。例如,对于一些简单的对象字段,如果不需要独立搜索和聚合,可以使用对象类型而不是嵌套类型。
  2. 搜索性能:合理的索引映射对于搜索性能至关重要。例如,正确的字段类型定义可以使 ElasticSearch 更高效地执行查询。如果将一个应该是 keyword 类型的字段错误定义为 text 类型,可能会导致搜索时进行不必要的分词和全文搜索,从而降低搜索效率。此外,索引的分片和副本设置也会影响搜索性能,在设计索引映射时,要结合数据量和查询模式来合理设置这些参数。

在实际应用中,我们需要综合考虑业务需求、数据特点和性能要求,精心设计 ElasticSearch 的索引映射,以充分发挥其强大的搜索和数据分析能力。通过不断优化索引映射,我们可以提高系统的整体性能和用户体验。无论是处理大规模的电商数据、复杂的日志记录还是企业内部的知识管理,正确的索引映射都是成功应用 ElasticSearch 的关键一步。同时,随着业务的发展和数据的变化,我们要持续关注索引映射的合理性,及时进行调整和优化。