1. 程式人生 > 其它 >elasticsearch-對映和分析

elasticsearch-對映和分析

elasticsearch-對映和分析

對映和分析

  1. 檢視索引對映分析
    /gb/_mapping
  2. 全文搜尋和精確欄位搜尋
/gb/_search?q=2014-09-15
/gb/_search?q=date:2014-09-15

精確欄位搜尋和全文搜尋是搜尋引擎和其它資料庫的本質區別

精確值VS全文

  1. es中的資料可以概括的分為兩類:精確值和全文
  2. 精確值:例如日期或者使用者id, 字串也可以表示精確值,例如使用者名稱或者郵箱地址,
    對於精確值來說,Foo和foo是不同的,2014和2014-09-15也是不同的
  3. 全文:是指文字資料,例如一個推文的內容或者一封郵件的內容
    全文通常是指非結構化的資料
  4. 精確值很容易查詢,一般用sql就可以直接查,但是查詢全文資料要微妙的多
    我們問的不只是這個文件匹配查詢嗎,而是該文件匹配程度有多大,換句話說,該文件與給出的查詢相關性如何
  5. 我們很少對全文型別的域做精確匹配,相反,我們希望在文字型別的域中做搜尋,不僅如此,
    我們還希望搜尋能夠理解我們的意圖
    • 搜尋 UK ,會返回包含 United Kindom 的文件。
    • 搜尋 jump ,會匹配 jumped , jumps , jumping ,甚至是 leap 。
    • 搜尋 johnny walker 會匹配 Johnnie Walker , johnnie depp 應該匹配 Johnny Depp 。
    • fox news hunting 應該返回福克斯新聞( Foxs News )中關於狩獵的故事,同時, fox hunting news 應該返回關於獵狐的故事。
      為了促進這類在全文域中的查詢,es首先分析文件,然後根據結果建立倒排索引,接下來討論倒排索引和分析過程

倒排索引

  1. es使用一種稱為 倒排索引 的結構,它適用於快速全文搜尋,一個倒排索引由文件中所有不重複詞的列表組成
    對於其中的每個詞,有一個包含它的文件列表
  2. 假設我們有兩個文件,每個文件的content域內容如下
    • The quick brown fox jumped over the lazy dog
    • Quick brown foxes leap over lazy dogs in summer
      為了建立倒排索引我們需要將每個文件的content域拆分成單獨的詞(我們稱之為詞條或tokens),
      建立一個包含所有不重複詞條的排序列表,然後列出每個詞條出現在哪個文件,
      Term Doc_1 Doc_2

Quick | | X
The | X |
brown | X | X
dog | X |
dogs | | X
fox | X |
foxes | | X
in | | X
jumped | X |
lazy | X | X
leap | | X
over | X | X
quick | X |
summer | | X
the | X |

現在,如果我們先搜尋 quick brown,我們只需要查詢包含每個詞條的文件
Term Doc_1 Doc_2

brown | X | X
quick | X |

Total | 2 | 1
兩個文件都匹配,但是第一個比第二個匹配度更高,如果我們僅適用詞條匹配數量相似性演算法
第一個文件比第二個文件更佳
但是也有一些問題,
Quick 和 quick 以獨立的詞條出現,然而使用者可能認為它們是相同的詞。
fox 和 foxes 非常相似, 就像 dog 和 dogs ;他們有相同的詞根。
jumped 和 leap, 儘管沒有相同的詞根,但他們的意思很相近。他們是同義詞。
3. 文件中多個欄位and查詢
/gb,us/_search

{
    "query": {
        "bool": {
            "must": [
                {"match_phrase": {"name": "mary"}},
                {"match_phrase": {"tweet": "however"}}
            ]
        }
    }
}
  • 這非常重要,你只能搜尋在索引中出現的詞條,所以索引文字和查詢字串必須標準化為相同的格式

