Elasticsearch簡介&基本使用

簡介

Elasticsearch是一個分佈式、可擴展、實時的搜索與數據分析引擎

Elasticsearch被用作全文檢索、結構化檢索、分析以及這三個功能的組合

Elasticsearch 是使用 Java 編寫的,它的內部使用 Lucene 做索引與搜索,但是它的目的是使全文檢索變得簡單, 通過隱藏 Lucene 的複雜性,取而代之的提供一套簡單一致的 RESTful API

Elasticsearch描述

  • 一個分佈式的實時文檔存儲,每個字段都可以被索引與搜索
  • 一個分佈式實時分析搜索引擎
  • 能勝任上百個服務節點的擴展,並支持PB級別的結構化或者非結構化數據

Elasticsearch 是面向文檔的,意味着它存儲整個對象或 文檔。Elasticsearch 不僅存儲文檔,而且 索引 每個文檔的內容使之可以被檢索。在 Elasticsearch 中,對文檔進行索引、檢索、排序和過濾,而不是對行列數據。這是一種完全不同的思考數據的方式,也是 Elasticsearch 能支持複雜全文檢索的原因

存儲什麼樣的數據——文檔

在 Elasticsearch 中,術語 文檔 有着特定的含義。它是指最頂層或者根對象, 這個根對象被序列化成 JSON 並存儲到 Elasticsearch 中,指定了唯一 ID

一個文檔不僅僅包含它的數據 ,也包含 元數據 —— 有關 文檔的信息。 三個必須的元數據元素如下:

  • _index

    文檔在哪存放

    一個 索引 應該是因共同的特性被分組到一起的文檔集合,如產品 products

  • _type

    文檔表示的對象類別

    數據可能在索引中只是鬆散的組合在一起,但是通常明確定義一些數據中的子分區是很有用的,如不同的產品類別, “electronics” 、 “kitchen”

  • _id

    文檔唯一標識

    ID 是一個字符串, 當它和 _index 以及 _type 組合就可以唯一確定 Elasticsearch 中的一個文檔,當創建一個新的文檔,可以提供自己的 _id ,也可以讓 Elasticsearch 自動生成

    自己設置id

    PUT /website/blog/123
    {
      "title": "My first blog entry",
      "text":  "Just trying this out...",
      "date":  "2014/01/01"
    }
    

    自動生成id

    POST /website/blog/
    {
      "title": "My second blog entry",
      "text":  "Still trying this out...",
      "date":  "2014/01/01"
    }
    

檢查文檔是否存在

如果只想檢查一個文檔是否存在 ,不想關心內容,那麼用 HEAD 方法來代替 GET 方法。 HEAD 請求沒有返回體,只返回一個 HTTP 請求報頭

curl -i -XHEAD http://localhost:9200/website/blog/123

返回

HTTP/1.1 200 OK (存在) / HTTP/1.1 404 Not Found (不存在)

分佈式文檔存儲中如何找到數據所在的分片——路由一個文檔到一個分片中

當創建文檔時,這個文檔會根據一定的規則被存儲在某個分片中,在取文檔時根據同樣的規則找到對應的分片,這個過程是根據下面這個公式決定的

shard = hash(routing) % number_of_primary_shards

routing 是一個可變值,默認是文檔的 _id ,也可以設置成一個自定義的值

routing 通過 hash 函數生成一個數字,然後這個數字再除以 number_of_primary_shards (主分片的數量)後得到 餘數

這個分佈在 0 到 number_of_primary_shards-1 之間的餘數,就是所尋求的文檔所在分片的位置

數據爲什麼可以被搜索——倒排索引

Elasticsearch 使用一種稱爲 倒排索引 的結構,它適用於快速的全文搜索。一個倒排索引由文檔中所有不重複詞的列表構成,對於其中每個詞,有一個包含它的文檔列表

例如,假設有兩個文檔,每個文檔的 content 域包含如下內容:

1.The quick brown fox jumped over the lazy dog

2.Quick brown foxes leap over lazy dogs in summer

創建一個包含所有不重複詞條的排序列表,然後列出每個詞條出現在哪個文檔。結果如下所示

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

如上,兩個文檔都會被匹配到

如何提高倒排索引詞條的可搜索性——分析與分析器

