[Elasticsearch] 索引管理 (二)

自定義解析器(Custom Analyzers)

雖然ES自己已經提供了一些解析器,可是經過組合字符過濾器(Character Filter),分詞器(Tokenizer)以及詞條過濾器(Token Filter)來建立你本身的解析器纔會顯示出其威力。html

解析和解析器中,咱們提到過解析器(Analyzer)就是將3種功能打包獲得的,它會按照下面的順序執行:node

字符過濾器(Character Filter) 字符過濾器用來在分詞前將字符串進行"整理"。好比,若是文本是HTML格式,那麼它會含有相似<p>或者<div>這樣的HTML標籤,可是這些標籤咱們是不須要索引的。咱們可使用html_strip字符過濾器移除全部的HTML標籤,並將全部的像Á這樣的HTML實體(HTML Entity)轉換爲對應的Unicode字符:Á。mysql

分詞器(Tokenizers) 一個解析器必須有一個分詞器。分詞器將字符串分解成一個個單獨的詞條(Term or Token)。在standard解析器中使用的standard分詞器,經過單詞邊界對字符串進行劃分來獲得詞條,同時會移除大部分的標點符號。另外還有其餘的分詞器擁有着不一樣的行爲。正則表達式

好比keyword分詞器,它不會進行任何分詞,直接原樣輸出。whitespace分詞器則只經過對空白字符進行劃分來獲得詞條。而pattern分詞器則根據正則表達式來進行分詞。sql

詞條過濾器(Token Filter) 在分詞後,獲得的詞條流(Token Stream)會按照順序被傳入到指定的詞條過濾器中。數據庫

詞條過濾器可以修改,增長或者刪除詞條。咱們已經提到了lowercase詞條過濾器stop詞條過濾器,可是ES中還有許多其它可用的詞條過濾器。stemming詞條過濾器會對單詞進行詞幹提取來獲得其詞根形態(Root Form)。ascii_folding詞條過濾器則會移除變音符號(Diacritics),將相似於très的詞條轉換成tresngram詞條過濾器edge_ngram詞條過濾器會產生適用於部分匹配(Partial Matching)或者自動完成(Autocomplete)的詞條。json

深刻搜索中,咱們會經過例子來討論這些分詞器和過濾器的使用場景和使用方法。可是首先,咱們須要解釋如何來建立一個自定義的解析器。app

深刻搜索中,咱們會經過例子來討論這些分詞器和過濾器的使用場景和使用方法。可是首先,咱們須要解釋如何來建立一個自定義的解析器。elasticsearch

建立一個自定義的解析器

和上面咱們配置es_std解析器的方式相同,咱們能夠在analysis下對字符過濾器,分詞器和詞條過濾器進行配置:ide

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對新的解析器進行測試

http://node1:9200/my_index/_analyze?analyzer=my_analyzer&text="The Quick   brown fox"
{
    "tokens": [
        {
            "token": "quick",
            "start_offset": 5,
            "end_offset": 10,
            "type": "<ALPHANUM>",
            "position": 1
        },
        {
            "token": "brown",
            "start_offset": 13,
            "end_offset": 18,
            "type": "<ALPHANUM>",
            "position": 2
        },
        {
            "token": "fox",
            "start_offset": 19,
            "end_offset": 22,
            "type": "<ALPHANUM>",
            "position": 3
        }
    ]
}

咱們須要告訴ES這個解析器應該在什麼地方使用。咱們能夠將它應用在text字段的映射中:

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

類型和映射(Types and Mappings)

在ES中的類型(Type)表明的是一類類似的文檔。一個類型包含了一個名字(Name) - 好比user或者blogpost - 以及一個映射(Mapping)。映射就像數據庫的模式那樣,描述了文檔中的字段或者屬性,和每一個字段的數據類型 -textintegerdate等 - 這些字段是如何被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字段,只不過它們使用的解析器分別是english和spanish。

問題能夠經過下面的查詢反映:

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

咱們在兩個類型中搜索title字段。查詢字符串(Query String)須要被解析,可是應該使用哪一個解析器:是spanish仍是english?答案是會利用首先找到的title字段對應的解析器,所以對於部分文檔這樣作是正確的,對於另外一部分則否則。

咱們能夠經過將字段命名地不一樣 - 好比title_en和title_es - 或者經過顯式地將類型名包含在字段名中,而後利用multi_match對每一個字段獨立查詢來避免這個問題:

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 爲了不發生這些衝突,建議索引中,每一個類型的同名字段都使用相同的映射方式。

相關文章
相關標籤/搜索