分析和分析器

  1. 分析包含下面的過程
    首先,將一塊文字分成適合於倒排索引的獨立的詞條
    之後,將這些詞條統一化為標準格式提高它們的可搜尋性,或者recall
  2. 分析器執行上面的工作實際上是將三個功能包在了一起
  • 字元過濾器
    首先字串按順序通過每個字元過濾器,它們的任務是在分詞前整理字串,一個字元過濾器可以用來去掉HTML,
    或者將&轉化為and
  • 分詞器
    字串被分詞器分為單個的詞條,一個簡單的分詞器在遇到空格和標點的時候,可能會將文字拆分成詞條
  • token過濾器
    詞條按照順序通過每個token過濾器,這個過程可能會改變詞條(大寫轉小寫),
    刪除詞條(例如像a and the等無用詞),或者增加詞條(向jump和leap這種同義詞)
  • es提供了開箱即用的字元過濾器、分詞器、token過濾器,這些可以組合起來形成自定義的分析器,用於不同的目的,
  1. 內建分析器
    es還提供了可以直接使用的預包裝的分析器,接下來看看最重要的分析器,為了證明它們的差異
    我們看看每個分析器會從下面的字串得到哪些詞條
    Set the shape to semi-transparent by calling set_trans(5)
    3.1 標準分析器
    標準分析器是es預設使用的分析器,它是分析各種語言文字最常用的選擇,他根據unicode聯盟定義的單詞邊界劃分文字
    刪除絕大部分標點,最後將詞條小寫,它會產生
    set, the, shape, to, semi, transparent, by, calling, set_trans, 5
    3.2 簡單分析器
    簡單分析器是在任何不是字母的地方分割文字,它會產生
    set, the, shape, to, semi, transparent, by, calling, set, trans
    3.3 空格分析器
    空格分析器在空格的地方劃分文字,它會產生
    Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
    3.4 語言分析器
    特定語言分析器可用於很多語言,它們可以考慮指定語言的特點,英語分析器附帶了一組英語無用詞,它們會直接被刪除
    由於該分析器理解英語語法的規則,這個分詞器可以提取英語單詞的詞幹
    英語分詞器會產生下面的詞條
    set, shape, semi, transpar, call, set_tran, 5
    注意看 transparent、 calling 和 set_trans 已經變為詞根格式。

  2. 什麼時候使用分析器
    當我們索引一個文件時,它的全文域被分析成詞條用來建立倒排索引,但是,當我們在全文域進行搜尋的時候
    也需要將查詢字串通過相同的分析過程,以保證我們查詢的詞條格式和索引中的詞條格式一致

  • 當你查詢一個全文域時,會對查詢字串應用相同的分析器,以產生正確的搜尋詞條列表
  • 當你查詢一個精確值域時,不會分析查詢字串,而是搜尋你指定的精確值
    再看之前的問題,date是精確值域:單獨的詞條,2014-05-19,所以GET /_search?q=date:2014是查不到的
  1. 測試分析器
    有時候很難理解分詞的過程和儲存到索引中的詞條,為了理解發生了什麼
    我們可以使用analyze API來看文字是如何被拆分的,在訊息體裡,指定分析器和要分析的文字
    GET /_analyze
{
  "analyzer": "standard",
  "text": "Text to analyze"
}

結果中,每個元素代表一個單獨的詞條

{
    "tokens": [
        {
            "token": "text",
            "start_offset": 0,
            "end_offset": 4,
            "type": "<ALPHANUM>",
            "position": 0
        },
        {
            "token": "to",
            "start_offset": 5,
            "end_offset": 7,
            "type": "<ALPHANUM>",
            "position": 1
        },
        {
            "token": "analyze",
            "start_offset": 8,
            "end_offset": 15,
            "type": "<ALPHANUM>",
            "position": 2
        }
    ]
}

token是實際儲存到索引中的詞條,position指明詞條在原始文字中出現的位置,
start_offset和end_offset指明詞條在原始字串中的位置
每個分析器的type值都不一樣,可以忽略它們,它們在Elasticsearch中的唯一作用在於keep_types token 過濾器

  • analyze API是一個有用的工具,它有助於我們理解es索引內部發生了什麼,後面會進一步討論
  1. 指定分析器
    當es在你的文件中檢測到一個新的字串域,它會自動設定其為一個全文字串域,使用標準分析器對它進行分析
    當我們不希望總是這樣,希望使用一個不同的分析器,適用於我們的資料使用的語言,有時候你想一個字串域
    就是一個字串域,不使用分析,直接索引你傳入的精確值,例如使用者Id或者一個內部的狀態域或標籤
    要做到這一點我們就必須手動指定這些域的對映