分析包含下面的過程:

  • 首先,將一塊文本分成適合於倒排索引的獨立的詞條
  • 之後,將這些詞條統一化爲標準格式以提高它們的”可搜索性”,或者recall分析器執行上面的工作。分析器實際上是將三個功能封裝到了一個包裏

    • 字符過濾器

      首先,字符串按順序通過每個字符過濾器,他們的任務是在分詞前整理字符串,一個字符過濾器可以用來去掉HTML,或者將&轉化成’and’

    • 分詞器

      其次,字符串被分詞器分爲單個的詞條,一個簡單的分詞器遇到空格和標點的時候,可能會將文本拆分成詞條

    • Token過濾器

      最後,詞條按順序通過每個token過濾器,這個過程可能會改變詞條(例如,小寫化Quick),刪除詞條(像 aandthe 等無用詞),或者增加詞條(例如,像jump和leap這種同義詞)

搜索到的多條結果如何排序——相關性

在 Elasticsearch 中, 相關性得分 由一個浮點數進行表示,並在搜索結果中通過 _score 參數返回, 默認排序是 _score 降序,_score 的評分越高,相關性越高

查詢語句會爲每個文檔生成一個 _score 字段。評分的計算方式取決於查詢類型 不同的查詢語句用於不同的目的: fuzzy 查詢會計算與關鍵詞的拼寫相似程度,terms 查詢會計算 找到的內容與關鍵詞組成部分匹配的百分比,但是通常說的 relevance 是用來計算全文本字段的值相對於全文本檢索詞相似程度的算法

Elasticsearch 的相似度算法 被定義爲檢索詞頻率/反向文檔頻率, TF/IDF ,包括以下內容

  • 檢索詞頻率

    檢索詞在該字段出現的頻率,出現頻率越高,相關性也越高,字段中出現過5次要比只出現過1次的相關性高

  • 反向文檔頻率

    每個檢索詞在索引中出現的頻率,頻率越高,相關性越低,檢索詞出現在多數文檔中會比出現在少數文檔中的權重更低

  • 字段長度準則

    字段的長度是多少,長度越長,相關性越低,檢索詞出現在一個短的title要比同樣的詞出現在一個長的content字段權重更大

理解評分標準

當調試一條複雜的查詢語句時, 想要理解 _score 究竟是如何計算是比較困難的。Elasticsearch 在 每個查詢語句中都有一個 explain 參數,將 explain 設爲 true 就可以得到更詳細的信息

GET /_search?explain 
{
   "query"   : { "match" : { "tweet" : "honeymoon" }}
}

返回值提供了_explanation,每個入口都包含一個description、value、details字段,它分別表示計算的類型、計算結果和任何需要的計算細節

"_explanation": { 
   "description": "weight(tweet:honeymoon in 0)
                  [PerFieldSimilarity], result of:",
   "value":       0.076713204,
   "details": [
      {
         "description": "fieldWeight in 0, product of:",
         "value":       0.076713204,
         "details": [
            {  
                // 檢索詞頻率
                // 檢索詞 `honeymoon` 在這個文檔的 `tweet` 字段中的出現次數
               "description": "tf(freq=1.0), with freq of:",
               "value":       1,
               "details": [
                  {
                     "description": "termFreq=1.0",
                     "value":       1
                  }
               ]
            },
            { 
                // 反向文檔頻率
                // 檢索詞 `honeymoon` 在索引上所有文檔的 `tweet` 字段中出現的次數
               "description": "idf(docFreq=1, maxDocs=1)",
               "value":       0.30685282
            },
            { 
                // 字段長度準則
                // 在這個文檔中, `tweet` 字段內容的長度 -- 內容越長,值越小
               "description": "fieldNorm(doc=0)",
               "value":        0.25,
            }
         ]
      }
   ]
}

JSON 形式的 explain 描述是難以閱讀的, 但是轉成 YAML 會好很多,只需要在參數中加上 format=yaml

理解文檔是如何被匹配到的

當 explain 選項加到某一文檔上時, explain api 會幫助你理解爲何這個文檔會被匹配,更重要的是,一個文檔爲何沒有被匹配

GET /us/tweet/12/_explain
{
   "query" : {
      "bool" : {
         "filter" : { "term" :  { "user_id" : 2           }},
         "must" :  { "match" : { "tweet" :   "honeymoon" }}
      }
   }
}

在description元素中會說明原因

"failure to match filter: cache(user_id:[2 TO 2])"

RESTful API 使用

所有其他語言可以使用 RESTful API 通過端口 9200 和 Elasticsearch 進行通信

curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'

示例:

curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
    "query": {
        "match_all": {}
    }
}
'

後面將用縮寫格式來展示這些 curl 示例,如

