1. 程式人生 > >ElasticSearch---文件的增刪改

ElasticSearch---文件的增刪改

假如我們以員工物件為例,我們要做的是儲存員工資料,每個文件代表一個員工,在es中儲存資料的行為就叫索引,文件歸屬於一種型別,而這些型別存在於索引中,我們可以簡單的對比下傳統資料庫和es的對應關係: 關係資料庫—資料庫db—表table—行row—列column elasticSearch—索引庫—型別—文件–欄位fields es叢集可以包含多個索引(資料庫),每一個索引庫可以包含多個型別(types)表,每一個型別可以包含多個文件(document)(行),每個文件可以包含多個欄位(fields)(列) 建立索引文件 使用自己的id建立: PUT {index}/{type}/{id} { “field”: “value”, … } ②ES內建ID建立: POST {index}/{type}/ { “field”: “value”, … } ①②ES響應內容: { “_index”: “itsource”, “_type”: “employee”, “_id”: xxxxxx, “_version”: 1, //文件版本號 “created”: true //是否新增 }

③ 獲取指定ID的文件 GET itsource/employee/123?pretty ③返回的內容: { “_index” : “itsource”, “_type” : “employee”, “_id” : “123”, “_version” : 1, “found” : true, “_source” : { “email”: "[email protected]", “fullName”: “倪先華”, … “joine_date”: “2016-06-01” } } 建立索引文件 ①使用自己的ID建立: PUT {index}/{type}/{id} { “field”: “value”, … } ②ES內建ID建立: POST {index}/{type}/ { “field”: “value”, … } ①②ES響應內容: { “_index”: “itsource”, “_type”: “employee”, “_id”: xxxxxx, “_version”: 1, //文件版本號 “created”: true //是否新增 }

③ 獲取指定ID的文件 GET itsource/employee/123?pretty ③返回的內容: { “_index” : “itsource”, “_type” : “employee”, “_id” : “123”, “_version” : 1, “found” : true, “_source” : { “email”: "[email protected]", “fullName”: “倪先華”, … “joine_date”: “2016-06-01” } } 返回文件的部分欄位: GET預設返回整個文件,通過GET /itsource/employee/123?_source=fullName,email 只返回文件內容,不要元資料: GET itsource/employee/123/_source 只檢查文件是否存在(查詢頭資訊): curl -i -X HEAD

http://localhost:9200/itsource/employee/123 ④ 修改文件 更新整個文件 同PUT {index}/{type}/{id} 在響應中,我們可以看到Elasticsearch把 _version 增加了。 { … “_version” : 2, “created”: false } created 標識為 false 因為同索引、同類型下已經存在同ID的文件。 在內部,Elasticsearch已經標記舊文件為刪除並添加了一個完整的新文件。舊版本文件不會立即消失,但你也不能去訪問它。Elasticsearch會在你繼續索引更多資料時清理被刪除的文件。 區域性更新文件 接受一個區域性文件引數 doc,它會合併到現有文件中,物件合併在一起,存在的標量欄位被覆蓋,新欄位被新增。 POST itsource/employee/123/_update { “doc” : { “email” : "[email protected]", “salary”: 1000 } } email會被更新覆蓋,salary會新增。 這個API 似乎 允許你修改文件的區域性,但事實上Elasticsearch 遵循與之前所說完全相同的過程,這個過程如下:

  1. 從舊文件中檢索JSON
  2. 修改它
  3. 刪除舊文件
  4. 索引新文件 指令碼更新文件 也可以通過使用簡單的指令碼來進行。這個例子使用一個指令碼將age加5: POST itsource/emploee/123/_update { “script” : “ctx._source.age += 5” } 在上面的例子中, ctx._source指向當前被更新的文件。 注意,目前的更新操作只能一次應用在一個文件上。 刪除文件 DELETE {index}/{type}/{id} 存在文件的返回: { “found” : true, “_index” : “website”, “_type” : “blog”, “_id” : “123”, “_version” : 3 } 不存在的返回: { “found” : false, “_index” : “website”, “_type” : “blog”, “_id” : “123”, “_version” : 4 } 注意:儘管文件不存在,但_version依舊增加了。這是內部記錄的一部分,它確保在多節點間不同操作可以有正確的順序。 批量操作bulk API 使用單一請求來實現多個文件的create、index、update 或 delete。 Bulk請求體格式: { action: { metadata }}\n { request body }\n { action: { metadata }}\n { request body }\n 每行必須以 “\n” 符號結尾,包括最後一行。這些都是作為每行有效的分離而做的標記。 create當文件不存在時建立之。 index建立新文件或替換已有文件。 update區域性更新文件。 delete刪除一個文件。 例如: POST _bulk { “delete”: { “_index”: “itsource”, “_type”: “employee”, “_id”: “123” }} { “create”: { “_index”: “itsource”, “_type”: “blog”, “_id”: “123” }} { “title”: “我釋出的部落格” } { “index”: { “_index”: “itsource”, “_type”: “blog” }} { “title”: “我的第二部落格” }

注意:delete後不需要請求體,最後一行要有回車 4. DSL查詢與過濾 4.1. 什麼是DSL查詢 由ES提供豐富且靈活的查詢語言叫做DSL查詢(Query DSL),它允許你構建更加複雜、強大的查詢。 DSL(Domain Specific Language特定領域語言)以JSON請求體的形式出現。我們可以這樣表示之前關於“倪先華”的查詢: 查詢字串模式:GET itsource/employee/_search?q=fullName:倪先華 DSL模式: GET itsource/employee/_search { “query” : { “match” : { “fullName” : “倪先華” } } } 對於簡單查詢,使用查詢字串比較好,但是對於複雜查詢,由於條件多,邏輯巢狀複雜,查詢字串不易組織與表達,且容易出錯,因此推薦複雜查詢通過DSL使用JSON內容格式的請求體代替。 4.2. DSL查詢 使用DSL查詢,必須要傳遞query引數給ES。 GET _search {“query”: YOUR_QUERY_HERE} 一個常用的相對完整的DSL查詢: GET itsource/employee/_search { “query”: { “match”: {“sex”:“女”} }, “from”: 20, “size”: 10, " _source": [“fullName”, “age”, “email”], “sort”: [{“join_date”: “desc”},{“age”: “asc”}] } 上面的DSL查詢語句代表:查詢公司員工性別為女的員工,並按照加入時間降序、年齡升序排列,最終返回第21條至30條資料(只返回名字、年齡和email欄位)

4.3. DSL過濾 DSL過濾語句和DSL查詢語句非常相似,但是它們的使用目的卻不同: DSL過濾查詢文件的方式更像是對於我的條件“有”或者“沒有”,而DSL查詢語句則像是“有多像”。 DSL過濾和DSL查詢在效能上的區別:  過濾結果可以快取並應用到後續請求。  查詢語句同時匹配文件,計算相關性,所以更耗時,且不快取。  過濾語句可有效地配合查詢語句完成文件過濾。 原則上,使用DSL查詢做全文字搜尋或其他需要進行相關性評分的場景,其它全用DSL過濾。 2.0以上的用法 { “query”: { “bool”: { “must”: [ {“match”: {“description”: “search” }} ], “filter”: { “term”: {“tags”: “lucene”} } } } } 2.0以前的用法 { “query”: { “filtered”: { “query”: { “match”: {“description”: “search” } }, “filter”: { “term”: {“tags”: “lucene”} } } } }

4.4. 使用DSL查詢與過濾 ① 全匹配(match_all) 普通搜尋(匹配所有文件): { “query” : { “match_all” : {} } } 如果需要使用過濾條件(在所有文件中過濾,紅色部分預設可不寫): { “query” : { “bool” : { “must” : [{ “match_all”:{} }], “filter”:{…} } } } ② 標準查詢(match和multi_match) match查詢是一個標準查詢,不管你需要全文字查詢還是精確查詢基本上都要用到它。 如果你使用match查詢一個全文字欄位,它會在真正查詢之前用分析器先分析查詢字元: { “query”: { “match”: { “fullName”: “Steven King” } } } 上面的搜尋會對Steven King分詞,並找到包含Steven或King的文件,然後給出排序分值。 如果用 match 下指定了一個確切值,在遇到數字,日期,布林值或者 not_analyzed的字串時,它將為你搜索你給定的值,如: { “match”: { “age”: 26 }} { “match”: { “date”: “2014-09-01” }} { “match”: { “public”: true }} { “match”: { “tag”: “full_text” }} multi_match 查詢允許你做 match查詢的基礎上同時搜尋多個欄位: { “query”:{ “multi_match”: { “query”: “Steven King”, “fields”: [ “fullName”, “title” ] } } } 上面的搜尋同時在fullName和title欄位中匹配。 提示:match一般只用於全文欄位的匹配與查詢,一般不用於過濾。

③單詞搜尋與過濾(Term和Terms) { “query”: { “bool”: { “must”: { “match_all”: {} }, “filter”: { “term”: { “tags”: “elasticsearch” } } } } } Terms搜尋與過濾 { “query”: { “terms”: { “tags”: [“jvm”, “hadoop”, “lucene”], “minimum_match”: 2 } } } minimum_match:至少匹配個數,預設為1

④ 組合條件搜尋與過濾(Bool) 組合搜尋bool可以組合多個查詢條件為一個查詢物件,查詢條件包括must、should和must_not。 例如:查詢愛好有美女,同時也有喜歡遊戲或運動,且出生於1990-06-30及之後的人。 { “query”: { “bool”: { “must”: [{“term”: {“hobby”: “美女”}}], “should”: [{“term”: {“hobby”: “遊戲”}}, {“term”: {“hobby”: “運動”}} ], “must_not”: [ {“range” :{“birth_date”:{“lt”: “1990-06-30”}}} ], “filter”: […], “minimum_should_match”: 1 } } } 提示: 如果 bool 查詢下沒有must子句,那至少應該有一個should子句。但是 如果有 must子句,那麼沒有 should子句也可以進行查詢。 ⑤ 範圍查詢與過濾(range) range過濾允許我們按照指定範圍查詢一批資料: { “query”:{ “range”: { “age”: { “gte”: 20, “lt”: 30 } } } } 上例中查詢年齡大於等於20並且小於30。 gt:> gte:>= lt:< lte:<= ⑥ 存在和缺失過濾器(exists和missing) { “query”: { “bool”: { “must”: [{ “match_all”: {} }], “filter”: { “exists”: { “field”: “gps” } } } } } 提示:exists和missing只能用於過濾結果。 ⑦ 前匹配搜尋與過濾(prefix) 和term查詢相似,前匹配搜尋不是精確匹配,而是類似於SQL中的like ‘key%’ { “query”: { “prefix”: { “fullName”: “倪” } } } 上例即查詢姓倪的所有人。 ⑧ 萬用字元搜尋(wildcard) 使用代表0~N個,使用?代表1個。 { “query”: { “wildcard”: { “fullName”: "倪華" } } } 4.5. 小結 DSL查詢是ES提供的通用查詢方式,這種方式最大的特點是開發語言的無關性,即任意的客戶端只要支援HTTP請求,就可以通過JSON格式的查詢資料完成複雜的搜尋。 查詢與過濾在實際的專案開發中是經常遇到的主題。 5. 分詞與對映 5.1. 為什麼要使用分詞與對映 在全文檢索理論中,文件的查詢是通過關鍵字查詢文件索引來進行匹配,因此將文字拆分為有意義的單詞,對於搜尋結果的準確性至關重要,因此,在建立索引的過程中和分析搜尋語句的過程中都需要對文字串分詞。 ES中分詞需要對具體欄位指定分詞器等細節,因此需要在文件的對映中明確指出。 5.2. IK分詞器 ES預設對英文文字的分詞器支援較好,但和lucene一樣,如果需要對中文進行全文檢索,那麼需要使用中文分詞器,同lucene一樣,在使用中文全文檢索前,需要整合IK分詞器。 ES的IK分詞器外掛原始碼地址:https://github.com/medcl/elasticsearch-analysis-ik ① Maven打包IK外掛 ② 解壓target/releases/elasticsearch-analysis-ik-5.2.2.zip檔案 並將其內容放置於ES根目錄/plugins/ik ③ 配置外掛:(可預設不改) 外掛配置:plugin-descriptor.properties ④ 分詞器(可預設不改) 詞典配置:config/IKAnalyzer.cfg.xml ⑤ 重啟ES ⑥ 測試分詞器 POST _analyze { “analyzer”:“ik_smart”, “text”:“中國駐洛杉磯領事館遭亞裔男子槍擊 嫌犯已自首” } 注意:IK分詞器有兩種型別,分別是ik_smart分詞器和ik_max_word分詞器。 ik_smart: 會做最粗粒度的拆分,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,國歌”。 ik_max_word: 會將文字做最細粒度的拆分,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,中華人民,中華,華人,人民共和國,人民,人,民,共和國,共和,和,國國,國歌”,會窮盡各種可能的組合;

提示:也可以直接使用已整合好各種外掛的elasticsearch-rtf-master.zip中文發行版,但ES版本為5.1.1。 5.3. 文件對映Mapper ES的文件對映(mapping)機制用於進行欄位型別確認,將每個欄位匹配為一種確定的資料型別。 5.3.1. ES欄位型別 ① 基本欄位型別 字串:text,keyword text預設為全文文字,keyword預設為非全文文字 數字:long,integer,short,double,float 日期:date 邏輯:boolean ② 複雜資料型別 物件型別:object 陣列型別:array 地理位置:geo_point,geo_shape 5.3.2. 預設對映 檢視索引型別的對映配置:GET {indexName}/{typeName}/_mapping ES在沒有配置Mapping的情況下新增文件,ES會嘗試對欄位型別進行猜測,並動態生成欄位和型別的對映關係。 JSON type Field type Boolean: true or false “boolean” Whole number: 123 “long” Floating point: 123.45 “float” String, valid date:“2014-09-15” “date” String: “foo bar” “text”

5.3.3. 簡單型別對映 欄位對映的常用屬性配置列表 type 型別:基本資料型別,integer,long,date,boolean,keyword,text… enable 是否啟用:預設為true。 false:不能索引、不能搜尋過濾,僅在_source中儲存 boost 權重提升倍數:用於查詢時加權計算最終的得分。 format 格式:一般用於指定日期格式,如 yyyy-MM-dd HH:mm:ss.SSS ignore_above 長度限制:長度大於該值的字串將不會被索引和儲存。 ignore_malformed 轉換錯誤忽略:true代表當格式轉換錯誤時,忽略該值,被忽略後不會被儲存和索引。 include_in_all 是否將該欄位值組合到_all中。 _all : 虛擬欄位,每個文件都有該欄位,代表所有欄位的組合資訊,作用方便直接對整個文件的所有資訊進行搜尋。 {id:1,name:zs,age:20,_all:1,zs,20} null_value 預設控制替換值。如空字串替換為”NULL”,空數字替換為-1 store 是否儲存:預設為false。true意義不大,因為_source中已有資料 index 索引模式:analyzed (索引並分詞,text預設模式), not_analyzed (索引不分詞,keyword預設模式),no(不索引) analyzer 索引分詞器:索引建立時使用的分詞器,如ik_smart,ik_max_word,standard search_analyzer 搜尋分詞器:搜尋該欄位的值時,傳入的查詢內容的分詞器。 fields 多欄位索引:當對該欄位需要使用多種索引模式時使用。 如:城市搜尋New York “city”: { “type”: “text”, “analyzer”: “ik_smart”, “fields”: { “raw”: { “type”: “keyword” } }

} 那麼以後搜尋過濾和排序就可以使用city.raw欄位名

① 針對單個型別的對映配置方式 POST {indexName}/{typeName}/_mapping { “{typeName}”: { “properties”: { “id”: { “type”: “long” }, “content”: { “type”: “text”, “analyzer”: “ik_smart”, “search_analyzer”: “ik_smart” } } } } 注意:你可以在第一次建立索引的時候指定對映的型別。此外,你也可以晚些時候為新欄位新增對映(或者為已有的型別更新對映)。 你可以向已有對映中增加欄位,但你不能修改它。如果一個欄位在對映中已經存在,這可能意味著那個欄位的資料已經被索引。如果你改變了欄位對映,那已經被索引的資料將錯誤並且不能被正確的搜尋到。 我們可以更新一個對映來增加一個新欄位,但是不能把已有欄位的型別那個從 analyzed 改到 not_analyzed。 ② 同時對多個型別的對映配置方式(推薦) PUT {indexName} { “mappings”: { “user”: { “properties”: { “id”: { “type”: “integer” }, “info”: { “type”: “text”, “analyzer”: “ik_smart”, “search_analyzer” } } }, “dept”: { “properties”: { “id”: { “type”: “integer” }, …更多欄位對映配置 } } } } 5.3.4. 物件及陣列型別對映 ① 物件的對映與索引 { “id” : 1, “girl” : { “name” : “王小花”, “age” : 22 } } 對應的mapping配置: { “properties”: { “id”: {“type”: “long”}, “girl”: { “properties”:{ “name”: {“type”: “keyword”}, “age”: {“type”: “long”} } } } } 注意:Lucene不理解內建物件,一個lucene文件包含鍵值對的一個扁平化列表,以便於ES索引內建物件,它把文件轉換為類似這樣: { “id”: 1, “girl.name”:“王小花”, “girl.age”:26 } 內建欄位與名字相關,區分兩個欄位中相同的名字,可以使用全路徑,例如user.girl.name ② 陣列與物件陣列 注意:陣列中元素的型別必須一致。 Java : Object[] objects = []; Js : var objs = [1,true,”haha”]; { “id” : 1, “hobby” : [“王小花”,“林志玲”] } 對應的mapping配置是: { “properties”: { “id”: {“type”: “long”}, “hobby”: {“type”: “keyword”} } }

物件陣列的對映 { “id” : 1, “girl”:[{“name”:“林志玲”,“age”:32},{“name”:“趙麗穎”,“age”:22}] } 對應的對映配置為: “properties”: { “id”: { “type”: “long” }, “girl”: { “properties”: { “age”: { “type”: “long” }, “name”: { “type”: “text” } } } } 注意:同內聯物件一樣,物件陣列也會被扁平化索引 { “user.girl.age”: [32, 22], “user.girl.name”: [“林志玲”, “趙麗穎”] } 注意:扁平化後,物件屬性的相關性已經丟失,因為每個多值欄位只是一個數值集,不是排序的陣列。 比如查詢:哪個女朋友的年齡是22歲? 這個是無法查詢到答案,如果需要保留關係,需要使用巢狀物件nested objects。 5.3.5. 全域性對映 全域性對映可以通過動態模板和預設設定兩種方式實現。 預設方式:default 索引下所有的型別對映配置會繼承_default_的配置,如: PUT {indexName} { “mappings”: { “default”: { “_all”: { “enabled”: false } },

"user": {}, 
"dept": { 
  "_all": {
    "enabled": true
  }
}

} } 上例中:user和dept都會繼承_default_的配置,user型別的文件中將不會合並所有欄位到_all,而dept會。 動態模板:dynamic_templates 注意:ES會預設把string型別的欄位對映為text型別(預設使用標準分詞器)和對應的keyword型別,如: “name”: { “type”: “text”, “fields”: { “keyword”: { “type”: “keyword”, “ignore_above”: 256 } } } 在實際應用場景中,一個物件的屬性中,需要全文檢索的欄位較少,大部分字串不需要分詞,因此,需要利用全域性模板覆蓋自帶的預設模板: PUT _template/global_template //建立/修改名為global_template的模板 { “template”: “", //匹配所有索引庫 “settings”: { “number_of_shards”: 1 }, //匹配到的索引庫只建立1個主分片 “mappings”: { “default”: { “_all”: { “enabled”: false //關閉所有型別的_all欄位 }, “dynamic_templates”: [ { “string_as_text”: { “match_mapping_type”: “string”,//匹配型別string “match”: "_txt”, //匹配欄位名字以_txt結尾 “mapping”: { “type”: “text”,//將型別為string的欄位對映為text型別 “analyzer”: “ik_max_word”, “search_analyzer”: “ik_max_word”, “fields”: { “raw”: { “type”: “keyword”, “ignore_above”: 256 } } } } }, { “string_as_keyword”: { “match_mapping_type”: “string”,//匹配型別string “mapping”: { “type”: “keyword”//將型別為string的欄位對映為keyword型別 } } } ] } }} 說明:上例中定義了兩種動態對映模板string_as_text和string_as_keyword. 在實際的型別欄位對映時,會依次匹配: ①欄位自定義配置、②全域性dynamic_templates[string_as_text、string_as_keyword]、 ③索引dynamic_templates[…]、④ES自帶的型別對映。 以最先匹配上的為準。 注意:索引庫在建立的時候會繼承當前最新的dynamic_templates,索引庫建立後,修改動態模板,無法應用到已存在的索引庫。 5.3.6. 最佳實踐 對映的配置會影響到後續資料的索引過程,因此,在實際專案中應遵循如下順序規則: ① 配置全域性動態模板對映(覆蓋預設的string對映) ② 配置自定義欄位對映(由於基本型別主要用於過濾和普通查詢,因此,欄位對映主要對需要全文檢索的欄位進行配置) ③ 建立、更新和刪除文件 ④ 搜尋