ES-更新現有文檔
ES的更新API允許發送文檔所需要做的修改,而且API會返回一個答復,告知操作是否成功。
更新流程如下
1. 檢索現有的文檔。為了使這步奏效,必須打開_source字段,否則ES並不知道原有文檔的內容。
2. 進行制定的修改。例如,如果文檔是
{"name":"Elasticsearch Denver","organizer":"Lee"}
修改組織者,修改后的文檔是
{"name":"Elasticsearch Denver","organizer":"Roy"}
3. 刪除舊的文檔,在其原有位置索引新的文檔(包含修改的內容)。
使用更新API
(1).通過發送部分文檔,增加或替換現有文檔的一部分。
(2).如果文檔不存在,當發送部分文檔或者腳本時,請確認文檔是否被創建。如果文檔之前不存在,可以指定被索引的文檔原始內容
(3).發送腳本來更新文檔。
1. 發送部分文檔
發送部分的文檔內容,包含所需要設置的字段值,是更新一個或多個字段最容易的方法。為了實現這個操作,需要將這些信息通過HTTP POST請求發送到該文檔URL的_update端點。
先插入一條新文檔
curl -XPUT 'localhost:9200/get-together/group/1?pretty' -d '{ "name":"Elasticsearch Denver", "organizer":"Lee" }'
更新
curl -XPOST 'localhost:9200/get-together/group/1/_update?pretty' -d '{ "doc":{ "organizer":"Roy" } }'
這條命令設置了在doc下指定的字段,將其值設置為所提供的值。它並不考慮這些字段之前的值,也不考慮這些字段之前是否存在。如果之前整個文檔是不存在的,那么更新操作會失敗,並提示文檔缺失。
驗證
FengZhendeMacBook-Pro:cv FengZhen$ curl 'localhost:9200/get-together/group/1?pretty' { "_index" : "get-together", "_type" : "group", "_id" : "1", "_version" : 2, "found" : true, "_source" : { "name" : "Elasticsearch Denver", "organizer" : "Roy" } }
注意:在更新的時候,要牢記可能存在沖突。例如,如果將分組的組織者修改為”Roy”,還有人將其修改為”Feng”,那么其中一次更新會被另一次覆蓋。為了控制這種局面,可以使用版本功能。
2. 使用upsert來創建尚不存在的文檔
為了處理更新時文檔不存在的情況,可以使用upsert.(update和insert的混成詞)
如果被更新的文檔不存在,可以在JSON的upsert部分中添加一個初始文檔用於索引。
curl -XPOST 'localhost:9200/get-together/group/1/_update?pretty' -d '{ "doc":{ "organizer":"Roy" }, "upsert":{ "name":"Elasticsearch Denver", "organizer":"Roy" } }'
3. 通過腳本來更新文檔
(1).默認的腳本語言是Groovy。它的語法和Java相似,但是作為腳本,使用更加簡單。
(2).由於更新要獲得現有文檔的_source內容,修改並重新索引新的文檔,因此腳本會修改_source中的字段。使用ctx._source來使用_source,使用ctx._source[字段名]來引用某個指定的字段。
(3).如果需要變量,推薦在params下作為參數單獨定義,和腳本本身區分開來。這是因為腳本需要編譯,一旦編譯完成,就會被緩存。如果使用不同參數,多次運行同樣的腳本,腳本只需要編譯一次。之后的運行都會從緩存中獲取現有腳本。相比每次不同的腳本,這樣運行會更快,因為不同的腳本每次都需要編譯。
注意:由於安全因素,通過API運行以下腳本可能默認的被禁止,這取決於所運行的ES版本。這成為動態腳本,在elasticsearch.yml中將script.disable_dynamic設置為false,就可以打開這個功能。
curl -XPUT 'localhost:9200/online-shop/shirts/1?pretty' -d '{ "caption":"Learning Elasticsearch", "price":15 }' curl -XPOST 'localhost:9200/online-shop/shirts/1/_update?pretty' -d '{ "script":"ctx._source.price += price_diff", "params":{ "price_diff":10 } }' FengZhendeMacBook-Pro:cv FengZhen$ curl 'localhost:9200/online-shop/_search?pretty' { "took" : 37, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 1.0, "hits" : [ { "_index" : "online-shop", "_type" : "shirts", "_id" : "1", "_score" : 1.0, "_source" : { "caption" : "Learning Elasticsearch", "price" : 25 } } ] } }
可以看到,這里使用的是ctx._source.price而不是ctx._source[‘price’].這是指向price字段的另一個方法。
通過版本來實現並發控制
如果同一時刻多次更新都在執行,將面臨並發的問題。ES支持並發控制,為每篇文檔設置了一個版本號。最初被索引的文檔版本是1。當更新操作重新索引它的時候,版本號就設置為2了。如果與此同時另一個更新將版本設置為2,那么就會產生沖突,目前的更新也會失敗。可以重試這個更新操作,如果不再有沖突,那么版本就會設置為3.
並發控制流程如下
curl -XPOST 'localhost:9200/online-shop/shirts/1/_update?pretty' -d '{ "script":"Thread.sleep(10000);ctx._source.price = 2" }' curl -XPOST 'localhost:9200/online-shop/shirts/1/_update?pretty' -d '{ "script":"ctx._source.caption = \"Knowing Elasticsearch\"" }'
(1).索引文檔然后更新它
(2).更新1在后台啟動,有一定時間的等待(睡眠)
(3).在睡眠期間,發出另一個update的指令來修改文檔。變化發送在更新1獲取原有文檔之后、重新索引回去之前。
(4).由於文檔的版本已經變為2,更新1就會失敗,而不會取消更新2所做的修改。這個時候有就機會重試更新1,然后進行版本為3的修改。
這種並發控制稱為樂觀鎖,因為它允許並行的操作並假設沖突時很少出現的,真的出現時就拋出錯誤。它和悲觀鎖是相對的,悲觀鎖通過鎖住可能引起沖突的操作,第一時間預防沖突。
1. 沖突發生時自動重試更新操作
當版本沖突出現的時候,可以再應用程序中處理。如果是更新操作,可以再次嘗試,但是也可以通過設置retry_on_conflict參數,讓ES自動重試
curl -XPOST 'localhost:9200/online-shop/shirts/1/_update?retry_on_conflict=3' -d '{ "script":"ctx._source.price = 2" }'
2.索引文檔的時候使用版本號
更新文檔的另一個方法是不使用更新API,而是在同一個索引、類型和ID之處索引一個新的文檔。這樣的操作會覆蓋現有的文檔,這種情況下仍然可以使用版本字段來進行並發控制。為了實現這一點,要設置HTTP請求中的version參數。其值應該是期望該文檔要擁有的版本號(當前的版本號)。如:如果認為現有的版本已經是3,一個重新索引的請求如下
curl -XPUT 'localhost:9200/online-shop/shirts/1?version=3' -d '{ "caption":"i konw about es versioning", "price":"5" }'
如果現有的版本實際上不是3,name這個操作就會拋出版本沖突異常並失敗。
有了版本號就可以安全的索引和更新文檔了。
使用外部版本
目前為止都是使用ES的內部版本,每次操作,無論是索引還是更新,ES都會自動的增加版本號。如果數據源是另一個數據存儲,也許在那里有版本控制系統。例如,一種基於時間戳的系統。
為了使用外部版本,需要為每次請求添加version_type=external,以及版本號
curl -XPUT 'localhost:9200/online-shop/shirts/1?version=4&version_type=external' -d '{ "caption":"i konw about es versioning", "price":"5" }'
這將使ES接受任何版本號,只要比現有版本高,而且ES也不會自己增加版本號。