GET /_count
{
    "query": {
        "match_all": {}
    }
}

存儲文檔

PUT /megacorp/employee/1
{
    "first_name" : "John",
    "last_name" :  "Smith",
    "age" :        25,
    "about" :      "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}

megacorp
    索引名稱
employee
    類型名稱
1
    特定僱員的ID

創建新文檔(已存在則提示)

PUT /website/blog/123/_create
{ ... }

請求成功執行(文檔不存在),返回元數據和一個 201 Created 的 HTTP 響應碼

具有相同的 _index 、 _type 和 _id 的文檔已經存在,返回409 Conflict 響應碼,以及如下的錯誤信息

{
   "error": {
      "root_cause": [
         {
            "type": "document_already_exists_exception",
            "reason": "[blog][123]: document already exists",
            "shard": "0",
            "index": "website"
         }
      ],
      "type": "document_already_exists_exception",
      "reason": "[blog][123]: document already exists",
      "shard": "0",
      "index": "website"
   },
   "status": 409
}

刪除文檔

DELETE /website/blog/123

如果找到該文檔,返回200 ok 的 HTTP 響應碼和

{
  "found" :    true,
  "_index" :   "website",
  "_type" :    "blog",
  "_id" :      "123",
  "_version" : 3 // _version的值會增加
}

如果文檔沒有找到,返回 404 Not Found 的響應碼和類似這樣的響應體

{
  "found" :    false,
  "_index" :   "website",
  "_type" :    "blog",
  "_id" :      "123",
  "_version" : 4
  // 即使文檔不存在( Found 是 false ), _version 值仍然會增加,
  用來確保這些改變在跨多節點時以正確的順序執行
}

刪除文檔不會立即將文檔從磁盤中刪除,只是將文檔標記爲已刪除狀態。隨着不斷的索引更多的數據,Elasticsearch 將會在後臺清理標記爲已刪除的文檔

更新文檔

在 Elasticsearch 中文檔是 不可改變 的,不能修改它們。 相反,如果想要更新現有的文檔,需要 重建索引 或者進行替換

PUT /website/blog/123
{
  "title": "My first blog entry",
  "text":  "I am starting to get the hang of this...",
  "date":  "2014/01/02"
}

返回

{
  "_index" :   "website",
  "_type" :    "blog",
  "_id" :      "123",
  "_version" : 2,
  // created 標誌設置成 false ,是因爲相同的索引、類型和 ID 的文檔已經存在
  "created":   false 
}

通過update API可以對文檔的某個位置進行部分更新,與之前描述的 檢索-修改-重建索引 的處理過程相同。 區別在於這個過程發生在分片內部,這樣就避免了多次請求的網絡開銷。通過減少檢索和重建索引步驟之間的時間,減少了其他進程的變更帶來衝突的可能性

新增字段tags和views

POST /website/blog/1/_update
{
   "doc" : {
      "tags" : [ "testing" ],
      "views": 0
   }
}

使用腳本部分更新文檔

腳本可以在 update API中用來改變 _source 的字段內容, 它在更新腳本中稱爲 ctx._source 。 例如,可以使用腳本來增加博客文章中 views 的數量

POST /website/blog/1/_update
{
   "script" : "ctx._source.views+=1"
}

使用腳本給 tags 數組添加一個新的標籤

POST /website/blog/1/_update
{
   "script" : "ctx._source.tags+=new_tag",
   "params" : {
      "new_tag" : "search"
   }
}

通過設置 ctx.op 爲 delete 來刪除基於其內容的文檔

POST /website/blog/1/_update
{
   "script" : "ctx.op = ctx._source.views == count ? 'delete' : 'none'",
    "params" : {
        "count": 1
    }
}

如果嘗試更新一個不存在的文檔,那麼更新操作將會失敗,可以使用 upsert 參數,指定如果文檔不存在就應該先創建它

POST /website/pageviews/1/_update
{
   "script" : "ctx._source.views+=1",
   "upsert": {
       "views": 1
   }
}

對於部分更新的很多使用場景,文檔已經被改變也沒有關係,它們發生的先後順序其實不太重要, 如果衝突發生了,唯一需要做的就是嘗試再次更新,可以通過 設置參數 retry_on_conflict 來自動完成, 這個參數規定了失敗之前 update 應該重試的次數,它的默認值爲 0

POST /website/pageviews/1/_update?retry_on_conflict=5 
{
   "script" : "ctx._source.views+=1",
   "upsert": {
       "views": 0
   }
}

檢索文檔

GET /megacorp/employee/1

返回

{
  "_index" :   "megacorp",
  "_type" :    "employee",
  "_id" :      "1",
  "_version" : 1,
  "found" :    true,
  "_source" :  {
      "first_name" :  "John",
      "last_name" :   "Smith",
      "age" :         25,
      "about" :       "I love to go rock climbing",
      "interests":  [ "sports", "music" ]
  }
}

取回多個文檔

如果需要從 Elasticsearch 檢索很多文檔,那麼使用 multi-get 或者 mget API 來將這些檢索請求放在一個請求中,將比逐個文檔請求更快地檢索到全部文檔

mget API 要求有一個 docs 數組作爲參數,每個 元素包含需要檢索文檔的元數據, 包括 _index 、 _type 和 _id 。如果想檢索一個或者多個特定的字段,可以通過 _source 參數來指定這些字段的名字

GET /_mget
{
   "docs" : [
      {
         "_index" : "website",
         "_type" :  "blog",
         "_id" :    2
      },
      {
         "_index" : "website",
         "_type" :  "pageviews",
         "_id" :    1,
         "_source": "views"
      }
   ]
}

返回

{
   "docs" : [
      {
         "_index" :   "website",
         "_id" :      "2",
         "_type" :    "blog",
         "found" :    true,
         "_source" : {
            "text" :  "This is a piece of cake...",
            "title" : "My first external blog entry"
         },
         "_version" : 10
      },
      {
         "_index" :   "website",
         "_id" :      "1",
         "_type" :    "pageviews",
         "found" :    true,
         "_version" : 2,
         "_source" : {
            "views" : 2
         }
      }
   ]
}

如果所有文檔的 _index 和 _type 都是相同的,可以只傳一個 ids 數組,而不是整個 docs 數組

GET /website/blog/_mget
{
   "ids" : [ "2", "1" ]
}

如果請求的文檔不存在,將在響應體中被報告

{
  "docs" : [
    {
      "_index" :   "website",
      "_type" :    "blog",
      "_id" :      "2",
      "_version" : 10,
      "found" :    true,
      "_source" : {
        "title":   "My first external blog entry",
        "text":    "This is a piece of cake..."
      }
    },
    {
      "_index" :   "website",
      "_type" :    "blog",
      "_id" :      "1",
      "found" :    false  
    }
  ]
}

每個文檔都是單獨檢索和報告的,某一個文檔未能找到並不影響其他文檔的檢索

即使有某個文檔沒有找到,上述請求的 HTTP 狀態碼仍然是 200 。事實上,即使請求 沒有 找到任何文檔,它的狀態碼依然是 200 –因爲 mget 請求本身已經成功執行。 爲了確定某個文檔查找是成功或者失敗,需要檢查 found 標記

搜索

空搜索

沒有指定任何查詢的空搜索 ,它簡單地返回集羣中所有索引下的所有文檔

GET /_search

返回

{
    // 一個 hits 數組包含所查詢結果的前十個文檔
   "hits" : {
       // 表示匹配到的文檔總數
      "total" :       14,
      "hits" : [
        {
          "_index":   "us",
          "_type":    "tweet",
          "_id":      "7",
          "_score":   1,
          "_source": {
             "date":    "2014-09-17",
             "name":    "John Smith",
             "tweet":   "The Query DSL is really powerful and flexible",
             "user_id": 2
          }
       },
        ... 9 RESULTS REMOVED ...
      ],
      // max_score 值是與查詢所匹配文檔的 _score 的最大值
      "max_score" :   1
   },
   // 執行整個搜索請求耗費了多少毫秒
   "took" :           4,
   // 查詢中參與分片的總數,以及這些分片成功了多少個失敗了多少個
   "_shards" : {
      "failed" :      0,
      "successful" :  10,
      "total" :       10
   },
   // 查詢是否超時,指定 timeout,如 GET /_search?timeout=10ms
   "timed_out" :      false
}

分頁

  • size
    顯示應該返回的結果數量,默認是 10
  • from
    顯示應該跳過的初始結果數量,默認是 0

    GET /_search?size=5&from=5
    
    或 
    
    POST /_search
    {
      "from": 30,
      "size": 10
    }
    

使用查詢表達式搜索

一條複合語句可以合併 任何 其它查詢語句,包括複合語句,這就意味着,複合語句之間可以互相嵌套,可以表達非常複雜的邏輯

GET /megacorp/employee/_search
{
    "query" : {
        // 將多查詢組合在一起
        "bool": {
            // 文檔 必須 匹配這些條件才能被包含進來,對應的 must_not 表示文檔 必須不 匹配這些條件才能被包含進來
            "must": {
                // 查詢last_name字段中包含smith
                "match" : {
                    "last_name" : "smith" 
                },
                // 在多個字段上執行相同的 match 查詢
                "multi_match": {
                    "query":    "full text search",
                    "fields":   [ "title", "body" ]
                }
            },
            // 必須 匹配,但它以不評分、過濾模式來進行。這些語句對評分沒有貢獻,只是根據過濾標準來排除或包含文檔
            "filter": {
                "bool": { 
                     "must": [
                          // gt(大於) gte(大於等於) lt(小於) lte(小於等於)
                          { "range": { "date": { "gte": "2014-01-01" }}},
                          { "range": { "price": { "lte": 29.99 }}}
                      ]
                }
            },
            // 如果滿足這些語句中的任意語句,將增加 _score ,否則,無任何影響。它們主要用於修正每個文檔的相關性得分
            "should": [
                { "match": { "tag": "starred" }}
            ]
        }
    }
}

term 查詢

term 查詢被用於精確值 匹配,這些精確值可能是數字、時間、布爾或者那些 not_analyzed 的字符串

{ "term": { "age":    26           }}
{ "term": { "date":   "2014-09-01" }}
{ "term": { "public": true         }}
{ "term": { "tag":    "full_text"  }}

terms 查詢和 term 查詢一樣,但它允許你指定多值進行匹配。如果這個字段包含了指定值中的任何一個值,那麼這個文檔滿足條件

{ "terms": { "tag": [ "search", "full_text", "nosql" ] }}

exists 查詢和 missing 查詢

被用於查找那些指定字段中有值 (exists) 或無值 (missing) 的文檔。這與SQL中的 IS_NULL (missing) 和 NOT IS_NULL (exists) 在本質上具有共性

{
    "exists":   {
        "field":    "title"
    }
}

constant_score 查詢

將一個不變的常量評分應用於所有匹配的文檔,可以使用它來取代只有 filter 語句的 bool 查詢。在性能上是完全相同的,但對於提高查詢簡潔性和清晰度有很大幫助

{
    "constant_score":   {
        "filter": {
            "term": { "category": "ebooks" } 
        }
    }
}

驗證查詢

validate-query API 可以用來驗證查詢是否合法

GET /gb/tweet/_validate/query?explain 
{
   "query": {
      "tweet" : {
         "match" : "really powerful"
      }
   }
}

返回

{
  "valid" :     false,
  "_shards" :   { ... },
  "explanations" : [ {
    "index" :   "gb",
    // 不合法
    "valid" :   false,
    // 原因
    "error" :   "org.elasticsearch.index.query.QueryParsingException:
                 [gb] No query registered for [tweet]"
  } ]
}

高亮搜索

GET /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : // 僅匹配同時包含 “rock” 和 “climbing” ,並且 二者以短語 “rock climbing” 的形式 
        {
            "about" : "rock climbing"
        }
    },
    "highlight": {
        "fields" : {
            "about" : {}
        }
    }
}