對映

  1. 為了將時間域視為時間,數字域視為數字,字串域視為全文或精確值字串,es需要知道每個域中的資料型別
    這個資訊包含在對映中
    索引中每個文件都有型別,每種型別都有它自己的對映,
  2. 核心簡單域型別
  • es支援如下簡單域型別
    • 字串 text
    • 整數 byte、short、integer、long
    • 浮點數 float double
    • 布林型 boolean
    • 日期 date
  1. 當你索引一個包含新域的文件--之前未曾出現,es會使用動態對映,
    通過json中節本資料型別,嘗試猜測域型別
    這意味著如果你想通過"13"索引一個數字,它會被對映為text型別,而不是long,
    但是如果這個域已經被對映為long,那麼es會嘗試將該字串轉換為long型別,如果無法轉換,則丟擲一個異常

  2. 檢視對映
    通過 /索引/_mapping,我們可以檢視es中一個或多個索引的對映
    es根據我們索引的文件,為域(稱為屬性)動態生成的對映

  • 錯誤的對映,例如將age域對映為string型別,而不是integer或long,會導致查詢出現令人困惑的結果,
  1. 自定義域對映
    儘管很多情況下基本域資料型別已經夠用,但你經常需要為單獨域自定義對映,特別是字串域
    自定義對映允許你執行下面的操作
  • 全文字串域與精確值字串域的區別
  • 使用特定語言分析器
  • 優化域以適應部分匹配
  • 指定自定義資料格式
  • 。。。
    域最重要的屬性是type,對於不是text的域,你一般只需要設定type
    預設text型別域會被認為包含全文,也就是說它們的值在索引前,會通過一個分析器,
    針對於這個域的查詢在搜尋前也會經過這個分析器
    增加一個tag2域,index選擇為false
{
    "properties": {
        "tag2": {
            "type": "text",
            "index": false
        }
    }
}

也就是說tag2的域是不能被索引的查詢的

  • text域對映的兩個最重要屬性index、analyzer
  • index屬性控制怎樣索引字串: true、false
    true:索引這個域,false:不索引這個域
    text域的index屬性預設值是true,如果我們想不索引這個域,我們可以設定它為false
  • analyzer
    對於analyzer屬性指定在搜尋時和索引時使用的分析器,es預設使用standard分析器,
    但是也可以指定一個內建的分析器替代它:simple、whitespace、english
  1. 更新對映
  • 建立索引,指定域的型別和tweet域使用的分析器
    PUT /gb
{
    "mappings": {
        "properties": {
            "tweet": {
                "type": "text",
                "analyzer": "english"
            },
            "date": {
                "type": "date"
            },
            "name": {
                "type": "text"
            },
            "user_id": {
                "type": "long"
            }
        }
    }
}

通過訊息體中指定的mappings建立了索引,
稍後我們在gb索引中增加一個新名為tag的type型別為keyword不分詞文字域,使用_mapping
PUT /gb/_mapping

{
    "properties": {
        "tag": {
            "type": "keyword"
        }
    }
}

注意:我們不需要再次列出所有已經存在的域,因為無論如何我們都無法改變它們,
新域已經被合併到了存在的對映中

  1. 測試對映
    GET /gb/_analyze
{
    "field": "name",
    "text": "a b c "
}
{
    "field": "tag",
    "text": "a b c "
}

訊息體裡傳輸我們想要分析的文字
name域產生三個詞條a、b、c,tag域產生一個詞條“a b c ”
換句話說,我們的對映正常工作

  1. 分析器使用whitespace的對映情況
    GET /test01/_analyze
{
    "field": "tag3",
    "text": "abc-def aa ddef"
}

會分析對映成三個token, abc-def aa ddef

