elasticsearch

es基本概念

es術語介紹

  • 文檔Document
    • 用戶存儲在es中的數據文檔
  • 索引Index
    • 由具備相同字段的文檔列表組成
  • 節點node
    • 一個Elasticsearch的運行實例,是集羣的構成單元
  • 集羣Cluster
    • 由一個或多個節點組成,對外提供服務

document介紹

  • json object,由字段(field)組成,常見數據類型以下:
    • 字符串:text,keyword
    • 數值型:long,integer,short,byte,double,float,half_float,scaled_float
    • 布爾:boolean
    • 日期:date
    • 二進制:binary
    • 範圍類型:integer_range,float_range,long_range,double_range,date_range
  • 每一個文檔有惟一的id標識
    • 自行指定
    • es自動生成
  • 元數據,用於標註文檔的相關信息
    • _index:文檔所在的索引名
    • _type:文檔所在的類型名
    • _id:文檔惟一id
    • _uid:組合id,由_type和_id組成(6.x _type再也不起做用,同_id同樣)
    • _source:文檔的原始json數據,能夠從這裏獲取每一個字段的內容
    • _all:整合全部字段內容到該字段,默認禁用

Index介紹

  • 索引中存儲具備相同結構的文檔(Document)
    • 每一個索引都有本身的mapping定義,用於定義字段名和類型
  • 一個集羣能夠有多個索引,好比:
    • nginx日誌存儲的時候能夠按照日期天天生成一個索引來存儲

rest api

  • elasticsearch集羣對外提供restful api
    • rest-representational state transfer
    • uri指定資源,如index、document等
    • http method指明資源操做類型,如GET、POST、PUT、DELETE等
  • 經常使用兩種交互方式
    • curl命令行
    • Kibana Devtools

索引API

  • es有專門的index api,用於建立、更新、刪除索引配置等
    • 建立索引api以下:
      PUT /test_index

文檔api

  • es有專門的Document API
    • 建立文檔
    • 查詢文檔
    • 更新文檔
    • 刪除文檔
  • 建立文檔
    • 指定id建立文檔api以下:
      PUT /test_index/doc/1
      {
      "username":"alfred", "age":1 }

      建立文檔時,若是索引不存在,es會自動建立對應的index和typehtml

    • 不指定id建立文檔api以下:
      POST /test_index/doc
      {
      "username":"tom", "age":20 }
  • 查詢文檔
    • 指定要查詢的文檔id
      GET /test_index/doc/1
    • 搜索全部文檔,用到_search,以下:
GET /test_index/doc/_search
{
    "query":{ "term":{ "_id":"1" } } }

查詢語句,json格式,放在http body中發送到esjava

批量建立文檔api

  • es容許一次建立多個文檔,從而減小網絡傳輸開銷,提高寫入速率
    • endpoint爲_bulk,以下:
      POST _bulk
      {"index":{"_index":"test_index","_type":"doc","_id":"3"}} {"username":"alfred","age":10} {"delete":{"_index":"test_index","_type":"doc","_id":"1"}}

批量查詢文檔API

  • es容許一次查詢多個文檔
    • endpoint爲_mget,以下:
      GET /_mget
      {
      "doc":[ { "_index":"test_index", "_type":"doc", "_id":"1" }, { "_index":"test_index", "_type":"doc", "_id":"2" } ] }

索引

正排索引與倒排索引

  • 正排索引
    • 文檔id到文檔內容、單詞的關聯關係
文檔id 文檔內容
1 elasticsearch是最流行的搜索引擎
2 PHP是世界上最好的語言
3 搜索引擎是如何誕生的
  • 倒排索引
    • 單詞到文檔id的關聯關係
單詞 文檔ID列表
elasticsearch 1
流行 1
搜索引擎 1,3
世界 2

倒排索引-查詢流程

  • 查詢包含"搜索引擎"的文檔
    • 經過倒排索引得到"搜索引擎"對應的文檔ID有1和3
    • 經過正排索引查詢1和3的完整內容
    • 返回用戶最終結果

倒排索引詳解

  • 倒排索引是搜索引擎的核心,主要包含兩部分:
    • 單詞字典(Term Dictionary)
    • 倒排列表(Posting List)
  • es存儲的是一個json格式的文檔,其中包含多個字段,每一個字段會有本身的倒排索引

單詞詞典

  • 單詞字典(Term Dictionary)是倒排索引的重要組成部分
    • 記錄全部文檔的單詞,通常都比較大
    • 記錄單詞到倒排列表的關聯信息
  • 單詞字典的實現通常是用B+ Tree

倒排列表

  • 倒排列表記錄了單詞對應的文檔集合,由倒排索引項組成
  • 倒排索引項主要包含以下信息:
    • 文檔ID,用於獲取原始信息
    • 單詞頻率,記錄該單詞在該文檔中的出現次數,用於後續相關性算分
    • 位置,記錄單詞在該文檔中的分詞位置(多個),用於作詞語搜索
    • 偏移,記錄單詞在文檔的開始和結束位置,用於作高亮顯示

分詞

  • 分詞是指將文本轉換成一系列單詞的過程,也叫做文本分析,在es裏面稱爲analysis