返回

{
   ...
   "hits": {
      "total":      1,
      "max_score":  0.23013961,
      "hits": [
         {
            ...
            "_score":         0.23013961,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            },
            "highlight": {
               "about": [
                  "I love to go <em>rock</em> <em>climbing</em>" 
               ]
            }
         }
      ]
   }
}

排序

多級排序

匹配的結果首先按照日期排序,然後按照相關性排序

GET /_search
{
    "query" : {
        "bool" : {
            "must":   { "match": { "tweet": "manage text search" }},
            "filter" : { "term" : { "user_id" : 2 }}
        }
    },
    "sort": [
        { "date":   { "order": "desc" }},
        { "_score": { "order": "desc" }}
    ]
}

排序條件的順序是很重要的。結果首先按第一個條件排序,僅當結果集的第一個 sort 值完全相同時纔會按照第二個條件進行排序,以此類推

字段多值的排序

一種情形是字段有多個值的排序, 需要記住這些值並沒有固有的順序;一個多值的字段僅僅是多個值的包裝

對於數字或日期,可以將多值字段減爲單值,這可以通過使用 min 、 max 、 avg 或是 sum 排序模式 。 例如可以按照每個 date 字段中的最早日期進行排序

"sort": {
    "dates": {
        "order": "asc",
        "mode":  "min"
    }
}

