在學習Elasticsearch的過程當中想找一些能夠系統的描述es操做的文章,可是官網沒有中文頁面,ES中文指南的排版和翻譯又很突兀和不協調,所以決定本身看一遍官方的maunal總結一下,因爲沒時間把全部章節所有翻一遍,因此寫一篇學習筆記以便完成初步的學習。html
概念總覽:node
在描述ES的基本操做以前,首先來介紹幾個概念:python
Relational DB -> Databases -> Tables -> Rows -> Columns Elasticsearch -> Indices -> Types -> Documents -> Fields
以上是早期的官方文檔貼出的一個概念介紹圖,其含義不用多說,其實ES更適合與MongoDB類比:linux
MongoDB -> DBs -> Collections -> Documents -> Fields Elasticsearch -> Indices -> Types -> Documents -> Fields
ES裏的Index能夠看作一個庫,Documents至關於表的行,而Types至關於表。數據庫
可是Types的概念將會被逐漸弱化並可能在將來版本中刪除,而在Elasticsearch 6中,一個index下已經只能包含一個type了,所以能夠將index理解爲一個表,types意如其名僅用於展現一個document所屬的分類,實際上在本文對ES進行操做時因爲index和type的一對一關係,許多時候查詢document已經只須要指定index而無需再指定type了。api
本文使用Elasticsearch 6.5.4和Kibana 6.5.4下的環境進行演示。安全
1、Kibana命令行操做服務器
使用Kibana操做ES是當前最簡單的一種方式,且提供命令補全、index名稱補全等便捷的功能。同時console界面的小扳手點進去還有和官方手冊裏同樣的「copy as CURL」選擇,將選中的命令copy以後粘貼到linux中就會轉換爲curl命令的格式,對於想要了解curl直接操做ES的同窗是頗有幫助的。數據結構
我我的並不建議直接使用curl操做ES,由於不少時候須要本身設置header,麻煩且低效。併發
Elasticsearch官方操做手冊地址:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
這裏參考官網的reference手冊對內置API進行詳細梳理,因爲官方手冊的介紹方式不適用於我這種新手,我只能打亂順序學習,本部分的介紹基本遵循學習傳統數據庫的流程,主要分爲如下7個部分:
Note:本文全部命令都是在Kibana console操做的,關於Kibana的安裝配置和使用,參考《Kibana安裝配置》一文。
1.數據結構搭建
結構的搭建主要包含index的建立和刪除、查詢等等,types無需建立。
#建立名爲test的index,兩種寫法等同,名字不能包含特殊字符,只能小寫,不能以-, _, +開頭,不能超過255字節。 PUT test PUT /test --PUT /test的本質是PUT http://ip:9200/test,kibana作了優化所以寫不寫以前的/無所謂 #固然你還能夠直接插入一條數據,index會自動被建立 PUT leo/dramas/1 { "name":"權力的遊戲" } #查看建立好的index的詳細信息 GET leo #刪除index DELETE leo #查詢當前全部的index,這裏調用了_cat的API GET _cat/indices
上圖爲我測試建立的多個indices,每列的列名分別是:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size --其中pri表示number of shards,rep表示number of replicas,新建的index health爲yellow的緣由是我只有一臺服務器所以未能建立replica。
在使用GET <index_name>查看index詳細信息時能夠看到,每一個index下都有一個名爲mapping的屬性,這個屬性用於描述當前type下的大體field有哪些,固然也別忘了在6.5.4版本里一個index下只有一種type了。
2.增
即向ES插入數據:
#插入單條數據,用PUT或POST均可以 PUT test/books/1 {"name":"《阿Q正傳》","price":100} PUT test/books/2 {"name":"《鋼鐵是怎樣煉成的》","price":200} PUT test/books/3 {"name":"《西遊記》","price":300}
插入多條數據,目前只能用_bulk API來實現,index表示新插入數據,create同理,在python的index()方法中op_type=create表示若是index不存在那麼直接建立index並插入數據,而op_type=index表示向已存在的index中插數據,此外還能夠一塊兒bulk delete、update等操做。
PUT test/books/_bulk { "index":{"_id":4}} {"name":"《圍城》","price":101} { "index":{"_id":5}} {"name":"《格林童話》","price":108} } #若是你不想設置主鍵_id,那麼能夠直接置空,系統會建立默認主鍵,寫法以下: PUT test/books/_bulk { "index":{}} {"name":"《圍城》","price":101} { "index":{}} {"name":"《格林童話》","price":108}
注意插入數據時若是指定的_id已經存在,那麼新插入的數據會直接替換原ID的數據。
查看下插入的數據:
GET test/books/_search {"query":{"match_all":{}}} GET test/books/_search {"query":{"match":{"_id":1}}} GET test/_search {"query": {"range": {"price": {"lte":1000} } } }
index下也有_search API所以這裏你也能夠省略books直接查詢整個index全部types下的記錄,實際上在6版本中因爲types概念的弱化(一個index只能有一種type)許多查詢均可以直接不寫type名了。
這裏的query和range以及lte都是DSL關鍵字,其實query只至關於模糊查詢或全文搜索。關於查詢,更系統的DSL(domain specific language)關鍵字及示例會在第5部分「查」補充。
3.刪
記錄的刪除一般由2個API,直接DELETE和POST _delete_by_query完成,示例以下:
#DELETE只能根據ID進行刪除,本例中刪除的是系統自定義的ID所以比較奇怪。 DELETE test/books/_mbEdGgBH8b_BYBmOW-C #_delete_by_query API容許你刪除符合query條件的記錄,其query body與上邊的查詢過濾的query body規則同樣。 POST test/_delete_by_query {"query": {"range": {"price": {"lte":1000} } } } #其實刪除、修改和查詢還涉及到多版本控制的概念,這個概念在傳統數據庫中已經很熟悉了,就是爲了保證數據一致性的。 #關於版本控制的內容會在第6部分「版本控制」補充。
4.改
記錄更新也是2個API,_update和_update_by_query,前者根據ID進行更新,後者能夠更新指定的query結果。此外你還能夠不使用這兩個API直接像新插入數據那樣更新數據,只是此時你的body部分必須包含全部的fields了,不然操做完畢後你會發現document只剩下你所更新的那幾個fields,其餘的全沒了。
至於爲何刪除使用DELETE命令,而更新只能用_update的API,只是由於ES是RESTFUL風格的,http的指令有DELETE但並無UPDATE關鍵字。
更新涉及到版本控制以便維護數據一致性,其實分爲兩個操做:get和reindex,大體步驟是:首先取到相應的document,而後執行更新script,最後返回執行的結果。至於具體的多版本控制機制將在第6部分解釋。
更新涉及的DSL語言也與其餘操做很不同:
#_update API,表示將id爲5的document的price改成100 POST test/books/5/_update {"script": {"source":"ctx._source.price=params.price", "lang":"painless", "params":{ "price":100 } }
這裏的script,source,lang,params都是DSL關鍵字,lang=painless表示使用painless腳本語言來編寫script來完成。
ctx我暫理解爲當前事務,ctx._source表示當前定位的document,params表示本次更新用到的數據,source則表示更新操做,通俗來說就是用params的數據+source的操做一塊兒完成更新。
#若是隻是簡單的增長新field和刪除field那麼格式就比較簡單: POST test/books/5/_update { "script":"ctx._source.booktype='少兒童話'" } POST test/books/5/_update { "script":"ctx._source.remove('少兒童話')" } #此外ctx._source或ctx._source.<field_name>還有不少其餘的方法和屬性,這裏貼一個官網的示例來做出引伸,更多的示例慢慢實踐吧。 POST test/_doc/1/_update { "script" : { "source": "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'none' }", "lang": "painless", "params" : { "tag" : "green" } } }
這個示例的含義就是:對於id=1的document,若是tags包含green字符,那麼刪掉這個document,不然不操做。至於contains是模糊匹配仍是精確匹配,有興趣的能夠花幾十秒作個測試。
5.查
前4個部分的示例中已經有許多查詢的示例了,這裏在以前的基礎上介紹一些比較複雜的查詢,首先來了解一個DSL的概念:
DSL:Domain Specific Language,ES提供一種基於JSON的查詢語言,這種查詢語言包含兩種子句模式:
1.Leaf query clauses
2.Compound query clauses --經常使用的就是bool組合查詢
好吧,其實這裏介紹這兩個概念對理解複雜查詢毫無做用,我只是照搬下官方手冊,防止某天頓悟時找不到概念,接下來再看兩個DSL的概念:
Query通常來講包含兩各部分:query context 或 filter context:
舉例來講:
GET /_search { "query": { "bool": { "must": [ { "match": { "title": "Search" }}, { "match": { "content": "Elasticsearch" }} ], "filter": [ { "term": { "status": "published" }}, { "range": { "publish_date": { "gte": "2015-01-01" }}} ] } } }
這個例子的query就包含了全部2種context,並使用了bool組合查詢,能夠看到bool是最外圍的關鍵字,must與filter並行。
bool組合查詢的子關鍵字主要包含must,must_not,should,分別對應AND、NOT、OR三種邏輯運算,此外還有一個filter子關鍵字。
--filter與must:match的區別:
參考:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html
1.must:match會爲匹配到的每一個記錄打分,稱做scoring,表示匹配程度,查詢的結果按打分進行排序。
2.filter與must:match基本一致,惟一的區別是其結果不參與打分,至關於一個再過濾。
到這裏DSL的4個概念就介紹完了,是的所有介紹完了。官網總共也就這幾行,更多關於關鍵字的具體應用須要到特定的頁面且也一般都是一個簡單的示例完事,所以只能靠平常實踐了。
介紹完DSL那麼回到實際應用中來,用於查詢的API通常也是2種:直接經過GET index/doc_type/doc_id獲取,以及_search API
#GET獲取比較簡單,只要有id就能夠了,沒id請使用_search API GET test/books/1 #_search API是查詢使用的核心API,包含諸如聚合、排序、集羣查詢、explain API等等等等,這裏只貼個官方連接和一個示例算啦,重在實踐掌握。 https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html POST /twitter/_search?routing=kimchy { "query": { "bool" : { "must" : { "query_string" : { "query" : "some query string here" } }, "filter" : { "term" : { "user" : "kimchy" } } } } } #這裏的?routing=kimchy是指在集羣中查詢時能夠指定名爲kimchy的shard。
6.版本控制
Versionning,在官網中暫未找到獨立的說明頁面,只找到2篇古老的博客,分別是2011年和2013年的,地址以下:
https://www.elastic.co/blog/versioning
https://www.elastic.co/blog/elasticsearch-versioning-support
第一篇:
內容顯示versioning是由elasticsearch在0.15版本引入的新特性」樂觀併發控制「引伸出來的,只介紹了每一個document都會有個由系統控制自增的_version屬性,並未對版本控制機制做出細節解釋。
不過既然是樂觀併發控制咱們能夠參考傳統RDBMS數據庫中的樂觀鎖來理解,即數據庫服務器會自動進行document快照存儲以便實現事務一致性,接下來看下第二篇博客(實際上看完第二篇博客,裏邊也確實介紹了樂觀鎖定)。
第二篇:
以一個經典的丟失更新示例來描述下樂觀併發控制的必要性:
#首先造一條數據 PUT bank_account/shanghai/1 { "name":"leo", "deposit":100 } GET bank_account/shanghai/1 #以下爲插入的數據,能夠看到_version屬性值爲1 { "_index" : "bank_account", "_type" : "shanghai", "_id" : "1", "_version" : 1, "found" : true, "_source" : { "name" : "leo", "deposit" : 100 } } #若是這時候兩個商戶同時要從我帳戶里扣1塊錢,結果就是兩家同時取到我帳戶餘額爲100,各扣了一元並把99的餘額寫入ES,這顯然是錯的。所以ES推出了versioning特性。
對於index中的每條記錄都會有一個_version的屬性,其取值範圍爲:[1,2^63),插入數據時默認的_version都是1,每次對這個document進行修改或刪除操做都會使其+1,這個過程是由ES本身控制的。
總結一下Versioning的工做機制實際上是這樣的,咱們以一個投票計數案例爲例,1表示球員的ID,每次有人爲id=1的球員投票都將投票計數votes+1:
POST NBA/all_star_votes/1/_update?retry_on_conflict=5 {"script":"ctx._source.votes += 1"}
1.首先查詢到你要更新的documents。
2.而後進行version check,記下你查詢到的documents的_version。
3.更新時指定_version=<第二步中查到的version>
4.ES server端收到更新請求後開始進行衝突檢測,若是發現有人在這期間成功投了票(那麼_version就會變化),那麼直接返回一個http的409 conflict錯誤碼,若是能夠更新那麼天然返回200 ok就好。
5.若是你顯式的設置了retry_on_conflict參數,那麼步驟四的表現還會有所變化:在發現記錄被更改後,server端會嘗試根據scripts將votes+1,而後將_version也+1,而後使用新的_version值和votes值進行更新,若是再次衝突那麼重複以前的操做直到成功更新或達到retry_on_conflict的重複次數。
以上操做據官方手冊說是節省了頻繁獲取/釋放鎖的開銷,versioning特性並不是強制開啓的,只有你指定了version參數或者retry_on_conflict參數時,ES纔會啓用versioning特性爲你進行version check和衝突檢測。所以對於相似投票計數這種field的更新你能夠開啓versionging特性,對於不規則的併發更新你能夠棄用此特性直接使用程序隊列或者乾脆用關係型數據庫存儲數據,對於存款更新這種不規則併發更新的金融場景,併發請求之間不可能每次都增減相同的金額,使用retry_on_conflict顯然是無效的,這種場景用關係型數據庫顯然更安全。
固然對於delete操做來講versioning的表現又有所不一樣,由於若是一個系統頻繁的進行數據的刪除,那麼保存大量的舊version會致使資源迅速被耗盡,所以對於delete的記錄ES的默認保存version的時間是1min,這被稱做GC(垃圾回收),你能夠經過修改index.gc_deletes參數來擴大此超時時間。
PS:官網沒說update操做留下的舊version是否也會被按期清除,這個能夠試驗來驗證,插入一條數據屢次更新後進行指定_version的查詢便可驗證,這裏節省時間懶的測了。
7.集羣操做
集羣操做這裏省略,會寫在單獨的集羣搭建筆記中。
2、Python接口操做
你可使用Python內置的REST API:requests module來進行es的操做,可是es提供了一種更加貼近elasticsearch概念體系的API:elasticsearch-py,所以這裏使用elasticsearch-py來進行演示。
elasticsearch API詳述:https://elasticsearch-py.readthedocs.io/en/master/api.html
Note:爲與Python語言兼容,避免出現關鍵字衝突,使用from_代替from,doc_type代替type參數。且爲保持一致性和安全性,本接口推薦使用關鍵字傳參,不建議使用位置傳參。
先來一個簡單的演示示例:
# -*- coding: utf-8 -*- from elasticsearch import Elasticsearch es = Elasticsearch(hosts='http://10.0.1.49:9200/') es.delete_by_query(index="test",doc_type="books",body={"query": { "match_all":{}}}) #這裏的id=1/2在進入ES後就變爲了默認主鍵,查詢時不能用id來查,而是要用_id。固然這裏的主鍵概念實際上是借用了mongo或其餘傳統關係型數據庫的概念,方便理解而已。 es.index(index="test", doc_type="books",id=1,body={"name": "《鋼鐵是怎樣煉成的》","price":100}) es.index(index="test", doc_type="books",id=2,body={"name": "《狂人日記》","price":200}) # res=es.search(index="test",doc_type="books",body={"query": {"match_all": {}}}) # print(res) res=es.search(index="test", doc_type="books", body={"query": {"range": {"price": { "lt":400} } }, "sort":{ "_id": {} # {"order":"desc"} } } ) print("%d documents found" % res['hits']['total']) for doc in res['hits']['hits']: print("%s) %s" % (doc['_id'], doc['_source']['name']))
這裏邊涉及到一些基礎的method,這些method的詳細參數和用法均可以在上邊貼出的elasticsearch API詳述網址中找到。
elasticsearch module包含CatClient, ClusterClient, IndicesClient, IngestClient, NodesClient, SnapshotClient and TasksClient等7個client子類以及一些其餘暫無需介紹的類,此外還有一個底層訪問接口Elasticsearch類,你能且也只能經過Elasticsearch來訪問前述的7種接口。
定義Elasticsearch class的部分相關代碼爲:
...... from ..transport import Transport from .indices import IndicesClient from .ingest import IngestClient from .cluster import ClusterClient from .cat import CatClient from .nodes import NodesClient from .remote import RemoteClient from .snapshot import SnapshotClient from .tasks import TasksClient class Elasticsearch(object): def __init__(self, hosts=None, transport_class=Transport, **kwargs): """ :arg transport_class: :class:`~elasticsearch.Transport` subclass to use. """ self.transport = transport_class(_normalize_hosts(hosts), **kwargs) # namespaced clients for compatibility with API names self.indices = IndicesClient(self) self.ingest = IngestClient(self) self.cluster = ClusterClient(self) self.cat = CatClient(self) self.nodes = NodesClient(self) self.remote = RemoteClient(self) self.snapshot = SnapshotClient(self) self.tasks = TasksClient(self) ......
另外一種通俗的解釋方式就是:
當你定義了一個Elasticsearch實例後,會衍生N種諸如IndicesClient、IngestClient等實例,你能夠根據本身的需求經過調用Elasticsearch的屬性來獲取這些實例,進而調用他們的各類method,這些屬性值能夠是__init__方法中任意屬性,調用這些屬性後你就可使用這些屬性實例的特有method了,這些client子類實例的屬性能夠在上邊貼出的網址裏學習,這裏只簡略貼一下核心接口類Elasticsearch的相關解釋:
class elasticsearch.Elasticsearch(hosts=None, transport_class=<class 'elasticsearch.transport.Transport'>, **kwargs)
hosts參數使用RESTFUL風格定義,即URL格式,相似上邊的'http://10.0.1.49:9200/'
除此以外你還可使用SSL協議建立鏈接,其參數官網並未單獨列出,但能夠經過其SSL鏈接示例獲知使用方式。
此class所有的method包含:
bulk(**kwargs) clear_scroll(**kwargs) count(**kwargs) create(**kwargs) delete(**kwargs) delete_by_query(**kwargs) delete_script(**kwargs) exists(**kwargs) exists_source(**kwargs) explain(**kwargs) field_caps(**kwargs) get(**kwargs) get_script(**kwargs) get_source(**kwargs) index(**kwargs) info(**kwargs) mget(**kwargs) msearch(**kwargs) msearch_template(**kwargs) mtermvectors(**kwargs) ping(**kwargs) put_script(**kwargs) reindex(**kwargs) reindex_rethrottle(**kwargs) render_search_template(**kwargs) scroll(**kwargs) search(**kwargs) search_shards(**kwargs) search_template(**kwargs) termvectors(**kwargs) update(**kwargs) update_by_query(**kwargs)