ElasticSearch搜索解析

這篇介紹稍多,篇幅可能有點多,下面會針對一些重要的點做一些小測試

搜索返回文檔解析

hits
搜索返回的結果中最重要的一部分其中包含了 索引信息(_index,_type,_index,_source,_score),_source又是其中我們最需要的東西,裏面包含了查詢的整個文檔的內容,默認返回10個文檔,這塊可以結合分頁處理

took
顯示查詢花費的時間

shards
查詢的數據實際都檢索了幾個分區,這塊跟關係型數據庫中的表分區差不多,mysql 中的 partitions 通過執行計劃查看可以看到
"_shards" : {
"failed" : 0,
"successful" : 10,
"total" : 10
}
看下上面的問題,這裏可以會有失敗的檢索分片個數,在時間過程中如果數據分片丟失了,這裏仍然會返回查詢到的數據

timeout
查詢是否超時
"timed_out" : false,我們也可以這樣來寫
GET _index/_type/?timeout=50 (ms)
給出我們的預期檢索時間,當數據體量過大的時候或服務器性能本身的瓶頸,可能一次有效的搜索返回的結果很大(很多滿足條件的搜索),這可能需要很長的時間,這是用戶不能接受的,給出一個預期的需要查詢的失效時間,滿足這個時間就會停止查詢,返回我們的結果,當然 可能搜索 10ms可能也會有很多數據,這塊就需要分頁顯示了,給數據庫分頁一個概念

多索引多類型查詢 

滿足如下規則即可
_index1,_index2/_type1,_type2/_search 用逗號分隔開

對應泛指情況可以使用*來處理,這點有點類似 Window系統中常用的搜索 如 *.jpg
a*,b*/_type1,_type2/_search
這裏需要說明的是所有的 索引類型都是組合關係

分頁查詢

分頁查詢結合前面的查詢通過 size 、from 來處理
GET _index/_type/_search?size=10&from=10
size:需要查詢的數據條數 、from:從那一條開始 默認是0,這塊與EF中的 Take(size).Skip(from) 類似,取多少條,跳過多少開始取

這裏如果是單個分片查詢還好,如果數據來之多個分片的情況下,排序就需要處理了,否則查詢出來的數據可能不是最滿足我們條件的數據。

如果我們能在查詢來自3個分片,我們需要在我們實際查詢的邏輯是先查詢出來在組合起來排序取到size,這點類似 你高中時期的成績

如:我們需要取出來 全年級排名前40-50的人員信息,首先我們不能取每個班級第40-50的人員 然後按班級組合在一起 在取第40-50,顯然這樣做是錯誤的,因爲 班級1-50中的一些完全有可能在年紀40-50中,但是這裏有一個臨界點,就是班級中50以後的絕對不會在年級的前50,這是可以肯定的,爲了查詢到最後的結果,必須把每個班級都是取前5-條數據,然後合併後在取第40-50的數據纔是正確的

注:這裏我們實際要檢索的數據 是 【分片數據*from】的數量,當我們的分頁縱深很大的時候 取到第500頁,每頁10條,3個分片,最終檢索出來的數據是 3*5000 條數據的排序 ,如果取到數據縱深越大,搜索效率也是越低的

輕量查詢

輕量級查詢不需要寫表達式,一般通過get請求,通過參數的方式查詢結果,帶有query match 等表達式的查詢不一樣,它是通過地址欄組合參數來處理

結合前面數據例子:
http://192.168.0.212:9200/liyouming/_search?pretty&q=+mytext:黎又銘)
結果:

{
  "took" : 7,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 3.0561461,
    "hits" : [
      {
        "_index" : "liyouming",
        "_type" : "liyoumingtext",
        "_id" : "c7eQAWoB0Mh7sqcTGY-K",
        "_score" : 3.0561461,
        "_source" : {
          "mytext" : "中午黎又銘在操場上打籃球"
        }
      },
      {
        "_index" : "liyouming",
        "_type" : "liyoumingtext",
        "_id" : "dLeQAWoB0Mh7sqcTdo9b",
        "_score" : 2.1251993,
        "_source" : {
          "mytext" : "深夜還在寫代碼的人只有黎又銘"
        }
      },
      {
        "_index" : "liyouming",
        "_type" : "liyoumingtext",
        "_id" : "crePAWoB0Mh7sqcTzY-2",
        "_score" : 0.8630463,
        "_source" : {
          "mytext" : "黎又銘早上吃了一碗麪"
        }
      }
    ]
  }
}
View Code