分詞器

  • 分詞器是es中專門處理分詞的組件,英文爲analy,它的組成以下:
    • Character Filters
      • 針對原始文本進行處理,好比去除HTML特殊標記符
    • Tokenizer
      • 將原始文本按照必定規則切分爲單詞
    • Token Filters
      • 針對tokenizer處理的單詞進行再加工,好比轉小寫、刪除或新增等處理

        Analyize API

    • es提供了一個測試分詞的api接口,方便驗證分詞效果,endpoint是_analyze
      • 能夠直接指定analyzer進行測試
      • 能夠直接指定索引中的字段進行測試
      • 能夠自定義分詞器進行測試
  • 直接指定analyzer進行測試,接口以下:
    POST _analyze
    {
    "analyzer":"standard", #分詞器 "text":"hello,world" #測試文本 }
  • 自定義分詞器進行測試,接口以下:
    POST _analyze
    {
    "tokenizer":"standard", "filterf":["lowercase"], #自定義analyzer "text":"hello world" }

預約義的分詞器

  • es自帶以下的分詞器
    • Standard
    • Simple
    • Whitespace
    • Stop
    • Keyword
    • Pattern
    • Language
  • Standard Analyzer
    • 默認分詞器
    • 其組成以下,特性爲:
      • 按詞切分,支持多語言
      • 小寫處理
  • Simple Analyzer
    • 其組成以下,特性爲:
      • 按照非字母切分
      • 小寫處理
  • Whitespace Analyzer
    • 其組成以下,特性爲:
      • 按照空格切分
  • Stop Analyzer
    • Stop Word指語氣助詞等修飾性的詞語,好比the、an、的、這等等
    • 其組成如圖,特性爲:
      • 相比Simple Analyzer多了stop Word處理
  • Keyword Analyzer
    • 其組成以下,特性爲:
      • 不分詞,直接將輸入做爲一個單詞輸出
  • Pattern Analyze
    • 其組成以下,特性爲:
      • 經過正則表達式自定義分隔符
      • 默認是\W+,即非字詞的符號做爲分隔符
  • language Analyze
    • 提供了30+常見語言的分詞器
    • Arabic,Armenian,basque,bengali,Brazilian,Bulgarian,catAlan,cjk,Czech,Danish,Dutch,English...

中文分詞

  • 難點
    • 中文分詞指的是將一個漢字序列切分紅一個一個單獨的詞。在英文中,單詞之間是以空格做爲天然分界符,漢語中沒有一個形式上的分界符
    • 上下文不一樣,分詞結果迥異,好比交叉歧義問題,好比下面兩種分詞都合理
      • 乒乓球拍/賣/完了
      • 乒乓球/拍賣/完了
  • 經常使用分詞系統
  • 基於天然語言處理的分詞系統

自定義分詞

  • 當自帶的分詞沒法知足需求時,能夠自定義分詞
    • 經過自定義Character Filters、Tokenizer Filter實現
  • Character Filters
    • 在Tokenizer以前對原始文本進行處理,好比增長、刪除或替換字符等
    • 自帶的以下:
      • HTML Strip去除html標籤和轉換html實體
      • Mapping進行字符替換操做
      • Pattern Replace進行正則匹配替換
    • 會影響後續Tokenizer解析的position和offset信息
  • Character Filters測試時能夠採用以下api:
    POST _analyze
    {
    "tokenizer":"keyword", #keyword類型的Tokenizer能夠直接看到輸出結果 "char_filter":["html_strip"], #指明要使用的char_filter "text":"<p>I'm so <b>happy</b>!</p>" }
  • Tokenizer
    • 將原始文本按照必定規則且分爲單詞(term or token)
    • 自帶的以下:
      • standard按照單詞進行分割
      • letter按照非字符類進行分割
      • whitespace按照空格進行分割
      • UAX URL Email按照standard分割,但不會分割郵箱和url
      • NGram和Edge NGram連詞分割
      • Path Hierarchy按照文件路徑進行分割
    • Tokenizer測試時能夠採用以下api:
      POST _analyze
      {
      "tokenizer":"path_hierarchy", "text":"/one/two/three" }
  • Token Filters
    • 對於Tokenizer輸出的單詞(term)進行增長、刪除、修改等操做
    • 自帶的以下:
      • lowercase將全部term轉換爲小寫
      • stop刪除stop words
      • NGram和Edge NGram連詞分割
      • Synonym添加近義詞的term
    • Filter測試時能夠採用以下api:
      POST _analyze
      {
      "text":"a hello world", "tokenizer":"standard", "filter":[ "stop", "lowercase", { "type":"ngram", "min_gram":4, "max_gram":4 } ] }

自定義分詞的api

  • 自定義分詞的api
    • 自定義分詞須要在索引的配置中設定,以下所示:
      PUT test_index
      {
      "settings":{ "analysis":{ "char_filter":{}, "tokenizer":{}, "filter":{}, "analyzer":{} } } }

分詞使用說明

  • 分詞會在以下兩個時機使用:
    • 建立或更新文檔時(Index Time),會對相應的文檔進行分詞處理
    • 查詢時(Search Time),會對查詢語句進行分詞
  • 索引時分詞是經過配置Index Mapping中每一個字段的analyzer屬性實現的,以下:
    • 不指定分詞時,默認使用standard
      PUT test_index
      {
      "mappings":{ "doc":{ "properties":{ "title":{ "type":"text", "analyzer":"whitespace" #指定分詞器 } } } } }
  • 查詢時分詞的指定方式有以下幾種:node

    • 查詢時經過analyzer指定分詞器
      POST test_index/_search
      {
      "query":{ "match":{ "message":{ "query":"hello", "analyzer":"standard" } } } }
  • 經過index mapping設置search_analyzer實現
PUT test_index
{
  "mappings":{ "doc":{ "properties":{ "title":{ "type":"text", "analyzer":"whitespace", "search_analyzer":"standard" } } } } }
  • 通常不須要特別指定查詢時分詞器,直接使用索引時分詞器便可,不然會出現沒法匹配的狀況下

分詞使用建議

  • 明確字段是否須要分詞,不須要分詞的字段就將type設置爲keyword,能夠節省空間和提升寫性能
  • 善用_analyze API,查看文檔的具體分詞結果
  • 動手測試

Mapping

  • 相似數據庫中的表結構定義,主要做用以下:
    • 定義Index下的字段名(Field Name)
    • 定義字段的類型,好比數值型、字符串型、布爾型等
    • 定義倒排索引相關的配置,好比是否索引、記錄position等。

自定義mapping

  • 自定義mapping的api以下所示:
    PUT my_index
    {
    "mappings":{ "doc":{ "properties":{ "title":{ "type":"text" }, "name":{ "type":"keyword" }, "age":{ "type":"integer" } } } } }
  • Mapping中的字段類型一旦設定後,禁止直接修改,緣由以下:
    • Lucene實現的倒排索引生成後不容許修改
  • 從新創建新的索引,而後作reindex操做
  • 容許新增字段
  • 經過dynamic參數來控制字段的新增
    • true(默認)容許自動新增字段
    • false不容許自動新增字段,可是文檔能夠正常寫入,但沒法對字段進行查詢等操做
    • strict文檔不能寫入,報錯

copy_to參數

  • copy_to
    • 將該字段的值複製到目標字段,實現相似_all的做用
    • 不會出如今_source中,只用來搜索
      PUT my_index
      {
      "mappings":{ "doc":{ "properties":{ "first_name":{ "type":""text", "copy_to":"full_name" }, "last_name":{ "type":"text", "copy_to":"full_name" }, "full_name":{ "type":"text" } } } } }

index參數

  • index
    • 控制當前字段是否索引,默認爲true,即記錄索引,false不記錄,即不可搜索
      PUT my_index
      {
      "mappings":{ "doc":{ "properties":{ "cookie":{ "type":"text", "index":"false" } } } } }

index_options參數

  • index_options用於控制倒排索引記錄的內容,有以下4中配置
    • docs只記錄doc id
    • freqs記錄doc id和term frequencies
    • positions記錄doc id、term frequencies和term position
    • offsets記錄doc id、term frequencies、term position和character offsets
  • text類型默認配置爲positions,其餘默認爲docs
  • 記錄內容越多,佔用空間越大
  • indexoptions設定以下所示:
    PUT my_index
    {
    "mappings":{ "doc":{ "properties":{ "cookies":{ "type":"text", "index_options":"offsets" } } } } }
  • null_value
    • 當字段遇到null值時的處理策略,默認爲null,即空值,此時es會忽略該值。能夠經過設定該值設定字段的默認值
      PUT my_index
      {
      "mappings":{ "my_type":{ "properties":{ "status_code":{ "type":"keyword", "null_value":"NULL" } } } } }

數據類型

  • 核心數據類型
    • 字符串:text,keyword
    • 數值型:long,integer,short,byte,double,float,half_float,scaled_float
    • 布爾:boolean
    • 日期:date
    • 二進制:binary
    • 範圍類型:integer_range,float_range,long_range,double_range,date_range
  • 複雜數據類型
    • 數組類型array
    • 對象類型object
    • 嵌套類型nested object
  • 地理位置數據類型
    • geo_point
    • geo_shape
  • 專用類型
    • 記錄ip地址ip
    • 實現自動補全completion
    • 記錄分詞數token_count
    • 記錄字符串hash值murmur3
    • percolator
    • join
  • 多字段特性
    • 容許對同一個字段採用不一樣的配置,好比分詞,常見例子如對人名實現拼音搜素,只須要在人名中新增一個子字段爲pinyin便可
      {
      "test_index":{ "mappings":{ "doc":{ "properties":{ "username":{ "type":"text", "fields":{ "pinyin":{ "type":"text", "analyzer":"pinyin" } } } } } } } }

Dynamic Mapping

  • es能夠自動識別文檔字段類型,從而下降用戶使用成本
  • es是依靠json文檔的字段類型來實現自動識別字段類型,支持的類型以下:
JSON類型 es類型
null 忽略
Boolean Boolean
浮點類型 float
整數 long
object object
array 有第一個非null值的類型決定
string 匹配爲日期則設爲date類型(默認開啓),匹配爲數字的話設爲float或long類型(默認關閉),設爲text類型,並附帶keyword的子字段

dynamic日期與數字識別

  • 日期的自動識別能夠自行配置日期格式,以知足各類需求
    • 默認是["strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy /MM/dd Z"]
    • strict_date_optional_time是ISO datetime的格式,完整格式相似下面:
      • YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-20T15;30:50 +01:00)
    • dynamic_date_formats能夠自定義日期類型
    • date_detection能夠關閉日期自動識別的機制
      PUT mu_index
      {
      "mappings":{ "my_type":{ "dynamic_date_formats":["MM/dd/yyyy"], "date_detection":false } } }
  • 字符串是數字時,默認不會自動識別爲整型,由於字符串中出現數字是徹底合理的
    • numeric_detection能夠開啓字符串中數字的自動識別,以下所示:
      PUT my_index
      {
      "mappings":{ "my_type":{ "numeric_detection":true } } }

Dynamic Templates

  • 容許根據es自動識別的數據類型、字段名等來動態設定字段類型,能夠實現以下效果:
    • 全部字符串類型都設定爲keyword類型,即默認不分詞
    • 全部以message開頭的字段都設定爲text類型,即分詞
    • 全部以long_開頭的字段都設定爲long類型
    • 全部自動分配爲double類型的都設定爲float類型,以節省空間
  • API以下所示:
    PUT test_index
    {
    "mappings":{ "doc":{ "dynamic_templates":[#數組,可指定多個匹配規則 { "strings":{ #模板名稱 "match_mapping_type":"string",#匹配規則 "mapping":{ "type":"keyword" } } } ] } } }
    • 匹配規則通常有以下幾個參數:
      • match_mapping_type匹配es自動識別的字段類型,如Boolean,long,string等
      • match,unmatch匹配字段名
      • path_match,path_unmatch匹配路徑

自定義mapping的建議

  • 自定義Mapping的操做步驟以下:
    1. 寫入一條文檔到es的臨時索引中,獲取es自動生成的mapping
    2. 修改步驟1獲得的mapping,自定義相關配置
    3. 使用步驟2的mapping建立實際所需索引

首先建立一個文檔python

PUT my_index/doc/1
{
  "referrer": "-", "response":"200", "remote_ip":"171.22.12.14", "method":"POST", "user_name":"-", "http_version":"1.1", "body_sent":{ "bytes":"0" }, "url":"/analyzeVideo" }

es會根據建立的文檔動態生成映射,能夠直接將動態生成的映射直接複製到須要自定義的mapping中nginx

PUT test_index
{
  "mappings": { "doc": { "properties": { "body_sent": { "properties": { "bytes": { "type": "long" } } }, "http_version": { "type": "keyword" }, "method": { "type": "keyword" }, "referrer": { "type": "keyword" }, "remote_ip": { "type": "keyword" }, "response": { "type": "long" }, "url": { "type": "text" }, "user_name": { "type": "keyword" } } } } }

這樣定義的映射仍是比較多餘,能夠利用動態模板將string類型直接替換成keywordgit

DELETE test_index PUT test_index { "mappings": { "doc": { "dynamic_templates":[ { "strings":{ "match_mapping_type":"string", "mapping":{ "type":"keyword" } } } ], "properties": { "body_sent": { "properties": { "bytes": { "type": "long" } } }, "response": { "type": "long" }, "url": { "type": "text" } } } } }

索引模板

  • 索引模板,主要用於在新建索引時自動應用預先設定的配置,簡化索引建立的操做步驟
    • 能夠設定索引的配置和mapping
    • 能夠有多個模板,根據order設置,order搭的覆蓋小的配置

索引模板API以下所示:github

PUT _template/test_template
{
  "index_patterns":["te*","bar*"], "order":0, "settings":{ "number_of_shards":1, "number_of_replicas":0 }, "mappings":{ "doc":{ "_source":{ "enabled":false }, "properties":{ "name":{ "type":"keyword" } } } } }
  • "index_paterns":匹配的索引名稱
  • "order":匹配的優先級
  • "settings":索引的配置

search API

  • 查詢有兩種形式:
    • URI search
      • 操做簡單,方便經過命令行測試
      • 僅包含部分查詢語法
    • Request Body Search
      • es提供完備的查詢語法Query DSL

URI Search

  • 經過url query參數來實現搜索,經常使用參數以下:
    • q 指定查詢的語句,語法爲Query String Syntax
    • df q中不指定字段時默認查詢的字段,若是不指定es會查詢全部字段
    • sort 排序
    • timeout 指定超時時間,默認不超時
    • from,size用於分頁

URI Search - Query String Syntax

  • term與phrase
    • alfred way 等效於Alfred OR way
    • "alfred way" 詞語查詢,要求前後順序
  • 泛查詢
    • alfred 等效於在全部字段去匹配該term
  • 指定字段
    • name:alfred
  • Group分組設定,使用括號指定匹配的規則
    • (quick OR brown) AND fox
    • status:(active OR pending) title:(full text search)
  • 布爾操做符
    • AND(&&),OR(||),NOT(!)
      • name:(tom NOT lee)
      • 注意大寫,不能小寫
    • +—分別對應must和must_not
      • name:(tom +lee -alfred) #返回必定包含lee,能夠包含tom,必定不包含alfred的文檔
      • name:((lee && !alfred)) || (tom && lee && !alfred)
      • +在url中會被解析爲空格,要使用encode後的結果才能夠,爲%2B
  • 範圍查詢,支持數值和日期
    • 區間寫法,閉區間用[],開區間用{}
      • age:[1 TO 10] 意爲1<=age<=10
      • age:[1 TO 10} 意爲1<=age<10
      • age:[1 TO] 意爲age>=1
      • age:[* TO 10] 意爲age<=10
    • 算數符號寫法
      • age:>=1
      • age:(>=1 && <=10) 或者age:(+>=1 +<=10)
  • 通配符查詢
    • ?表明1個字符,*表明0或多個字符
      • name:t?m
      • name:tom*
      • name:t*m
    • 通配符匹配執行效率低,且佔用較多內存,不建議使用
    • 如無特殊需求,不要將?/*放在最前面
  • 正則表達式匹配
    • name:/[mb]oat/
  • 模糊匹配fuzzy query
    • name:roam~1
    • 匹配與roam差一個character的詞,好比foam roams等
  • 近似度查詢 proximity search
    • "fox quick" ~5
    • 以term爲單位進行差別比較,好比"quick fox" "quick brown fox"都會被匹配

Query DSL

  • 基於json定義的查詢語句,主要包含以下兩種類型:
    • 字段類查詢:
      • 如term,match,range等,只針對某一個字段進行查詢
    • 複合查詢
      • 如bool查詢等,包含一個或多個字段類查詢或者複合查詢語句

Query DSL 字段類查詢

  • 字段類查詢主要包括如下兩類:
    • 全文匹配
      • 針對text 類型的字段進行全文檢索,會對查詢語言先進行分詞處理,如match,match_phrase等query類型
    • 單詞匹配
      • 不會對查詢語句作分詞處理,直接去匹配字段的倒排索引,如term.terms,range等query類型
Match Query
  • 對字段做全文檢索,最基本和經常使用的查詢類型,API示例以下:
    GET test_index/_search
    {
    "query":{ "match":{ #關鍵詞 "remote_ip":"171.22.12.14" #字段名 } } }

    響應結果以下:正則表達式

    {
    "took": 4, #查詢總用時 "timed_out": false, #是否超時 "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 1, #匹配文檔總數 "max_score": 0.2876821, "hits": [ #返回文檔列表 { "_index": "test_index_index", "_type": "doc", "_id": "1", "_score": 0.2876821, #文檔相關度得分 "_source": { #文檔原始內容 "referrer": "-", "response": "200", "remote_ip": "171.22.12.14", "method": "POST", "user_name": "-", "http_version": "1.1", "body_sent": { "bytes": "0" }, "url": "/analyzeVideo" } } ] } }
  • 經過operator參數能夠控制單詞間的匹配關係,可選項爲or和and
  • 經過minimum_should_match參數能夠控制須要匹配的單詞數
Match Query -流程

首先對查詢語句進行分詞,分詞後分別根據字段的倒排索引進行匹配算分,並會匹配到一個或多個文檔,再將匹配到的文檔進行彙總得分,根據得分排序返回多個文檔算法

Match Phrase Query
  • 對字段作檢索,有順序要求,API示例以下:
    GET test_index_index/_search
    {
    "query":{ "match_phrase":{ "remote_ip":"171.22.12.14" } } }
  • 經過slop參數能夠控制單詞間的間隔
Query String Query
  • 相似於URI Search中的q參數查詢sql

    GET test_index_index/_search
    {
    
    "query":{ "query_string":{ "default_field":"remote_ip", "query":"171.22.12.14" } } }
Simple Query String Query
  • 相似Query String,可是會忽略錯誤的查詢語法,而且僅支持部分查詢語法
  • 其經常使用的邏輯符號以下,不能使用AND、OR、NOT等關鍵詞:

    • +代指AND
    • |代指OR
    • -代指NOT
      經常使用API以下所示:
      GET test_index_index/_search {

    "query":{
    "simple_query_string":{
    "fields":["remote_ip"],
    "query":"alfred +way"
    }
    }
    }

     
Term Query
  • 將查詢語句做爲整個單詞進行查詢,即不對查詢語句作分詞處理,以下所示:

    GET test_index_index/_search
    {
    
    "query":{ "term":{ "remote_ip":"171.22.12.14" } } }
Range Query
  • 範圍查詢主要針對數值和日期類型,以下所示:

    GET test_index_index/_search
    {
    
    "query":{ "range":{ "response":{#找出響應狀態碼大於10,小於300的文檔 "gt": 10, "lte":300 } } } }

    針對日期的查詢以下所示:

    GET test_index_index/_search
    {
    "query":{ "range":{ "birth":{ "gt": "1990-01-01", "lte":"now-2h", "gt":"2019-01-01||+1M/d" } } } }

相關性算分

  • 相關性算分是指文檔與查詢語句間的相關度,英文爲relevance
    • 經過倒排索引能夠獲取與查詢語句相匹配的文檔列表,那麼如何將最符合用戶查詢需求的文檔放到前列呢?
    • 本質是一個排序問題,排序的依據是相關性算分
  • 相關性算分的幾個重要概念以下:

    • Term Frequency(TF)詞頻,即單詞在該文檔中出現的次數。詞頻越高,相關度越高
    • Document Frequency(DF)文檔頻率,即單詞出現的文檔數
    • Inverse document Frequency(IDF)逆向文檔頻率,與文檔頻率相反,簡單理解爲1/DF。即單詞出現的文檔數越少,相關度越高
    • Field-length Norm 文檔越短,相關性越高
  • ES目前主要有兩個相關性算分模型,以下:
    • TF/IDF模型
    • BM25模型 5.x以後的默認模型
TF/IDF模型
  • 能夠經過explain參數來查看具體的計算方法,但要注意:
    • es的算分是按照shard進行的,即shard的分數計算是相互獨立的,因此在使用explain的時候注意分片數
    • 能夠經過設置索引的分片數爲1來避免這個問題
      GET test_index_index/_search
      {
      "explain": true, "query":{ "match":{ "remote_ip":"171.22.12.14" } } }
BM25模型
  • BM25模型中BM指Best Match,25指迭代了25次才計算方法,是針對TF/IDF的一個優化。
  • BM25相比TF/IDF的一大優化是下降了tf在過大時的權重

Query DSL 複合查詢

  • 複合查詢是指包含字段類查詢或符合查詢的類型,主要包括如下幾類:
    • constant_score query
    • bool query
    • dis_max query
    • function_score query
    • boosting query
      Constant Score Query
  • 該查詢將其內部的查詢結果文檔得分都設定爲1或者boost的值
    • 多用於結合bool查詢實現自定義部分
      GET test_index_index/_search
      {
      "query":{ "constant_score":{ "filter":{ "match": { "response":200 } } } } }

      響應以下:

      {
      "took": 7, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "test_index_index", "_type": "doc", "_id": "1", "_score": 1, "_source": { "referrer": "-", "response": "200", "remote_ip": "171.22.12.14", "method": "POST", "user_name": "-", "http_version": "1.1", "body_sent": { "bytes": "0" }, "url": "/analyzeVideo" } } ] } }
Bool Query
  • 布爾查詢由一個或多個布爾子句組成,主要包含以下四個:
子句 內容
filter 只過濾符合條件的文檔,不計算相關性得分
must 文檔必須符合must中的全部條件,會影響相關性得分
must_not 文檔必須不符合must_not中的全部條件
should 文檔能夠符合should中的條件,會影響相關性得分
  • Bool查詢的API以下所示
    GET test_index_index/_search
    {
    "query":{ "bool":{ "filter":[ {} ], "should":[ {} ], "must":[ {} ], "must_not": [ {} ] } } }

    查詢response爲200,ip爲"171.22.12.14"的值

    GET test_index_index/_search
    {
    "query":{ "bool":{ "must":[ { "match":{ "response":200 } }, { "match":{ "remote_ip": "171.22.12.14" } } ] } } }

查詢上下文與過濾器上下文

  • 當一個查詢語句位於Query或者Filter上下文時,es執行的結果會不一樣,對好比下:
上下文類型 執行類型 使用方式
Query 查詢與查詢語句最匹配的文檔,對全部文檔進行相關性算分並排序 1.query 2. bool中的must和should
Filter 查找與查詢語句相匹配的文檔,只過濾不算分,常用過濾器,ES會自動的緩存過濾器的內容,這對於查詢來講,會提升不少性能 1.bool中的filter與must_not 2.constant_score中的filter
GET /_search
{
  "query": { "bool": { "must": [ { "match": { "title": "Search" }}, { "match": { "content": "Elasticsearch" }} ], "filter": [ { "term": { "status": "published" }}, { "range": { "publish_date": { "gte": "2015-01-01" }}} ] } } }

Count API

  • 只返回符合條件的文檔數,endpoint爲_count,不返回文檔內容
    API示例以下:
    GET test_index_index/_count
    {
    "query":{ "match":{ "response":200 } } }

    響應以下:

    {
    "count": 1, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 } }

Source Filter

  • 過濾返回結果中_source中的字段,主要有以下幾種方式:
    不返回_source
    GET test_index/_search
    {
    "_source":false }

    返回部分字段

    GET test_index/_search
    {
    "_source":["response","name"] } GET test_index/_search { "_source":{ "includes":"*i*", "excludes":"remote_ip" } }

es分佈式

分佈式特性

  • es支持集羣模式,是一個分佈式系統,其好處主要有兩個:
    • 增大系統容量,如內存、磁盤,使得es集羣能夠支持PB級的數據
    • 提升系統可用性,即便部分節點中止服務,整個集羣依然能夠正常服務
  • es集羣由多個es實例組成
    • 不一樣集羣經過集羣名字來區分,可經過cluster.name進行修改
    • 每一個es實例本質上是一個JVM進程,且有本身的名字,經過node.name進行修改

Master Node

  • 能夠修改cluster state的節點稱爲master節點,一個集羣只能有一個
  • cluster state存儲在每一個節點上,master維護最新版本並同步給其餘節點
  • master節點是經過集羣中全部節點選舉產生的,能夠被選舉的節點稱爲master-eligible節點,相關配置以下:
    • node.master:true

Coordinating Node

  • 處理請求的節點即爲coordinating節點,該節點爲全部節點的默認角色,不能取消
    • 路由請求到正確的節點處理,好比建立索引的請求到master節點

Data Node

  • 存儲數據的節點即爲data節點,默認節點都是data類型,相關配置以下:
    • node.data:true

副本與分片

  • 如何將數據分佈於全部節點上?
    • 引入分片解決問題
  • 分片是es支持PB級數據的基石
    • 分片存儲了部分數據,能夠分部於任意分片上
    • 分片數在索引建立時指定且後續不容許再更改,默認爲5個
    • 分片有主分片和副本分片之分,以實現數據的高可用
    • 副本分片的數據由主分片同步,能夠有多個,從而提升讀取的吞吐量
  • 分片數的設定很重要,須要提早規劃好
    • 太小會致使後續沒法經過增長節點實現水平擴容
    • 過大會致使一個節點上分佈過多過多分片,形成資源浪費,同時會影響查詢性能

故障轉移

  • node1所在機器宕機致使服務終止,此時集羣會如何處理?
    1. node2和node3發現node1沒法響應一段時間後會發起master選舉,好比這裏選擇node2爲master節點。此時因爲主分片P0下線,集羣狀態變爲Red。
    2. node2發現主分片P0未分配,將R0提高爲主分片。此時因爲全部主分片都正常分配,集羣狀態變爲yellow。
    3. node2爲P0和P1生成新的副本,集羣狀態變爲綠色

文檔分佈式存儲

  • document1是如何存儲到分片P1的?選擇P1的依據是什麼?
    • 須要文檔到分片的映射算法
  • 目的
    -使得文檔均與分佈在全部分片上,以充分利用資源
  • 算法
    • shard=hash(routing)% number_of_primary_shards
    • hash算法保證能夠將數據均勻的分散在分片中
    • routing是一個關鍵參數,默認是文檔id,也能夠自行指定
    • number_of_primary_shards 是主分片
  • 該算法與主分片相關,這也是分片一旦肯定後便不能更改的緣由

文檔建立的流程

  1. client向node3發起建立文檔的請求
  2. node3經過routing計算該文檔應該存儲在shard1上,查詢cluster state後確認主分片P1在node2上,而後轉發建立文檔的請求到node2
  3. P1接收並執行建立文檔請求後,將一樣的請求發送到副本分片R1
  4. R1接收並執行建立文檔請求後,通知P1成功的結果
  5. P1接收副本分片結果後,通知node3建立成功
  6. node3返回結果到client

文檔讀取的流程

  1. client向node3發起獲取文檔1的請求
  2. node3經過routing計算該文檔在shard1上,查詢cluster state後獲取shard1的主副分片列表,而後以輪詢的機制獲取一個shard,好比這裏是R1,而後轉發讀取文檔的請求到node1
  3. R1接收並執行讀取文檔請求後,將結果返回node3
  4. node3返回結果給client

批量建立文檔的流程

  1. client向node3發起批量建立文檔的請求
  2. node3經過routing計算全部文檔對應的shard,而後按照主shard分配對應執行的操做,同時發送請求到涉及的主shard,好比這裏3個主shard都須要參與
  3. 主shard接收並執行請求後,將一樣的請求發送到對應的副本shard
  4. 副本shard執行結果後返回結果到主shard,主shard再返回node3
  5. node3整合結果返回client

批量讀取文檔的流程

  1. client向node3發起批量獲取文檔的請求(mget)
  2. node3經過routing計算全部文檔對應的shard,而後以輪詢的機制獲取要參與shard,按照shard構建mget請求,同時發送請求到涉及的shard,好比這裏有兩個shard須要參與
  3. R一、R2返回文檔結果
  4. node3返回結果給client

搜索數據的流程

  1. 客戶端發送請求到一個coordinate node
  2. 協調節點將搜索請求轉發到全部的shard對應的primary shard或replica shard也能夠
  3. query phase:每一個shard將本身的搜索結果(其實就是一些doc id),返回給協調節點,由協調節點進行數據的合併、排序、分頁等操做,產出最終結果
  4. fetch phase:接着由協調節點,根據doc id去各個節點上拉取實際的document數據,最終返回給客戶端

腦裂問題

  • 腦裂問題,英文爲split-brain,是分佈式系統中的經典網絡問題
    • 當node1故障時,node2與node3會從新選舉master,好比node2成爲了新的master,此時會更新cluster state
    • node1本身組成集羣后,也會更新cluster state
  • 同一個集羣有兩個master,並且維護不一樣的cluster state,網絡恢復後沒法選擇正確的master
  • 解決方案爲僅可在選舉master-eligible節點數大於等於quorum時才能夠進行master選舉
    • quorum=master-eligible節點數/2+1,例如三個master-eligible節點時,quorum爲2
    • 設定discovery.zen.minimum_master_nodes爲quorum便可避免腦裂

shard詳解

倒排索引的不可變動

  • 倒排索引一旦生成,不能更改
  • 好處以下:
    • 不用考慮併發寫文件的問題,杜絕了鎖機制帶來的性能問題
    • 因爲文件再也不更改,能夠充分利用文件系統緩存,只需載入一次,只要內存足夠,對該文件的讀取都會從內存讀取,性能高
    • 利於生成緩存數據
    • 利於對文件進行壓縮存儲,節省磁盤和內存存儲空間
  • 壞處爲須要寫入新文檔時,必須從新構建倒排索引文件,而後替換老文件後,新文檔才能被檢索,致使文檔實時性差

文檔搜索實時性

  • 解決方案是新文檔直接生成新的倒排索引文件,查詢的時候同時查詢全部的倒排文件,而後作結果的彙總便可
  • Lucene即是採用了這種方案,它構建的單個倒排索引稱爲segment,合在一塊兒稱爲index,與ES中的index概念不一樣。ES中的一個shard對應一個Lucene index
  • Lucene會有一個專門的文件來記錄全部的segment信息,稱爲commit point

文檔搜索實時性-refresh

  • segment寫入磁盤的過程依然很耗時,能夠藉助文件系統緩存的特性,先將segment在緩存中建立並開放查詢來進一步提高實時性,該過程在es中被稱爲refresh
  • 在refresh以前文檔會先存儲在一個buffer中,refresh時將buffer中的全部文檔清空並生成segment
  • es默認每一秒執行一次refresh,所以文檔的實時性被提升到1秒,這也是es被稱爲近實時的緣由
  • refresh發生的時機主要有以下幾種狀況:
    • 間隔時間達到時,經過index.settings.refresh_interval來設定,默認是一秒
    • index.buffer佔滿時,其大小經過indices.memory.index_buffer_size設置,默認爲jvm heap的10%,全部shard共享
    • flush發生時也會發生refresh

文檔搜索實時性-translog

  • 若是在內存中的segment尚未寫入磁盤前發生了宕機,name其中的文檔就沒法恢復了。
    • es引入translog機制。寫入文檔到buffer時,同時將該操做寫入translog
    • translog文件會即時寫入磁盤(fsync),6.x默認每一個請求都會落盤,能夠修改成每五秒寫一次,這樣風險即是丟失5秒內的數據,相關配置爲index.translog.*
    • es啓動時會檢查translog文件,並從中恢復數據

文檔搜索實時性-flush

  • flush負責將內存中的segment寫入磁盤,主要作以下的工做:
    • 將translog寫入磁盤,造成translog文件
    • 將index buffer清空,其中的文檔生成一個新的segment,至關於一個refresh操做
    • 更新commit point並寫入磁盤
    • 執行fsync操做,將內存中的segment寫入磁盤
    • 刪除舊的translog文件
  • flush發生的時機主要有以下幾種狀況:
    • 間隔時間達到時,默認是30分鐘,5.x以前能夠經過index.translog.flush_threshold_period修改,以後沒法修改
    • translog佔滿時,其大小能夠經過index.translog.flush_threshold_size控制,默認是512mb,每一個index有本身的translog

文檔搜索實時性- 刪除與更新文檔

  • segment一旦生成就不能更改,那麼若是你要刪除文檔該如何操做?
    • Lucene專門維護一個.del的文件,記錄全部已經刪除的文檔,注意.del上記錄的是文檔在Lucene內部的id
    • 在查詢結果返回前會過濾掉.del中的全部文檔
  • 更新文檔如何進行
    • 首先刪除文檔,而後再建立新文檔

Segment Merging

  • 隨着segment的增多,因爲一次查詢的segment數增多,查詢速度會變慢
  • es會定時在後臺進行segment merge的操做,減小segment的數量
  • 經過force_merge api能夠手動強制作segment merge的操做

search 運行機制

  • search 執行的時候實際分兩個步驟運做的
    • Query階段
    • fetch階段
  • Query-Then-Fetch

Query階段

  • node3在接收到用戶的search請求後,會先進行Query階段
  • node3在6個主副分片中隨機選擇三個分片,發送search request
  • 被選中的3個分片會分別進行查詢並排序,返回from+size個文檔Id和排序值
  • node3整合3個分片返回的from+size個文檔Id,根據排序值排序後選取from到from+size的文檔Id

Fetch階段

  • node3根據Query階段獲取的文檔Id列表去對應的shard上獲取文檔詳情數據
    1. node3向相關的分片發送multi_get請求
    2. 3個分片返回文檔詳細數據
    3. node3拼接返回的結果並返回客戶端

相關性算分

  • 相關性算分在shard與shard之間是相互獨立的,也就意味着同一個Term的IDF等值在不一樣shard上是不一樣的。文檔的相關性算分和他所處的shard相關
  • 在文檔數量很少時,會致使相關性算分嚴重不許的狀況發生
  • 解決思路:
    • 一是設置分片數爲1個,從根本上排除問題,在文檔數量很少的時候能夠考慮該方案,好比百萬到千萬級別的文檔數量
    • 二是使用DFS Query-then-Fetch查詢方式
  • DFS Query-then-fetch是在拿到全部文檔後再從新完整的計算一次相關性算分,耗費更多的CPU和內存,執行性能也比較低下,通常不建議使用。使用方式以下:
    GET test_index/_search?search_type=dfs_query_then_fetch

排序

  • es默認會採用相關性算分排序,用戶能夠經過設定sorting參數來自行設定排序規則
    GET test_search_index/_search
    {
    "sort":[ #關鍵字 { "birth":"desc" }, { "_score":"desc" }, { "_doc":"desc" } ] }
    • 按照字符串排序比較特殊,由於es有text和keyword兩種類型,針對text類型排序,以下所示:
      GET test_search_index/_search
      {
      "sort":{ "username":"desc" } } #會產生報錯
  • 針對keyword類型排序,能夠返回預期結果
    GET test_search_index/_search
    {
    "sort":{ "username.keyword":"desc" } }
  • 排序的過程實質是對字段原始內容排序的過程,這個過程當中倒排索引沒法發揮做用(term->document),須要用到正排索引,也就是經過文檔id和字段能夠快速獲得字段原始內容。
  • es對此提供了兩種實現方式:
    • fielddata默認禁用
    • doc values默認啓用,除了text類型
文檔ID 字段值
1 100
2 89
3 129

fielddata vs DocValues

對比 fielddata DocValues
建立時機 搜索時即時建立 索引時建立,與倒排索引建立時機一致
建立位置 JVM Heap 磁盤
優勢 不會佔用額外的磁盤空間 不會佔用Heap內存
缺點 文檔過多時,即時建立會花過多時間,佔用過多Heap內存 減慢索引的速度,佔用額外的磁盤資源

Fielddata

  • Fielddata默認是關閉的,能夠經過以下api開啓:
    • 此時字符串是按照分詞後的term排序,每每結果很難符合預期
    • 通常是在對分詞作聚合分析的時候開啓
      PUT test_search_index/_mapping/doc
      {
      "properties":{ "username":{ "type":"text", "fileddata":"true" } } }

Doc Values

  • Doc Values默認是啓用的,能夠在建立索引的時候關閉:
    • 若是後面要再開啓doc values,須要作reindex操做
      PUT test_doc_value/
      {
      "mappings":{ "doc":{ "properties": } } }

docvalue_fields

  • 能夠經過該字段獲取fielddata或者doc values中存儲的內容
    GET test_search_index/_search
    {
    "docvalue_fields":[ "username", "username.keyword", "age" ] }

分頁與遍歷

From/Size

  • 最經常使用的分頁方案
    • from指明開始位置
    • size指明獲取總數
  • 深度分頁是一個經典問題:在數據分片存儲的狀況下如何獲取前1000個文檔?
    • 獲取從990-1000的文檔時,會在每一個分片上都先獲取1000個文檔,而後再由coordinating node局和全部分片的結果後再排序選取前1000個文檔
    • 頁數越深,處理文檔越多,佔用內存越多,耗時越長。儘可能避免深度分頁,es經過index.max_result_window限定最多到10000條數據
      total_page=(total+page_size-1)/page_size
      total爲文檔總數

Scroll

  • 遍歷文檔集的api,以快照的方式來避免深度分頁的問題
    • 不能用來作實時搜索,由於數據不是實時的,是以快照的方式作的
    • 儘可能不要使用複雜的sort條件,使用_doc最高效
    • 使用稍嫌複雜
  • 第一步須要發起1個scroll search,以下所示:
    • es在收到該請求後會根據查詢條件建立文檔id合計的快照
      GET test_search_index/_search?scrol=5m  #該scroll快照有效時間 { "size":1 #指明每次scroll返回的文檔數 }
  • 第二步調用scroll search的api,獲取文檔集合,以下所示:
    • 不斷迭代調用直到返回hits.hits數組爲空時中止
      post _search/scroll
      {
      "scroll":"5m", #指明有效時間 "scroll_id":"..." #上一步返回的id }
  • 過多的scroll調用會佔用大量內存,能夠經過clear api刪除過多的scroll快照:
    DELETE /_search/scroll { "scroll_id":[ "DXFZAD....", "DESGRHRH..." ] } DELETE /_search/scroll/_all

search_after

  • 避免深度分頁的性能問題,提供實時的下一頁文檔獲取功能
    • 缺點是不能使用from參數,即不能指定頁數
    • 只能下一頁,不能上一頁
    • 使用簡單
  • 第一步爲正常的搜索,但要指定sort值,並保證值惟一
  • 第二步爲使用上一步最後一個文檔的sort值進行查詢
    GET test_search_index/_search
    {
    "size":1, "sort":{ "age":"desc", "_id":"desc" } } GET test_search_index/_search { "size":1, "search_after":[28,"2"], "sort":{ "age":"desc", "_id":"desc" } }
  • search_after如何避免深度分頁問題
    • 經過惟一排序值定位將每次要處理的文檔數都控制在size內

應用場景

類型 場景
from/size 須要實時獲取頂部的部分文檔,且須要自由翻頁
scroll 須要所有文檔,如導出全部數據的功能
search_after 須要所有文檔,不須要自由翻頁

聚合分析

  • 聚合分析,英文爲aggregation,是es除搜索功能外提供的針對es數據作統計分析的功能
    • 功能豐富,提供bucket、metric。pipeline等多種分析方式,能夠知足大部分的分析需求
    • 實時性高,全部的計算都是即時返回的,而Hadoop等大數據系統通常都是T+1級別的(today+1,次日)
  • 聚合分析做爲search的一部分,api以下所示:
    GET test_search_index/_search
    {
    "size":0, "aggs":{ #關鍵詞,與query同級 "<aggregation_name>":{#定義聚合名稱 "<aggregation_type>":{#定義聚合類型 <aggregation_body> }, [,"aggs":{[<sub_aggregation>]+}]? #子查詢 } [,"<aggregation_name_2>":{...}]* #能夠包含多個聚合分析 } }

聚合分析分類

  • 爲了便於理解,es將聚合分析主要分爲以下四類
    • Bucket,分桶類型,相似sql中的group by語法
    • Metric,指標分析模型,如計算最大值、最小值、平均值等等
    • pipline,管道分析類型,基於上一級的聚合分析結果進行再分析
    • Matrix,矩陣分析類型

Metric聚合分析

  • 主要分以下兩類:
    • 單值分析,只輸出過一個分析結果
      • min,max,avg,sum
      • cardinality
    • 多值分析,輸出多個分析結果
      • stats,extended stats
      • percentile,percentile rank
      • top hits
  • 返回數值類字段的最小值
    GET test_search_index/_search
    {
    "size":0, #不須要返回文檔列表 "aggs":{ "min_age":{ "min":{ #關鍵詞 "field":"age" } } } }
  • 返回數值類字段的最大值
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "max_age":{ "max":{ "field":"age" } } } }
  • cardinality,意爲集合的勢,或者基數,是指不一樣數值的個數,相似sql中的distinct count概念
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "count_of_job":{ "cardinality":{ #關鍵詞 "field":"job.keyword" } } } }
  • 返回一系列數值類型的統計值,包含min、max、avg、sum和count
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "stats_age":{ "stats":{ "field":"age" } } } }
  • 對stats的擴展,包含了更多的統計數據,如方差、標準差等
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "stats_age":{ "extended_status":{ "field":"age" } } } }
  • 百分位數統計percentile
    GET  test_search_index/_search
    {
    "size":0, "aggs":{ "per_age":{ "percentiles":{ #關鍵詞 "field":"salary" } } } }
  • top hits通常用於分桶後獲取該桶內最匹配的頂部文檔列表,即詳情數據
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "jobs":{ "terms":{ "field":"job.keyword", "size":10 }, "aggs":{ "top_employee":{ "top_hits":{ "size":10, "sort":[ { "age":{ "order":"desc" } } ] } } } } } }

bucket聚合分析

  • Bucket,意爲桶,即按照必定的規則將文檔分配到不一樣的桶中,達到分類分析的目的
  • 按照Bucket的分桶策略,常見的Bucket聚合分析以下:
    • Terms
    • Range
    • Date Range
    • Histogram
    • Date Histogram

Terms

  • 該分桶策略最簡單,直接按照term來分桶,若是是text類型,則按照分詞後的結果分桶
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "jobs":{ "terms":{ #關鍵詞 "field":"job.keyword",#指明term字段 "size":5 #指定返回數目 } } } }

Range

  • 經過指定數值的範圍來設定分桶規則
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "salary_range":{ "range":{ #關鍵詞 "field":"salary",\ "ranges":[ #指定每一個range的範圍 { "to":1000 }, { "from":1000, "to":2000 } ] } } } }

Date Range

  • 經過指定日期的範圍來設定分桶規則
GET test_search_index/_search
{
  "size":0, "aggs":{ "date_range":{ "range":{ #關鍵詞 "field":"birth", "format":"yyyy",#指定返回結果的日期格式 "ranges":[ { "from":"1980",#指定日期,可使用date math "to":"1990" }, { "from":"1990", "to":"2000" } ] } } } }

historgram

  • 直方圖,以固定間隔的策略來分隔數據
GET test_search_index/_search
{
    "size":0, "aggs":{ "salary_hist":{ "histogram":{ #關鍵詞 "field":"salary", "interval":5000, #指定間隔大小 "extended_bounds":{ #指定數據範圍 "min":0, "max":40000 } } } } }

Date Historgram

  • 針對日期的直方圖或者柱形圖,是時序分析中經常使用的聚合分析類型
    Get test_search_index/_search
    {
    "size":0, "aggs":{ "by_year":{ "date_historgram":{ #關鍵詞 "field":"birth", "interval":"year", #指定間隔大小 "format":"yyyy" #指定日期格式化 } } } }

bucket和metric聚合分析

bucket+metric聚合分析

  • bucket聚合分析容許經過添加子分析來進一步進行分析,該子分析能夠是bucket也能夠是metric。這也使得es的聚合分析能力變得異常強大
  • 分桶後再分桶
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "jobs":{ "terms":{ "field":"job.keyword", "size":10 }, "aggs" } } }
  • 分桶後進行數據分析
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "jobs":{ #第一層聚合 "terms":{ "field":"job.keyword", "size":10 }, "aggs":{ "salary":{ #第二層聚合 "stats":{ "field":"salary" } } } } } }

pipeline聚合分析

  • 針對聚合分析的結果再次進行聚合分析,並且支持鏈式調用,能夠回答以下問題:
    • 訂單月平均銷售額是多少?
      POST order/_search
      {
      "size":0, "aggs":{ "sales_per_month":{ "date_histogram":{ "field":"date", "interval":"month" }, "aggs":{ "sales":{ "sum":{ "field":"price" } } } }, "avg_monthly_sales":{ "avg_bucket":{ "bucket_path":"sales_per_month>sales" } } } }
  • Pipeline的分析結果會輸出到原結果中,根據輸出位置的不一樣,分爲如下兩類:
    • Parent 結果內嵌到現有的聚合分析結果中
      • Derivative
      • Moving Average
      • Cumulative Sum
    • Sibling 結果與現有聚合分析結果同級
      • Max/Min/Avg/Sum Bucket
      • Stats/Extended Stats Bucket
      • Percentiles Bucket

Pipeline 聚合分析Sibling-Min Bucket

  • 找出全部Bucket中值最小的的Bucket名稱和值
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "jobs":{ "terms":{ "field":"job.keyword", "size":10 }, "aggs":{ "avg_salary":{ "avg":{ "field":"salary" } } } }, "min_salary_by_job":{ "min_bucket":{ #關鍵詞 "buckets_path":"jobs>avg_salary" } } } }

Pipeline聚合分析 Sibling-Max Bucket

  • 找出全部Bucket中值最大的Bucket名稱和值
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "jobs":{ "terms":{ "field":"job.keyword", "size":10 }, "aggs":{ "avg_salary":{ "avg":{ "field":"salary" } } } }, "max_salary_by_job":{ "max_bucket":{ "buckets_path":"jobs>avg_salary" } } } }

Pipeline 聚合分析Parent-Derivative

  • 計算Bucket值的導數
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "birth":{ "date_histogram":{ "field":"birth", "interval":"year", "min_doc_count":0 }, "aggs":{ "avg_salary":{ "avg":{ "field":"salary" } }, "derivative_avg_salary":{ "derivative":{ #關鍵詞 "bucket_path":"avg_salary" } } } } } }

Pipeline 聚合分析Parent-Moving Average

  • 計算Bucket值的移動平均值
    GET test_search_index/_search
    {
    "size":, "aggs":{ "birth":{ "date_histogram":{ "field":"birth", "interval":"year", "min_doc_count":0 }, "aggs":{ "avg_salary":{ "avg":{ "field":"salary" } }, "mavg_salary":{ "moving_avg":{ #關鍵詞 "buckets_path":"avg_salary" } } } } } }

做用範圍

  • es聚合分析默認做用範圍是query的結果集,能夠經過以下的方式改變其做用範圍:
    • filter
    • post_filter
    • global
      GET test_search_index/_search
      {
      "size":0, "query":{ "match":{#aggs做用域該query的結果集 "username":"alfred" } }, "aggs":{ "jobs":{ "terms":{ "field":"job.keyword", "size":10 } } } }

做用範圍-filter

  • 爲某個聚合分析設定過濾條件,從而在不更改總體語句的狀況下修改了做用範圍
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "jobs_salary_small":{ "filter":{ "range":{ "salary":{ "to":10000 } } }, "aggs":{ "jobs":{ "terms":{ "field":"job.keyword" } } } }, "jobs":{ "terms":{ "field":"job.keyword" } } } }

做用範圍 - post-filter

  • 做用於文檔過濾但在聚合分析後生效
    GET test_search_index/_search
    {
    "aggs":{ "jobs":{ "terms":{ "field":"job.keyword" } } }, "post_filter":{#過濾條件 "match":{ "job.keyword":"java engineer" } } }

做用範圍-global

  • 無視query過濾條件,基於所有文檔進行分析
    GET test_search_index/_search
    {
    "query":{ "match":{ "job.keyword":"java engineer" } }, "aggs":{ "java_avg_salary":{ "avg":{ "field":"salary" } }, "all":{ "global":{}, "aggs":{ "avg_salary":{#過濾條件 "avg":{ "field":"salary" } } } } } }

排序

  • 可使用自帶的關鍵數據進行排序,好比:
    • _count文檔數
    • _key按照key值排序
      GET test_search_index/_search
      {
      "size":0, "aggs":{ "jobs":{ "terms":{ "field":"job.keyword", "size":10, "order":[ { "count":"asc" }, { "_key":"desc" } ] } } } }

數據精準度問題

terms不許確的緣由

  • 數據分散在多shard上,coordinating node沒法得悉數據全貌

terms不許確的解決方法

  • 設置shard數爲1,消除數據分散的問題,但沒法承載大數據量
  • 合理設置shard_size的大小,即每次從shard上額外多獲取數據,以提高準確度
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "jobs":{ "terms":{ "field":"job.keyword", "size":1, "shard_size":10 } } } }

shard_size大小的設定方法

  • terms聚合返回結果中有以下兩個統計值:
    • doc_count_error_upper_bound被遺漏的term可能的最大值
    • sum_other_doc_count返回結果bucket的term外其餘term的文檔總數
  • 設定show_term_doc_count_error能夠查看每一個bucket誤算的最大值
    GET test_search_index/_search
    {
    "size":0, "aggs":{ "jobs":{ "terms":{ "field":"job.keyword", "size":2, "show_term_doc_count_error":true } } } }
  • shard_size默認大小以下:
    • shard_size=(sizex1.5)+10
  • 經過調整shard_size的大小下降doc_count_error_upper_bound來提高準確度
    • 增大了總體的計算量,從而下降了響應時間

近似統計算法

  • 追求海量數據的準確度,用做離線計算,失去了實時性
  • 追求海量數據的實時性,用做近似統計算法,失去了數據的準確度
  • 追求數據的準確度和實時性,只是做爲有限的數據計算,分析的數據只是片面的
  • 在es的聚合分析中,cardinality和percentile分析使用的是近似統計算法
    • 結果是近似準確的,但不必定精準
    • 能夠經過參數的調整使其結果精準,但同時也意味着更多的計算時間和更大的性能消耗

數據建模

  • 英文爲data modeling,爲建立數據模型的過程
  • 數據模型(Data Model)
    • 對現實世界進行抽象描述的一種工具和方法
    • 經過抽象的實體及實體之間聯繫的形式去描述業務規則,從而實現對現實世界的映射

數據建模的過程

  • 概念模型
    • 肯定系統的核心需求和範圍邊界,設計實體和實體間的關係
  • 邏輯模型
    • 進一步梳理業務需求,肯定每一個實體的屬性、關係和約束等
  • 物理模型
    • 結合具體的數據庫產品,在知足業務讀寫性能等需求的前提下肯定最終的定義
    • Mysql、MongoDB、elasticsearch等
    • 第三範式

ES中的數據建模

  • ES是基於Lucene以倒排索引爲基礎實現的存儲體系,不遵循關係型數據庫中的範式約定

Mapping字段的相關配置

  • enabled
    • true | false
    • 僅存儲,不作搜索或聚合分析
  • index
    • true | false
    • 是否構建倒排索引
  • index_options
    • docs | freqs |positions | offsets
    • 存儲倒排索引的哪些信息
  • norms
    • true | false
    • 是否存儲歸一化相關參數,若是字段僅用於過濾和聚合分析,可關閉
  • doc_values
    • true | false
    • c是否啓用doc_values,用於排序和聚合分析
  • field_data
    • false | true
    • 是否爲text類型啓用fielddata,實現排序和聚合分析
  • store
    • false | true
    • 是否存儲該字段值
  • coerce
    • true |false
    • 是否開啓自動數據類型轉換功能,好比字符串轉爲數字、浮點轉爲×××等
  • multifields多字段
    • 靈活使用多字段特性來解決多樣的業務需求
  • dynamic
    • true | false |strict
    • 控制mapping自動更新
  • date_detection
    • true | false
    • 是否自動識別日期類型

Mapping字段屬性的設定流程

  • 是何種類型
  • 是否須要檢索
  • 是否須要排序和聚合分析
  • 是否須要另行存儲

是何種類型

  • 字符串類型
    • 須要分詞則設定爲text類型,不然設定爲keyword類型
  • 枚舉類型
    • 基於性能考慮將其設定爲keyword類型,即使該數據爲整性
  • 數值類型
    • 儘可能選擇貼近的類型,好比byte便可表示全部數值時,即選用byte,不要用long
  • 其餘類型
    • 好比布爾類型、日期、地理位置數據等

是否須要檢索

  • 徹底不須要檢索、排序、聚合分析的字段
    • enabled設置爲false
  • 不須要檢索的字段
    • index設置爲false
  • 須要檢索的字段,能夠經過以下配置設定須要的存儲粒度
    • index_options結合須要設定
    • norms不須要歸一化數據時關閉便可

是否須要排序和聚合分析

  • 不須要排序或者聚合分析功能
    • doc_values設定爲false
    • fielddata設定爲false

是否須要另行存儲

  • 是否須要專門存儲當前字段的數據?
    • store設定爲true,便可存儲該字段的原始內容(與_source中的不相關)
    • 通常結合_source的enabled設定爲false時使用

關聯關係處理

  • ES不擅長處理關係型數據庫中的關聯關係,好比文章表blog與評論表comment之間經過blog_id關聯,在ES中能夠經過以下兩種手段變相解決
    • Nested Object
    • Parent/Child
  • ES 還提供了相似關係數據庫中join的實現方式,使用join數據類型實現
    PUT blog_index_parrent_child
    {
    "mappings":{ "doc":{ "properties":{ "join":{ "type":"join",#指明類型 "relations":{#指明父子關係 "blog":"comment" } } } } } }

關聯關係處理之Parent/Child

#建立父文檔 PUT blog_index_parent_child/doc/1 { "title":"blog", "join":"blog" } #建立子文檔 PUT blog_index_parent_child/doc/comment-1?routing=1 #指定routing值,確保父子文檔在一個分片上,通常使用父文檔id { "comment":"comment world", "join":{ "name":"comment", #指明子類型 "parent":1 #指明父文檔id } }
  • 常見query語法包括以下幾種:
    • parent_id返回某父文檔的子文檔
      GET blog_index_parent/_search
      {
      "query":{ "parent_id":{#關鍵詞 "type":"comment",#指明子文檔類型 "id":"2" #指明父文檔id } } }
    • has_child返回包含某子文檔的父文檔
      GET blog_index_parent/_search
      {
      "query":{ "has_child":{#關鍵詞 "type":"comment",#指明子文檔類型 "query":{ "match":{ "comment":"world" } } } } }
    • has_parent返回某父文檔的子文檔
      GET blog_index_parent/_search
      {
      "query":{ "has_parent":{ "has_parent":"blog",#指定父文檔類型 "query":{#指明父文檔查詢條件 "match":{ "title":"blog" } } } } }

Nested Object vs Parent/Child

對比 Nested Object Parent/Child
優勢 文檔存儲在一塊兒,所以讀取性能高 父子文檔能夠獨立更新,互不影響
缺點 更新父或子文檔時須要更新整個文檔 爲了維護join的關係,須要佔用部份內存,讀取性能較差
場景 子文檔偶爾更新,查詢頻繁 子文檔更新頻繁

Reindex

  • 指重建全部數據的過程,通常發生在以下狀況:
    • mapping設置變動,好比字段類型變化、分詞器字典更新等
    • index設置變動,好比分片數更改
    • 遷移數據
  • ES提供了現成的API用於完成該工做
    • _update_by_query在現有索引上重建
    • _reindex在其餘索引上重建

Reindex- _update_by_query

POST blog_index/_update_by_query?conflicts=proceed #若是遇到版本衝突,覆蓋並繼續執行 POST blog_index/_update_by_query { "script":{ #更新文檔的字段值 "source":"ctx._source.likes++", "lang":"painless" }, "query":{ #能夠更新部分文檔 "term":{ "user":"tom" } } }

Reindex - _reindex

POST _reindex
{
  "source":{ "index":"blog_index" }, "dest":{ "index":"blog_new_index" } }
POST _reindex
{
  "conflicts":"proceed",#衝突時覆蓋並繼續 "source":{ "index":"blog_index", "query":{ "term":{ "user":"tom" } } }, "dest":{ "index":"blog_new_index" } } 

Reindex - Task

  • 數據重建的時間受源索引文檔規模的影響,當規模越大時,所需時間越多,此時須要經過設定url參數wait_for_completion爲false來異步執行,ES以task來描述此類執行任務
  • ES提供了Task API來查看任務的執行進度和相關數據
    POST blog_index/_update_by_query?wait_for_completion=false #獲取task ID GET _tasks/_qkdskglrfodsm(task ID)

數據建模的建議

數據模型版本管理

  • 對Mapping進行版本管理
    • 包含在代碼或者以專門的文件進行管理,添加好註釋,並加入Git等版本管理倉庫中,方便回顧
    • 爲每一個增長一個metadata字段,在其中維護一些文檔相關的元數據,方便對數據進行管理

防止字段過多

  • 通常字段過多的緣由是因爲沒有高質量的數據建模致使的,好比dynamic設置爲true
  • 考慮拆分多個索引來解決問題
  • 字段過多主要由如下壞處:
    • 難於維護,當字段成百上千時,基本很難有人能明確知道每一個字段的含義
    • mapping的信息存儲在cluster state裏面,過多的字段會致使mapping過大,最終致使更新變慢
  • 經過設置index.mapping.total_fields.limit能夠限定索引中最大字段數,默認是1000
  • 能夠經過key/value的方式解決字段過多的問題,但並不完美
    Key/Value方式詳解
  • 雖然經過這種方式能夠極大地減小field數目,但也有一些明顯的壞處
    • query語句複雜度飆升,且有一些可能沒法實現,好比聚合分析相關的
    • 不利於在kibana中作可視化分析

集羣調優建議

生產環境部署建議

  • 遵守官方建議設置全部的系統參數
  • 參見文檔「setup Elasticsearch -> Important System Configuration"

    ES設置儘可能簡潔

  • elasticsearch.yml中儘可能只寫必備的參數,其餘能夠經過api動態設置的參數都經過api來設定
  • 隨着ES版本的升級,不少網絡流傳的配置參數已經再也不支持,所以不要隨便複製別人的集羣配置參數

    elasticsearch.yml中建議設定的基本參數

  • cluster.name
  • node.name
  • node.master/node.data/node.ingest
  • network.host建議顯示指定爲內網ip,不要偷懶直接設爲0.0.0.0
  • discovery.zen.ping.unicast.hosts 設定集羣其餘節點地址
  • discovery.zen.minimum_master_nodes通常設定爲2
  • path.data/path.log
  • 動態設定的參數有transient和persistent兩種設置,前者在集羣重啓後會丟失,後者不會,但兩種設定都會覆蓋elasticsearch.yml中的配置
    PUT /_cluster/settings
    {
    "persistent":{ "discovery.zen.minimum_master_nodes":2 }, "transient":{ "indices.store.throttle.max_bytes_per_sec":"50mb" } }

jvm內存設定

  • 不要超過31GB
  • 預留一半內存給操做系統,用來作文件緩存
  • 具體大小根據該node要存儲的數據量來估算,爲了保證性能,在內存和數據量間有一個建議的比例
    • 搜索類項目的比例建議在1:16之內
    • 日誌類項目的比例建議在1:48~1:96
  • 假設總數據量大小爲1TB,3個node,1個副本,那麼每一個node要存儲的數據量爲2TB/3=666TB,即700GB左右,作20%的預留空間,每一個node要存儲大約850GB的數據
    • 若是是搜索類項目,每一個node內存大小爲850GB/16=53GB,大於31GB。31*16=496,即每一個node最多存儲496GB數據,因此須要至少5個node
    • 若是是日誌類型項目,每一個node內存大小爲850GB/48=18GB,所以3個節點足夠

寫數據優化

寫數據過程

  • refresh
  • translog
  • flush

ES寫數據 - refresh

  • segment寫入磁盤的過程依然很耗時,能夠藉助文件系統緩存的特性,先將segment在緩存中建立並開放查詢來進一步提高實時性,該過程在es中稱爲refresh
  • 在refresh以前文檔會先存儲在一個buffer中,refresh時將buffer中的全部文檔清空並生成segment
  • es默認每一秒執行一次refresh,所以文檔的實時性被提升到一秒,這也是es被稱爲近實時的緣由

ES寫數據 - translog

  • 若是在內存中的segment尚未寫入磁盤前發生了宕機,那麼其中的文檔就沒法恢復了,如何解決這個問題?
    • es引入translog機制。寫文檔到buffer時,同時將該操做寫入translog
    • translog文件會即時寫入磁盤,6.x默認每一個請求都會落盤,能夠修改成每五秒寫一次,這樣風險即是丟失5秒內的數據,相關配置爲index.translog.*
    • es啓動時會檢查translog文件,並從中恢復數據

ES寫數據 - flush

  • flush負責將內存中的segment寫入磁盤,主要作以下的工做:
    • 將translog寫入磁盤
    • 將index buffer清空,其中的文檔生成一個新的segment,至關於一個refresh操做
    • 更新commit point並寫入磁盤
    • 執行fsync操做,將內存中的segment寫入磁盤
    • 刪除舊的translog文件

寫性能優化

  • 目標是增大寫吞吐量-EPS(Events Per Second)越高越好
  • 優化方案
    • 客戶端:多線程寫,批量寫
    • ES:在高質量數據建模的前提下,主要是在refresh、translog和flush之間作文章

寫性能優化 - refresh

  • 目標爲下降refresh的頻率
    • 增大refresh_interval,下降實時性,以增大一次refresh的文檔數,默認是1秒,設置爲-1直接禁止自動refresh
    • 增大index buffer size,參數爲indices.memory.index_buffer_size(靜態參數,須要設定在配置文件中),默認爲10%

寫性能優化 - translog

  • 目標是下降translog寫磁盤的頻率,從而提升寫效率,但會下降容災能力
    • index.tranlog.durability設置爲async,index.translog.sync_interval設置須要的大小,好比120s,那麼translog會改成每120s寫一次磁盤
    • index.translog.flush_threshold_size默認爲512mb,即translog超過該大小時會觸發一次flush,那麼調大該大小能夠避免flush的發生

寫性能優化 - flush

  • 目標爲下降flush的次數,在6.x可優化的點很少,多爲es自動完成

寫性能優化 - 其餘

  • 副本設置爲0,寫入完畢再增長
  • 合理地設計shard數,並保證shard均勻的分配在全部node上,充分利用全部node的資源
    • index.routing.allocation.total_shards_per_node限定每一個索引在每一個node上可分配的總主副分片數
    • 5個node,某索引有10個分片,1個副本,上述值應該設置爲多少?
      • (10+10)/5=4
      • 實際要設置爲5個,防止在某個node下線時,分片遷移失敗的問題

讀性能優化

  • 讀性能主要受如下幾方面影響:
    • 數據模型是否符合業務模型
    • 數據規模是否過大
    • 索引配置是否優化
    • 查詢語句是否優化

讀性能優化 - 數據建模

  • 高質量的數據建模是優化的基礎
    • 將須要經過script腳本動態計算的值提早算好做爲字段存到文檔中
    • 儘可能使得數據模型貼近業務模型
  • 根據不一樣的數據規模設定不一樣的sla
    • 上萬條數據與上千萬條數據性能確定存在差別

讀性能優化 - 索引配置調優

  • 索引配置優化主要包括以下:
    • 根據數據規模設置合理的主分片數,能夠經過測試獲得最適合的分片數
    • 設置合理的副本數目,不是越多越好

讀性能優化 - 查詢語句調優

  • 查詢語句調優主要有如下幾種常見手段:
    • 儘可能使用filter上下文,減小算分的場景,因爲filter有緩存機制,能夠極大提高查詢性能
    • 儘可能不使用script進行字段計算或者算分排序等
    • 結合profile、explain api分析慢查詢語句的癥結所在,而後再去優化數據模型

如何設定shard數

  • ES的性能基本是線性擴展的,所以咱們只要測出一個shard的性能指標,而後根據實際性能需求就算出須要的shard數。好比單shard寫入eps是10000,而線上eps需求是50000,那麼你須要五個shard(實際還要考慮副本的狀況)
  • 測試一個shard的流程以下:
    • 搭建與生產環境相同配置的單節點集羣
    • 設定一個單分片零副本的索引
    • 寫入實際生產數據進行測試,獲取寫性能指標
    • 針對數據進行查詢請求,獲取讀性能指標
  • 壓測工具能夠採用esrally
  • 壓測的流程仍是比較複雜,能夠根據經驗來設定。若是是搜索引擎場景,單shard大小不要超過15GB,若是是日誌場景,單shard大小不要超過50GB(shard越大,查詢性能越低)
  • 此時只要估算出你索引的總數據大小,而後再除以上面的單shard大小也能夠獲得分片數