[Elasticsearch] 索引管理 (二)

自定义解析器(Custom Analyzers)

虽然ES本身已经提供了一些解析器,但是通过组合字符过滤器(Character Filter),分词器(Tokenizer)以及词条过滤器(Token Filter)来创建你自己的解析器才会显示出其威力。

解析和解析器中,我们提到过解析器(Analyzer)就是将3种功能打包得到的,它会按照下面的顺序执行:

  • 字符过滤器(Character Filter) 字符过滤器用来在分词前将字符串进行"整理"。比如,如果文本是HTML格式,那么它会含有类似<p>或者<div>这样的HTML标签,但是这些标签我们是不需要索引的。我们可以使用html_strip字符过滤器移除所有的HTML标签,并将所有的像á这样的HTML实体(HTML
    Entity)转换为对应的Unicode字符:á。
  • 分词器(Tokenizers) 一个解析器必须有一个分词器。分词器将字符串分解成一个个单独的词条(Term or Token)。在standard解析器中使用的standard分词器,通过单词边界对字符串进行划分来得到词条,同时会移除大部分的标点符号。另外还有其他的分词器拥有着不同的行为。

    比如keyword分词器,它不会进行任何分词,直接原样输出。whitespace分词器则只通过对空白字符进行划分来得到词条。而pattern分词器则根据正则表达式来进行分词。

  • 词条过滤器(Token Filter) 在分词后,得到的词条流(Token Stream)会按照顺序被传入到指定的词条过滤器中。

    词条过滤器能够修改,增加或者删除词条。我们已经提到了lowercase词条过滤器stop词条过滤器,但是ES中还有许多其它可用的词条过滤器。stemming词条过滤器会对单词进行词干提取来得到其词根形态(Root
    Form)。ascii_folding词条过滤器则会移除变音符号(Diacritics),将类似于très的词条转换成tresngram词条过滤器edge_ngram词条过滤器会产生适用于部分匹配(Partial
    Matching)或者自动完成(Autocomplete)的词条。

深入搜索中,我们会通过例子来讨论这些分词器和过滤器的使用场景和使用方法。但是首先,我们需要解释如何来创建一个自定义的解析器。

创建一个自定义的解析器

和上面我们配置es_std解析器的方式相同,我们可以在analysis下对字符过滤器,分词器和词条过滤器进行配置:

PUT /my_index
{
    "settings": {
        "analysis": {
            "char_filter": { ... custom character filters ... },
            "tokenizer":   { ...    custom tokenizers     ... },
            "filter":      { ...   custom token filters   ... },
            "analyzer":    { ...    custom analyzers      ... }
        }
    }
}

比如,要创建拥有如下功能的解析器:

  1. 使用html_strip字符过滤器完成HTML标签的移除。
  2. 将&字符替换成" and ",使用一个自定义的mapping字符过滤器。
"char_filter": {
    "&_to_and": {
        "type":       "mapping",
        "mappings": [ "&=> and "]
    }
}
  1. 使用standard分词器对文本进行分词。
  2. 使用lowercase词条过滤器将所有词条转换为小写。
  3. 使用一个自定义的stopword列表,并通过自定义的stop词条过滤器将它们移除:
"filter": {
    "my_stopwords": {
        "type":        "stop",
        "stopwords": [ "the", "a" ]
    }
}

我们的解析器将预先定义的分词器和过滤器和自定义的过滤器进行了结合:

"analyzer": {
    "my_analyzer": {
        "type":           "custom",
        "char_filter":  [ "html_strip", "&_to_and" ],
        "tokenizer":      "standard",
        "filter":       [ "lowercase", "my_stopwords" ]
    }
}

因此,整个create-index请求就像下面这样:

PUT /my_index
{
    "settings": {
        "analysis": {
            "char_filter": {
                "&_to_and": {
                    "type":       "mapping",
                    "mappings": [ "&=> and "]
            }},
            "filter": {
                "my_stopwords": {
                    "type":       "stop",
                    "stopwords": [ "the", "a" ]
            }},
            "analyzer": {
                "my_analyzer": {
                    "type":         "custom",
                    "char_filter":  [ "html_strip", "&_to_and" ],
                    "tokenizer":    "standard",
                    "filter":       [ "lowercase", "my_stopwords" ]
            }}
}}}

创建索引之后,使用analyze API对新的解析器进行测试:

GET /my_index/_analyze?analyzer=my_analyzer
The quick & brown fox

得到的部分结果如下,表明我们的解析器能够正常工作:

{
  "tokens" : [
      { "token" :   "quick",    "position" : 2 },
      { "token" :   "and",      "position" : 3 },
      { "token" :   "brown",    "position" : 4 },
      { "token" :   "fox",      "position" : 5 }
    ]
}