當然:我們要對同一個字段得到 or 這種操作 通過 條件空格 來分割多個選項,參數只能寫q
如:http://192.168.0.212:9200/liyouming/_search?pretty&q=+mytext:(值1 值2)

這裏還有其他的一些符號
+ 表示+後面的字段必須滿足 值條件 類似 sql中的 in 包含
- 表示 - 後面的字段不能滿足 值條件 類似 sql中的 not in 不包含
:表示用來區分字段與字段值
() 表示對於多個值的一種組合

這是對字符串的操作,有時候我們需要對日期 數字 等進行範圍 大小等查詢操作
下面來加幾條數據

{
"url":"datetest/mytest",
 "param":{
"name":"張三",
 "date":"2019-04-18 15:15:16"

}
}
View Code

例子:
日期 http://192.168.0.212:9200/liyouming/_search?pretty&q=+date:>2019-03-01


顯然這樣搜索可能跟我們預期的結果不一樣,一條數據有沒有,其實添加的時候這個格式就錯誤了,可能沒有被es認知並轉換爲時間類型,elasticsearch中會轉換它認爲是時間格式的字符串,詳細可以參考這裏:
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html

我們先不加入時間段部分,查詢搜索就ok了

映射(Mapping)描述數據在每個字段內如何存儲

如同前面對日期的識別,其實es映射支持類型有,當滿足格式es會猜測類型

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

我們可以通過如下地址查看映射
http://192.168.0.212:9200/datetest/_mapping?pretty
來查看下 datetest 對於字段的映射,可以看到屬性中的字段類型
結果:

{
  "datetest" : {
    "mappings" : {
      "test" : {
        "properties" : {
          "date" : {
            "type" : "date"
          },
          "name" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    }
  }
}
View Code

接下來檢驗下這些類型映射情況並實現搜索下查看下最終的結果 Put創建下

{
"index":"mappingtest/test",
 "param":{
  "booltest1":false,
  "booltest2":"true",
  "inttest":123,
  "inttest1":"456",
  "floattest":123.45,
  "floattest1":"456.45",
  "datetest1":"2019-02-01",
  "datetest2":"2019-02-01 15:15:15", 
  "datetest3":"2019-02-01T15:15:15", 
  "datetest4":"10:15:30",
  "datetest5":"Tue, 3 Jun 2008 11:05:30 GMT",
  "datetest6":"2018-12-03+01:00",
  "datetest7":"20190103",
}
}
View Code

我們看下這些格式的映射情況 通過結果我們可以看到哪些字段被識別成了相應的類型

這是實際類型的識別情況

{
  "mappingtest" : {
    "mappings" : {
      "test" : {
        "properties" : {
          "booltest1" : {
            "type" : "boolean"
          },
          "booltest2" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "datetest1" : {
            "type" : "date"
          },
          "datetest2" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "datetest3" : {
            "type" : "date"
          },
          "datetest4" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "datetest5" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "datetest6" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "datetest7" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "floattest" : {
            "type" : "float"
          },
          "floattest1" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "inttest" : {
            "type" : "long"
          },
          "inttest1" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    }
  }
}
實際識別情況
{
"index":"mappingtest/test",
 "param":{
  "booltest1":false, // boolean
  "booltest2":"true", // text
  "inttest":123, // long
  "inttest1":"456", //text
  "floattest":123.45, //float
  "floattest1":"456.45", //text
  "datetest1":"2019-02-01",//date
  "datetest2":"2019-02-01 15:15:15",//text
  "datetest3":"2019-02-01T15:15:15", //date
  "datetest4":"10:15:30", //text
  "datetest5":"Tue, 3 Jun 2008 11:05:30 GMT", ", //text
  "datetest6":"2018-12-03+01:00", ", //text
  "datetest7":"20190103", ", //text
}
}
View Code

當然我們是可以對這塊映射預先設置對於的類型(type)以及 分析器(analyzed),但是這塊需要預先設置,如果對之前已經有的進行修改不會成功,下面我們添加了一個datetest10,我們在來測試羨慕提娜佳的數據看一下

{
"index":"mappingtest/_mapping/test",
"param":{
  "properties" : {
    "datetest10" : {
      "type" :    "date",
      "analyzed":"ik_smart"
    }
  }
}
}
View Code

預先設定好類型後對後續的收錄會出現相關的類型校驗,如果沒有被識別到對於類型的格式就會錯誤

分析(Analysis)全文是如何處理使之可以被搜索的


標準分析器:是Elasticsearch默認使用的分析器。它是分析各種語言文本最常用的選擇

空格分析器:在空格的地方劃分文本。它會產生

簡單分析器:在任何不是字母的地方分隔文本,將詞條小寫。它會產生

語言分析器:特定語言分析器可用於 {ref}/analysis-lang-analyzer.html[很多語言]。它們可以考慮指定語言的特點

這裏用中文分詞分析器來舉例,分析器會幫會將文檔內容中一個字段按照分詞的方式拆解開
這塊跟搜索密不可分,這裏就需要分詞插件 分詞庫,這裏測試用的ik
安裝ik在前面的文章中已經介紹過了

如果是在數據中要查找新聞內容的化會出現 like '%搜索詞彙%' 也可以通過全文查找,下面來介紹下es中的怎麼來處理的

打個比方 分析器就是物流運輸過程中的 分揀員,他會根據包裹的 地區 位置 等信息 分揀包裹 以便於用戶能夠準確的收到自己的包裹,分詞如下一樣,每個分詞組都記錄了來源的文檔位置方便索引,同時對匹配會有一個評分,匹配度高的詞彙來源文檔次數越多,這樣文檔的評分就越高

我們來創建一個我們的IK分析器測試 ik_max_word

{
"index":"_analyze?pretty",
"param":{
"analyzer":"ik_max_word",
"text":"這是一個非常好的分詞庫測試"
}
}
分詞結果:
{
  "tokens": [
    {
      "token": "這是",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "一個",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "",
      "start_offset": 2,
      "end_offset": 3,
      "type": "TYPE_CNUM",
      "position": 2
    },
    {
      "token": "",
      "start_offset": 3,
      "end_offset": 4,
      "type": "COUNT",
      "position": 3
    },
    {
      "token": "非常好",
      "start_offset": 4,
      "end_offset": 7,
      "type": "CN_WORD",
      "position": 4
    },
    {
      "token": "非常",
      "start_offset": 4,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 5
    },
    {
      "token": "",
      "start_offset": 6,
      "end_offset": 7,
      "type": "CN_CHAR",
      "position": 6
    },
    {
      "token": "",
      "start_offset": 7,
      "end_offset": 8,
      "type": "CN_CHAR",
      "position": 7
    },
    {
      "token": "分詞",
      "start_offset": 8,
      "end_offset": 10,
      "type": "CN_WORD",
      "position": 8
    },
    {
      "token": "詞庫",
      "start_offset": 9,
      "end_offset": 11,
      "type": "CN_WORD",
      "position": 9
    },
    {
      "token": "測試",
      "start_offset": 11,
      "end_offset": 13,
      "type": "CN_WORD",
      "position": 10
    }
  ]
}
View Code

同樣的句子我們再來以另一種規則分詞 ik_smart

{
"index":"_analyze?pretty",
"param":{
"analyzer":"ik_smart",
"text":"這是一個非常好的分詞庫測試"
}
}
分詞結果可以看到

{
  "tokens": [
    {
      "token": "這是",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "一個",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "非常好",
      "start_offset": 4,
      "end_offset": 7,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "",
      "start_offset": 7,
      "end_offset": 8,
      "type": "CN_CHAR",
      "position": 3
    },
    {
      "token": "",
      "start_offset": 8,
      "end_offset": 9,
      "type": "CN_CHAR",
      "position": 4
    },
    {
      "token": "詞庫",
      "start_offset": 9,
      "end_offset": 11,
      "type": "CN_WORD",
      "position": 5
    },
    {
      "token": "測試",
      "start_offset": 11,
      "end_offset": 13,
      "type": "CN_WORD",
      "position": 6
    }
  ]
}
View Code

 領域特定查詢語言(Query DSL)Elasticsearch 中強大靈活的查詢語言

說到這塊的查詢,表達式是以請求體的方式發送的而且是Get請求,一開始我也覺得很奇怪,我在.NetCore環境下模擬這些請求,Get請求帶請求體連我的Api路由都沒辦法匹配到

我又嘗試了用POST請求,但是這是可以查詢到結果的,於是我在Linux上又試了一次
curl -XGET "localhost:9200/test/_search?pretty" -H "Context-Type:application/json" -d "{Query DSL}"

這樣也是沒問題的,這塊是怎麼回事呢?

官方也給出了說明:
某些特定語言(特別是 JavaScript)的 HTTP 庫是不允許 GET 請求帶有請求體的。事實上,一些使用者對於 GET 請求可以帶請求體感到非常的吃驚,而事實是這個RFC文檔 RFC 7231— 一個專門負責處理 HTTP 語義和內容的文檔 — 並沒有規定一個帶有請求體的 GET 請求應該如何處理!結果是,一些 HTTP 服務器允許這樣子,而有一些 — 特別是一些用於緩存和代理的服務器 — 則不允許。
對於一個查詢請求,Elasticsearch 的工程師偏向於使用 GET 方式,因爲他們覺得它比 POST 能更好的描述信息檢索(retrieving information)的行爲。然而,因爲帶請求體的 GET 請求並不被廣泛支持,所以 search API同時支持 POST 請求

這也就是我使用POST請求的方式能夠成功的原因,它是都(GET/POST)支持的


合併查詢語句

葉子語句: 被用於將查詢字符串和一個字段(或者多個字段)對比 , 入宮MSSQL中組成的一個一個的條件,在MSSQL中一個一個的條件之間會存在一定的關係,比如 >、 <、 in、 not in 、= 、還有就是組合()括號內的條件 等等,那麼在es中是怎麼來處理這樣的查詢關係呢?需要組合條件

如果是單個的查詢 match 匹配 not_match 等,這裏都是match 匹配 類似(like)而非精確查找,如 = 這種精確查找 要結合 _mapping 中的 not_analyzed 設置 和 terms 來處理

複合語句:在EF中用到過Linq lambda表達的都知道Expression,有時候在查詢結果組合後都會返回一個 boolean值,在es中這塊也是異曲同工。

查詢表達式需要在query裏面,當然也可以通過簡單查詢通過url地址處理,這裏的是表達式方式,可以處理複雜的一些查詢條件

{
"query":{
  "match":{"filed1":"value1"},
  "match":{"filed2":"value2"}
}
}

這裏的每一個match都是匹配型的類似like,這裏兩個match之間的關係是and並且關係,下面我們來驗證下

{
"index":"liyouming/_search?pretty",
"param":{
"query":{
   "match": { "mytext": "黎又銘" }
}       
}
}

可以看到結果 所有包含【黎又銘】的都檢索出來了

{
  "took": 6,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 3.0561461,
    "hits": [
      {
        "_index": "liyouming",
        "_type": "liyoumingtext",
        "_id": "c7eQAWoB0Mh7sqcTGY-K",
        "_score": 3.0561461,
        "_source": {
          "mytext": "中午黎又銘在操場上打籃球"
        }
      },
      {
        "_index": "liyouming",
        "_type": "liyoumingtext",
        "_id": "dLeQAWoB0Mh7sqcTdo9b",
        "_score": 2.1251993,
        "_source": {
          "mytext": "深夜還在寫代碼的人只有黎又銘"
        }
      },
      {
        "_index": "liyouming",
        "_type": "liyoumingtext",
        "_id": "crePAWoB0Mh7sqcTzY-2",
        "_score": 0.8630463,
        "_source": {
          "mytext": "黎又銘早上吃了一碗麪"
        }
      }
    ]
  }
}
View Code

接下來使用2個條件

{
"index":"liyouming/_search?pretty",
"param":{
"query":{
   "match": { "mytext": "黎又銘" },
   "match": { "mytext": "中午" }
}   
}       
}
查詢結果 同時滿足條件的就只有1條數據了
{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1.0187154,
    "hits": [
      {
        "_index": "liyouming",
        "_type": "liyoumingtext",
        "_id": "c7eQAWoB0Mh7sqcTGY-K",
        "_score": 1.0187154,
        "_source": {
          "mytext": "中午黎又銘在操場上打籃球"
        }
      }
    ]
  }
}
View Code

關於這塊查詢還有太多的東西,將會在後面的章節繼續介紹