衝突處理

使用 index API 更新文檔 ,可以一次性讀取原始文檔,做出修改,然後重新索引 整個文檔 。 最近的索引請求將獲勝:無論最後哪一個文檔被索引,都將被唯一存儲在 Elasticsearch 中。如果其他人同時更改這個文檔,他們的更改將丟失

如下例,web_1 對 stock_count 所做的更改已經丟失,因爲 web_2 不知道它的 stock_count 的拷貝已經過期,這有可能會造成賣出的商品超過實際的庫存

變更越頻繁,讀數據和更新數據的間隙越長,也就越可能丟失變更

這裏寫圖片描述

在數據庫領域中,有兩種方法通常被用來確保併發更新時變更不會丟失

  • 悲觀併發控制

    這種方法被關係型數據庫廣泛使用,它假定有變更衝突可能發生,因此阻塞訪問資源以防止衝突。 一個典型的例子是讀取一行數據之前先將其鎖住,確保只有放置鎖的線程能夠對這行數據進行修改

  • 樂觀併發控制

    Elasticsearch 中使用的這種方法假定衝突是不可能發生的,並且不會阻塞正在嘗試的操作。 然而,如果源數據在讀寫當中被修改,更新將會失敗。應用程序接下來將決定該如何解決衝突。 例如,可以重試更新、使用新的數據、或者將相關情況報告給用戶