複雜核心域型別

  1. 除了我們提提到的簡單標量資料型別,json還有null值、陣列和物件,這些es都支援
  2. 多值域
  • 很有可能,我們希望tag域包含多個標籤,我們可以以陣列的形式索引標籤
    { "tag": [ "search", "nosql" ]}
    對於陣列沒有特殊的對映需求,任何域都可以包含0、1或者多個值,就像全文域分析得到多個詞條
    這暗示,陣列中的所有值都必須是相同的資料型別,不能將日期和字串混在一起,
    如果通過索引陣列來建立新的域,es會用陣列中第一個值的資料型別作為這個域的型別
    注意:當你從es得到一個文件,每個陣列的順序和你當初索引文件時一樣,你得到的_source域
    和當初索引文件時一模一樣的json
  • 但是陣列是以多值域索引的-可以搜尋,但是無序的,在搜尋的時候,不能指定第一個或者最後一個
    更確切的說,把陣列想象成裝在袋子裡的值
  1. 空域
  • 當然陣列可以為空,這相當於存在零值,事實上,在lucene中不能儲存null值,
    所以我們認為存null值的域為空域,下面3種域被認為是空域,將不會被索引
{
  "null_value": null,
  "empty_array": [],
  "array_with_null_value": [null]
}
  1. 多層級物件
    我們討論的最後一個json原生資料型別是物件,在其它語言中稱為雜湊 雜湊map 字典 關聯陣列
    內部物件經常用來嵌入一個實體或物件到其它物件中,例子
{
    "tweet":            "Elasticsearch is very flexible",
    "user": {
        "id":           "@johnsmith",
        "gender":       "male",
        "age":          26,
        "name": {
            "full":     "John Smith",
            "first":    "John",
            "last":     "Smith"
        }
    }
}
  1. 內部物件的對映
    es會動態檢測新的物件並對映它們為物件,在properties屬性下列出內部域
    最外層的是根物件,裡面的是內部物件
  • user和name域的對映結構與tweet型別的相同,tweet稱為根物件,除了它有一些文件元資料的頂級域外
    例如_source域,和其它內部物件一樣
  1. 內部物件是如何索引的
    lucene不理解內部物件,lucene文件是由一組鍵值對列表組成的,為了能讓es有效的索引內部類
    它把我們的文件轉化成這樣
{
    "tweet":            [elasticsearch, flexible, very],
    "user.id":          [@johnsmith],
    "user.gender":      [male],
    "user.age":         [26],
    "user.name.full":   [john, smith],
    "user.name.first":  [john],
    "user.name.last":   [smith]
}
  1. 多級巢狀查詢(內部物件資料)
    要檢視多級巢狀查詢的工作方式,首先需要一個具有巢狀欄位的索引,下面的請求用巢狀的make和model
    欄位定義驅動程式索引的對映
  • 新增一個drivers索引
    PUT /drivers
  • 為drivers索引新增域的對映型別
    PUT /drivers/_mapping
{
    "properties": {
        "driver": {
            "type": "nested",
            "properties": {
                "last_name": {
                    "type": "text"
                },
                "vehicle": {
                    "type": "nested",
                    "properties": {
                        "make": {
                            "type": "text"
                        },
                        "model": {
                            "type": "nested"
                        }
                    }
                }
            }
        }
    }
}
  • 接下來索引一些文件
    PUT /drivers/_doc/
{
  "driver" : {
        "last_name" : "Hudson",
        "vehicle" : [
            {
                "make" : "Mifune",
                "model" : "Mach Five"
            },
            {
                "make" : "Miller-Meteor",
                "model" : "Ecto-1"
            }
        ]
    }
}
{
  "driver" : {
        "last_name" : "McQueen",
        "vehicle" : [
            {
                "make" : "Powell Motors",
                "model" : "Canyonero"
            },
            {
                "make" : "Miller-Meteor",
                "model" : "Ecto-1"
            }
        ]
    }
}

現在可以使用多級巢狀查詢,根據make和model欄位匹配文件
GET /drivers/_search

{
    "query": {
        "nested": {
            "path": "driver",
            "query": {
                "nested": {
                    "path": "driver.vehicle",
                    "query": {
                        "match": {
                            "driver.vehicle.make": "mifune"
                        }
                    }
                }
            }
        }
    }
}

正常返回響應,非常強大啊!!!
參考文件

  1. es多個值作為關鍵字搜尋(相當於關係型資料庫中的in查詢)
    GET /test/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "terms": {
                        "tags": [11, 66]
                    }
                }
            ]
        }
    }
}
  1. 內部物件資料是如何被索引的
    最後,考慮包含內部物件的陣列是如何被索引的。 假設我們有個 followers 陣列:
{
    "followers": [
        { "age": 35, "name": "Mary White"},
        { "age": 26, "name": "Alex Jones"},
        { "age": 19, "name": "Lisa Smith"}
    ]
}

這個文件會像我們之前描述的那樣被扁平化處理,結果如下所示:

{
    "followers.age":    [19, 26, 35],
    "followers.name":   [alex, jones, lisa, smith, mary, white]
}

{age: 35} 和 {name: Mary White} 之間的相關性已經丟失了,因為每個多值域只是一包無序的值,而不是有序陣列。這足以讓我們問,“有一個26歲的追隨者?”

但是我們不能得到一個準確的答案:“是否有一個26歲 名字叫 Alex Jones 的追隨者?”

相關內部物件被稱為 nested 物件,可以回答上面的查詢,我們稍後會在巢狀物件中介紹它。

參考文件