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以下:
文檔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建立文檔api以下:
- 查詢文檔
- 指定要查詢的文檔id
GET /test_index/doc/1
- 搜索全部文檔,用到_search,以下:
- 指定要查詢的文檔id
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"}}
- endpoint爲_bulk,以下:
批量查詢文檔API
- es容許一次查詢多個文檔
- endpoint爲_mget,以下:
GET /_mget { "doc":[ { "_index":"test_index", "_type":"doc", "_id":"1" }, { "_index":"test_index", "_type":"doc", "_id":"2" } ] }
- endpoint爲_mget,以下:
索引
正排索引與倒排索引
- 正排索引
- 文檔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
- 針對tokenizer處理的單詞進行再加工,好比轉小寫、刪除或新增等處理
- es提供了一個測試分詞的api接口,方便驗證分詞效果,endpoint是_analyze
- 能夠直接指定analyzer進行測試
- 能夠直接指定索引中的字段進行測試
- 能夠自定義分詞器進行測試
- Character Filters
- 直接指定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...
中文分詞
- 難點
- 中文分詞指的是將一個漢字序列切分紅一個一個單獨的詞。在英文中,單詞之間是以空格做爲天然分界符,漢語中沒有一個形式上的分界符
- 上下文不一樣,分詞結果迥異,好比交叉歧義問題,好比下面兩種分詞都合理
- 乒乓球拍/賣/完了
- 乒乓球/拍賣/完了
- 經常使用分詞系統
- IK
- 實現中英文單詞的切分,支持ik_smart、ik_maxword等模式
- 可自定義詞庫,支持熱更新分詞詞典
- https://github.com/medcl/elasticsearch-analysis-ik
- jieba
- python中最流行的分詞系統,支持分詞和詞性標註
- 支持繁體分詞、自定義詞典、並行分詞等
- https://github.com/singlee/elasticsearch-jieba-plugin
- IK
- 基於天然語言處理的分詞系統
- Hanlp
- 由一系列模型與算法組成的Java工具包,目標是普及天然語言處理在生產環境中的應用
- https://github.com/hankcs/HanLP
- THULAC
- THU Lexical Analyzer for Chinese,由清華大學天然緣由處理與社會人文計算實驗室研製推出的一套中文詞法分析工具包,具備中文分詞和詞性標註功能
- https://github.com/microbun/elasticsearch-thulac-plugin
- Hanlp
自定義分詞
- 當自帶的分詞沒法知足需求時,能夠自定義分詞
- 經過自定義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" #指定分詞器 } } } } }
- 不指定分詞時,默認使用standard
-
查詢時分詞的指定方式有以下幾種:node
- 查詢時經過analyzer指定分詞器
POST test_index/_search { "query":{ "match":{ "message":{ "query":"hello", "analyzer":"standard" } } } }
- 查詢時經過analyzer指定分詞器
- 經過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" } } } } }
- 控制當前字段是否索引,默認爲true,即記錄索引,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" } } } } }
- 當字段遇到null值時的處理策略,默認爲null,即空值,此時es會忽略該值。能夠經過設定該值設定字段的默認值
數據類型
- 核心數據類型
- 字符串: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" } } } } } } } }
- 容許對同一個字段採用不一樣的配置,好比分詞,常見例子如對人名實現拼音搜素,只須要在人名中新增一個子字段爲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 } } }
- numeric_detection能夠開啓字符串中數字的自動識別,以下所示:
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的操做步驟以下:
- 寫入一條文檔到es的臨時索引中,獲取es自動生成的mapping
- 修改步驟1獲得的mapping,自定義相關配置
- 使用步驟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
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
- AND(&&),OR(||),NOT(!)
- 範圍查詢,支持數值和日期
- 區間寫法,閉區間用[],開區間用{}
- 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
- 通配符匹配執行效率低,且佔用較多內存,不建議使用
- 如無特殊需求,不要將?/*放在最前面
- ?表明1個字符,*表明0或多個字符
- 正則表達式匹配
- 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查詢實現自定義部分
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中的字段,主要有以下幾種方式:
不返回_sourceGET 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所在機器宕機致使服務終止,此時集羣會如何處理?
- node2和node3發現node1沒法響應一段時間後會發起master選舉,好比這裏選擇node2爲master節點。此時因爲主分片P0下線,集羣狀態變爲Red。
- node2發現主分片P0未分配,將R0提高爲主分片。此時因爲全部主分片都正常分配,集羣狀態變爲yellow。
- node2爲P0和P1生成新的副本,集羣狀態變爲綠色
文檔分佈式存儲
- document1是如何存儲到分片P1的?選擇P1的依據是什麼?
- 須要文檔到分片的映射算法
- 目的
-使得文檔均與分佈在全部分片上,以充分利用資源 - 算法
- shard=hash(routing)% number_of_primary_shards
- hash算法保證能夠將數據均勻的分散在分片中
- routing是一個關鍵參數,默認是文檔id,也能夠自行指定
- number_of_primary_shards 是主分片
- 該算法與主分片相關,這也是分片一旦肯定後便不能更改的緣由
文檔建立的流程
- client向node3發起建立文檔的請求
- node3經過routing計算該文檔應該存儲在shard1上,查詢cluster state後確認主分片P1在node2上,而後轉發建立文檔的請求到node2
- P1接收並執行建立文檔請求後,將一樣的請求發送到副本分片R1
- R1接收並執行建立文檔請求後,通知P1成功的結果
- P1接收副本分片結果後,通知node3建立成功
- node3返回結果到client
文檔讀取的流程
- client向node3發起獲取文檔1的請求
- node3經過routing計算該文檔在shard1上,查詢cluster state後獲取shard1的主副分片列表,而後以輪詢的機制獲取一個shard,好比這裏是R1,而後轉發讀取文檔的請求到node1
- R1接收並執行讀取文檔請求後,將結果返回node3
- node3返回結果給client
批量建立文檔的流程
- client向node3發起批量建立文檔的請求
- node3經過routing計算全部文檔對應的shard,而後按照主shard分配對應執行的操做,同時發送請求到涉及的主shard,好比這裏3個主shard都須要參與
- 主shard接收並執行請求後,將一樣的請求發送到對應的副本shard
- 副本shard執行結果後返回結果到主shard,主shard再返回node3
- node3整合結果返回client
批量讀取文檔的流程
- client向node3發起批量獲取文檔的請求(mget)
- node3經過routing計算全部文檔對應的shard,而後以輪詢的機制獲取要參與shard,按照shard構建mget請求,同時發送請求到涉及的shard,好比這裏有兩個shard須要參與
- R一、R2返回文檔結果
- node3返回結果給client
搜索數據的流程
- 客戶端發送請求到一個coordinate node
- 協調節點將搜索請求轉發到全部的shard對應的primary shard或replica shard也能夠
- query phase:每一個shard將本身的搜索結果(其實就是一些doc id),返回給協調節點,由協調節點進行數據的合併、排序、分頁等操做,產出最終結果
- 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上獲取文檔詳情數據
- node3向相關的分片發送multi_get請求
- 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" } } #會產生報錯
- 按照字符串排序比較特殊,由於es有text和keyword兩種類型,針對text類型排序,以下所示:
- 針對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": } } }
- 若是後面要再開啓doc values,須要作reindex操做
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返回的文檔數 }
- es在收到該請求後會根據查詢條件建立文檔id合計的快照
- 第二步調用scroll search的api,獲取文檔集合,以下所示:
- 不斷迭代調用直到返回hits.hits數組爲空時中止
post _search/scroll { "scroll":"5m", #指明有效時間 "scroll_id":"..." #上一步返回的id }
- 不斷迭代調用直到返回hits.hits數組爲空時中止
- 過多的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
- Parent 結果內嵌到現有的聚合分析結果中
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" } } } } }
- parent_id返回某父文檔的子文檔
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大小也能夠獲得分片數