Elasticsearch 是分佈式的。當文檔創建、更新或刪除時, 新版本的文檔必須複製到集羣中的其他節點。Elasticsearch 也是異步和併發的,這意味着這些複製請求被並行發送,並且到達目的地時也許順序是亂的。Elasticsearch 需要一種方法確保文檔的舊版本不會覆蓋新的版本

當嘗試通過重建文檔的索引來保存修改,指定 version 爲修改會被應用的版本

PUT /website/blog/1?version=1 
{
  "title": "My first blog entry",
  "text":  "Starting to get the hang of this..."
}

// 只有文檔現在的 _version 爲 1 時,本次更新才能成功,否則返回 409 Conflict HTTP 響應碼,和一個如下所示的響應體

{
   "error": {
      "root_cause": [
         {
            "type": "version_conflict_engine_exception",
            "reason": "[blog][1]: version conflict, current [2], provided [1]",
            "index": "website",
            "shard": "3"
         }
      ],
      "type": "version_conflict_engine_exception",
      // 文檔的當前 _version 號是 2 ,但指定的更新版本號爲 1 
      "reason": "[blog][1]: version conflict, current [2], provided [1]",
      "index": "website",
      "shard": "3"
   },
   "status": 409
}

通過外部系統使用版本控制

外部版本號的處理方式和之前討論的內部版本號的處理方式有些不同, Elasticsearch 不是檢查當前 _version 和請求中指定的版本號是否相同, 而是檢查當前 _version 是否 小於 指定的版本號(版本號必須是大於零的整數, 且小於 9.2E+18 — 一個 Java 中 long 類型的正值)。 如果請求成功,外部的版本號作爲文檔的新 _version 進行存儲

在 創建 新文檔時指定外部版本號

PUT /website/blog/2?version=5&version_type=external
{
  "title": "My first external blog entry",
  "text":  "Starting to get the hang of this..."
}

更新這個文檔,指定一個新的 version 號是 10

PUT /website/blog/2?version=10&version_type=external
{
  "title": "My first external blog entry",
  "text":  "This is a piece of cake..."
}

類型和映射

類型 在 Elasticsearch 中表示一類相似的文檔。 類型由 名稱,比如 user 或 blogpost 和 映射 組成

映射, 就像數據庫中的 schema ,描述了文檔可能具有的字段或 屬性 、 每個字段的數據類型—比如 string, integer 或 date —以及Lucene是如何索引和存儲這些字段的

根對象

映射的最高一層被稱爲 根對象 ,它可能包含下面幾項:

  • 一個 properties 節點,列出了文檔中可能包含的每個字段的映射
  • 各種元數據字段,它們都以一個下劃線開頭,例如 _type 、 _id 和 _source
  • 設置項,控制如何動態處理新的字段,例如 analyzer 、 dynamic_date_formats 和 dynamic_templates
  • 其他設置,可以同時應用在根對象和其他 object 類型的字段上,例如 enabled 、 dynamic 和 include_in_all