我们需要告诉ES这个解析器应该在什么地方使用。我们可以将它应用在string字段的映射中:

PUT /my_index/_mapping/my_type
{
    "properties": {
        "title": {
            "type":      "string",
            "analyzer":  "my_analyzer"
        }
    }
}

类型和映射(Types and Mappings)

在ES中的类型(Type)代表的是一类相似的文档。一个类型包含了一个名字(Name) - 比如user或者blogpost -
以及一个映射(Mapping)。映射就像数据库的模式那样,描述了文档中的字段或者属性,和每个字段的数据类型 -stringintegerdate
- 这些字段是如何被Lucene索引和存储的。

什么是文档中,我们说一个类型就好比关系数据库中的一张表。尽管一开始这样思考有助于理解,但是对类型本身进行更细致的解释
- 它们到底是什么,它们是如何在Lucene的基础之上实现的 - 仍然是有价值的。

Lucene是如何看待文档的

Lucene中的文档包含的是一个简单field-value对的列表。一个字段至少要有一个值,但是任何字段都可以拥有多个值。类似的,一个字符串值也可以通过解析阶段而被转换为多个值。Lucene不管值是字符串类型,还是数值类型或者什么别的类型 - 所有的值都会被同等看做一些不透明的字节(Opaque bytes)。

当我们使用Lucene对文档进行索引时,每个字段的值都会被添加到倒排索引(Inverted Index)的对应字段中。原始值也可以被选择是否会不作修改的被保存到索引中,以此来方便将来的获取。

类型是如何实现的

ES中的type是基于以下简单的基础进行实现的。一个索引中可以有若干个类型,每个类型又有它自己的mapping,然后类型下的任何文档可以存储在同一个索引中。

可是Lucene中并没有文档类型这一概念。所以在具体实现中,类型信息通过一个元数据字段_type记录在文档中。当我们需要搜索某个特定类型的文档时,ES会自动地加上一个针对_type字段的过滤器来保证返回的结果都是目标类型上的文档。

同时,Lucene中也没有映射的概念。映射是ES为了对复杂JSON文档进行扁平化(可以被Lucene索引)而设计的一个中间层。

比如,user类型的name字段可以定义成一个string类型的字段,而它的值则应该被whitespace解析器进行解析,然后再被索引到名为name的倒排索引中。

"name": {
    "type":     "string",
    "analyzer": "whitespace"
}

避免类型中的陷阱

由于不同类型的文档能够被添加到相同的索引中,产生了一些意想不到的问题。

比如在我们的索引中,存在两个类型:blog_en用来保存英文的博文,blog_es用来保存西班牙文的博文。这两种类型中都有一个title字段,只不过它们使用的解析器分别是englishspanish

问题可以通过下面的查询反映:

GET /_search
{
    "query": {
        "match": {
            "title": "The quick brown fox"
        }
    }
}

我们在两个类型中搜索title字段。查询字符串(Query String)需要被解析,但是应该使用哪个解析器:是spanish还是english?答案是会利用首先找到的title字段对应的解析器,因此对于部分文档这样做是正确的,对于另一部分则不然。

我们可以通过将字段命名地不同 - 比如title_entitle_es -
或者通过显式地将类型名包含在字段名中,然后对每个字段独立查询来避免这个问题:

GET /_search
{
    "query": {
        "multi_match": {
            "query":    "The quick brown fox",
            "fields": [ "blog_en.title", "blog_es.title" ]
        }
    }
}

multi_match查询会对指定的多个字段运行match查询,然后合并它们的结果。

以上的查询中对blog_en.title字段使用english解析器,对blog_es.title字段使用spanish解析器,然后对两个字段的搜索结果按照相关度分值进行合并。

这个解决方案能够在两个域是相同数据类型时起作用,但是考虑下面的场景,当向相同索引中添加两份文档时会发生什么:

类型user

{ "login": "john_smith" }

类型event

{ "login": "2014-06-01" }

Lucene本身不在意类型一个字段是字符串类型,而另一个字段是日期类型 - 它只是愉快地将它们当做字节数据进行索引。

但是当我们试图去针对event.login字段进行排序的时候,ES需要将login字段的值读入到内存中。根据Fielddata提到的,ES会将索引中的所有文档都读入,无论其类型是什么。

取决于ES首先发现的login字段的类型,它会试图将这些值当做字符串或者日期类型读入。因此,这会产生意料外的结果或者直接失败。

Tip 为了避免发生这些冲突,建议索引中,每个类型的同名字段都使用相同的映射方式。

时间: 2024-11-03 23:33:55