屬性

  • type

    字段的數據類型,例如 string 或 date

  • index

    字段是否應當被當成全文來搜索( analyzed ),或被當成一個準確的值( not_analyzed ),還是完全不可被搜索( no )

  • analyzer

    確定在索引和搜索時全文字段使用的 analyzer

元數據:_source字段

Elasticsearch 在 _source 字段存儲代表文檔體的JSON字符串。和所有被存儲的字段一樣, _source 字段在被寫入磁盤之前先會被壓縮

這個字段的存儲,意味着下面的這些:

  • 搜索結果包括了整個可用的文檔——不需要額外的從另一個的數據倉庫來取文檔
  • 如果沒有 _source 字段,部分 update 請求不會生效
  • 當映射改變時,需要重新索引數據,有了_source字段可以直接從Elasticsearch這樣做,而不必從另一個(通常是速度更慢的)數據倉庫取回所有文檔
  • 當不需要看到整個文檔時,單個字段可以從 _source 字段提取和通過 get 或者 search 請求返回
  • 調試查詢語句更加簡單,因爲你可以直接看到每個文檔包括什麼,而不是從一列id猜測它們的內容

元數據:_all字段

_all 字段:一個把其它字段值 當作一個大字符串來索引的特殊字段,_all 字段在新應用的探索階段,當還不清楚文檔的最終結構時是比較有用的。可以使用這個字段來做任何查詢,並且有很大可能找到需要的文檔

GET /_search
{
    "match": {
        "_all": "john smith marketing"
    }
}

元數據:文檔標識

文檔標識與四個元數據字段相關:

  • _id

    文檔的ID字符串

  • _type

    文檔的類型名

  • _index

    文檔所在的索引

  • _uid

    _type和_id連接在一起構成type#id

默認情況下, _uid 字段是被存儲(可取回)和索引(可搜索)的。 _type 字段被索引但是沒有存儲, _id 和 _index 字段則既沒有被索引也沒有被存儲,這意味着它們並不是真實存在的

映射

爲了能夠將時間域視爲時間,數字域視爲數字,字符串域視爲全文或精確值字符串, Elasticsearch 需要知道每個域中數據的類型。這個信息包含在映射中

Elasticsearch 支持 如下簡單域類型:

  • 字符串: string
  • 整數 : byte, short, integer, long
  • 浮點數: float, double
  • 布爾型: boolean
  • 日期: date

    Elasticsearch 會使用 動態映射 ,通過JSON中基本數據類型,嘗試猜測域類型,使用如下規則

JSON type 域 type
布爾型:true或者false boolean
整數:123 long
浮點數:123.45 double
字符串:有效日期:yyyy-MM-dd date
字符串:foo bar string

如果通過引號( “123” )索引一個數字,它會被映射爲 string 類型,而不是 long 。但是,如果這個域已經映射爲 long ,那麼 Elasticsearch 會嘗試將這個字符串轉化爲 long ,如果無法轉化,則拋出一個異常

查看映射

通過 /_mapping ,可以查看 Elasticsearch 在一個或多個索引中的一個或多個類型的映射

GET /gb/_mapping/tweet

返回

{
   "gb": {
      "mappings": {
         "tweet": {
            "properties": {
               "date": {
                  "type": "date",
                  "format": "strict_date_optional_time||epoch_millis"
               },
               "name": {
                  "type": "string"
               },
               "tweet": {
                  "type": "string"
               },
               "user_id": {
                  "type": "long"
               }
            }
         }
      }
   }
}

自定義域映射

自定義映射允許執行下面的操作:

  • 全文字符串域和精確值字符串域的區別
  • 使用特定語言分析器
  • 優化域以適應部分匹配
  • 指定自定義數據格式

域最重要的屬性是type,對於不是string的域,一般只需要設置type

{
    "number_of_clicks": {
        "type": "integer"
    }
}

默認, string 類型域會被認爲包含全文

string 域映射的兩個最重要 屬性是 index 和 analyzer

index

index屬性控制怎樣索引字符串,它可以是下面三個值:

analyzed
    首先分析字符串,然後索引它,以全文索引這個域

not_analyzed
    索引這個域,所以它能夠被搜索,但索引的是精確值,不會對它進行分析

no 
    不索引這個域,這個域不會被搜索到

string 域 index 屬性默認是 analyzed

修改通過如下方式

{
    "tag": {
        "type":     "string",
        "index":    "not_analyzed"
    }
}

其他簡單類型(例如 long , double , date 等)也接受 index 參數,但有意義的值只有 no 和 not_analyzed , 因爲它們永遠不會被分析

analyzer

對於 analyzed 字符串域,用 analyzer 屬性指定在搜索和索引時使用的分析器。默認, Elasticsearch 使用 standard 分析器,可以指定一個內置的分析器替代它,例如 whitespace 、 simple 和 english

{
    "tweet": {
        "type":     "string",
        "analyzer": "english"
    }
}

更新映射

首次創建一個索引的時候,可以指定類型的映射。也可以使用 /_mapping 爲新類型(或者爲存在的類型更新映射)增加映射

儘管可以增加一個存在的映射,但不能修改存在的域映射。如果一個域的映射已經存在,那麼該域的數據可能已經被索引。如果意圖修改這個域的映射,索引的數據可能會出錯,不能被正常的搜索

可以更新一個映射來添加一個新域,但不能將一個存在的域從 analyzed 改爲 not_analyzed

創建一個新索引,指定 tweet 域使用 english 分析器:

PUT /gb 
{
  "mappings": {
    "tweet" : {
      "properties" : {
        "tweet" : {
          "type" :    "string",
          "analyzer": "english"
        },
        "date" : {
          "type" :   "date"
        },
        "name" : {
          "type" :   "string"
        },
        "user_id" : {
          "type" :   "long"
        }
      }
    }
  }
}

增加一個新的名爲 tag 的 not_analyzed 的文本域,使用 _mapping

PUT /gb/_mapping/tweet
{
  "properties" : {
    "tag" : {
      "type" :    "string",
      "index":    "not_analyzed"
    }
  }
}

動態映射

當 Elasticsearch 遇到文檔中以前 未遇到的字段,它用 dynamic mapping 來確定字段的數據類型並自動把新的字段添加到類型映射

可以用 dynamic 配置來控制

  • true

    動態添加新的字段–缺省

  • false

    忽略新的字段

  • strict

    如果遇到新字段拋出異常

配置參數 dynamic 可以用在根 object 或任何 object 類型的字段上。可以將 dynamic 的默認值設置爲 strict , 而只在指定的內部對象中開啓它

PUT /my_index
{
    "mappings": {
        "my_type": {
            "dynamic":      "strict", 
            "properties": {
                "title":  { "type": "string"},
                "stash":  {
                    "type":     "object",
                    "dynamic":  true 
                }
            }
        }
    }
}

如果遇到新字段,對象 my_type 就會拋出異常,而內部對象 stash 遇到新字段就會動態創建新字段

PUT /my_index/my_type/1
{
    "title":   "This doc adds a new field",
    "stash": { "new_field": "Success!" }
}

自定義動態映射

日期檢測

當 Elasticsearch 遇到一個新的字符串字段時,它會檢測這個字段是否包含一個可識別的日期,比如 2014-01-01 。 如果它像日期,這個字段就會被作爲 date 類型添加。否則,它會被作爲 string 類型添加

日期檢測可以通過在根對象上設置 date_detection 爲 false 來關閉:

PUT /my_index
{
    "mappings": {
        "my_type": {
            "date_detection": false
        }
    }
}

動態模板

使用 dynamic_templates ,可以完全控制 新檢測生成字段的映射。甚至可以通過字段名稱或數據類型來應用不同的映射

模板按照順序來檢測;第一個匹配的模板會被啓用

PUT /my_index
{
    "mappings": {
        "my_type": {
            "dynamic_templates": [
                { "es": {
                      "match":              "*_es", 
                      "match_mapping_type": "string",
                      "mapping": {
                          "type":           "string",
                          "analyzer":       "spanish"
                      }
                }},
                { "en": {
                      "match":              "*", 
                      "match_mapping_type": "string",
                      "mapping": {
                          "type":           "string",
                          "analyzer":       "english"
                      }
                }}
            ]
}}}

索引

通過使用 index API ,文檔可以被 索引 —— 存儲和使文檔可被搜索

這個索引採用的是默認的配置,新的字段通過動態映射的方式被添加到類型映射

想禁止自動創建索引,可以通過在 config/elasticsearch.yml 的每個節點下添加下面的配置

action.auto_create_index: false

刪除索引

DELETE /my_index
// 刪除多個索引
DELETE /index_one,index_two
DELETE /index_*
// 刪除全部索引
DELETE /_all
DELETE /*

創建一個自定義分析器

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

把這個分析器應用在一個 string 字段上

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

參考:

Elasticsearch: 權威指南

https://www.elastic.co/guide/cn/elasticsearch/guide/current/foreword_id.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章