[Elasticsearch] 索引管理 (二)的相关文章

[Elasticsearch] 索引管理 (一)

索引管理 本文翻译自Elasticsearch官方指南的索引管理(Index Management)一章 我们已经了解了ES是如何在不需要任何复杂的计划和安装就能让我们很容易地开始开发一个新的应用的.但是,用不了多久你就会想要仔细调整索引和搜索过程来更好的适配你的用例. 几乎所有的定制都和索引(Index)以及其中的类型(Type)相关.本章我们就来讨论用于管理索引和类型映射的API,以及最重要的设置. 创建索引 到现在为止,我们已经通过索引一份文档来完成了新索引的创建.这个索引是使用默认的设置

[Elasticsearch] 索引管理 (五) - 默认映射,重索引,索引别名

默认映射(Default Mapping) 一般情况下,索引中的所有类型都会有相似的字段和设置.因此将这些常用设置在_default映射中指定会更加方便,这样就不需要在每次创建新类型的时候都重复设置._default映射的角色是新类型的模板.所有在_default映射之后创建的类型都会包含所有的默认设置,除非显式地在类型映射中进行覆盖. 比如,我们使用_default映射对所有类型禁用_all字段,唯独对blog类型启用它.可以这样实现: PUT /my_index { "mappings&qu

Elasticsearch索引管理

1.判断索引是否存在 IndicesExistsResponse indexResponse = ia.client.admin().indices().prepareExists("blog") .execute().actionGet(); System.out.println(indexResponse.isExists()); 也可以同时判断多个索引是否存在: IndicesExistsResponse indexResponse = ia.client.admin().ind

[Elasticsearch] 索引管理 (四) - 动态映射

动态映射(Dynamic Mapping) 当ES在文档中碰到一个以前没见过的字段时,它会利用动态映射来决定该字段的类型,并自动地对该字段添加映射. 有时这正是需要的行为,但有时不是.你或许不知道在以后你的文档中会添加哪些字段,但是你想要它们能够被自动地索引.或许你只是想要忽略它们.或者 - 尤其当你将ES当做主要的数据存储使用时 - 大概你会希望这些未知的字段会抛出异常来提醒你注意这一问题. 幸运的是,你可以通过dynamic设置来控制这一行为,它能够接受以下的选项: true:默认值.动态添

[Elasticsearch] 索引管理 (三) - 根对象(Root Object)

根对象(Root Object) 映射的最顶层被称为根对象.它包含了: 属性区域(Properties Section),列举了文档中包含的每个字段的映射信息. 各种元数据(Metadata)字段,它们都以_开头,比如_type,_id,_source. 控制用于新字段的动态探测(Dynamic Detection)的设置,如analyzer,dynamic_date_formats和dynamic_templates. 其它的可以用在根对象和object类型中的字段上的设置,如enabled,

Elasticsearch 索引管理和内核探秘

1. 创建索引,修改索引,删除索引 //创建索引 PUT /my_index { "settings": { "number_of_shards": 1, "number_of_replicas": 0 }, "mappings": { "my_type": { "properties": { "my_field": { "type": "

一文带您了解 Elasticsearch 中,如何进行索引管理(图文教程)

欢迎关注笔者的公众号: 小哈学Java, 每日推送 Java 领域干货文章,关注即免费无套路附送 100G 海量学习.面试资源哟!! 个人网站: https://www.exception.site/essay/about-elasticsearch-index-manage 在 Elasticsearch 中,索引是一个非常重要的概念,它是具有相同结构的文档集合.类比关系型数据库,比如 Mysql, 你可以把它对标看成和库同级别的概念. 今天小哈将带着大家了解, 在 Elasticsearch

elasticsearch系列二:索引详解(快速入门、索引管理、映射详解、索引别名)

一.快速入门 1. 查看集群的健康状况 http://localhost:9200/_cat http://localhost:9200/_cat/health?v 说明:v是用来要求在结果中返回表头 状态值说明 Green - everything is good (cluster is fully functional),即最佳状态Yellow - all data is available but some replicas are not yet allocated (cluster i

Elasticsearch系列---索引管理

概要 Elasticsearch让索引创建变得非常简单,只要索引一条新的数据,索引会自动创建出来,但随着数据量的增加,我们开始有了索引优化和搜索优化的需求之后,就会发现自动创建的索引在某些方面不能非常完美的适应我们的需求,我们开始考虑手动创建适合我们业务需求的索引. 索引的CRUD 为了更好地贴切我们的业务数据需求,我们开始更精细的管理我们的索引. 创建索引 创建索引的语法示例如下: PUT /music { "settings": { "number_of_shards&q