Elasticsearch是一個高度可擴展開源的全文搜索引擎.它搜索幾乎是實時的,用ES做爲搜索引擎,爲複雜搜索功能的需求提供解決方案.java
ES的使用場景:node
網上商場,搜索商品.mysql
ES配合logstash,kibana,日誌分析.linux
本教程的其餘部分,將指導你完成ES的安裝,啓動,瀏覽,以及數據的CRUD.若是你完整的完成了本教程,你應該已經對ES有很好的瞭解了,但願你能從中受到啓發.sql
ES有幾個核心概念,從一開始理解這些概念將對你後面的學習有很大幫助。shell
近實時(NRT)express
ES是一個近實時的搜索引擎(平臺),表明着從添加數據到能被搜索到只有不多的延遲。(大約是1s)json
集羣api
能夠將多臺ES服務器做爲集羣使用,能夠在任何一臺節點上進行搜索。集羣有一個默認的名稱(可修改),「elasticsearch」,這個集羣名稱必須是惟一的,由於集羣的節點是經過集羣名稱來加入集羣的。數組
確保在相同環境中不要有相同的集羣名稱,不然有可能節點會加入到非預期的集羣中。
節點
節點是做爲集羣的一部分的單個服務器,存儲數據,而且參與集羣的索引和搜索功能。與集羣同樣,節點由一個名稱標識,默認狀況下,該名稱是在啓動時分配給節點的隨機通用惟一標識符(UUID)。若是不但願使用默認值,則能夠定義所需的任何節點名稱。此名稱對於管理目的很重要,由於您但願肯定網絡中的哪些服務器對應於ElasticSearch集羣中的哪些節點。
索引
索引是具備某種類似特性的文檔集合。例如,您能夠擁有客戶數據的索引、產品目錄的另外一個索引以及訂單數據的另外一個索引。索引由一個名稱(必須所有是小寫)標識,當對其中的文檔執行索引、搜索、更新和刪除操做時,該名稱用於引用索引。在單個集羣中,您能夠定義任意多個索引。
若是你學習過Mysql ,能夠將其暫時理解爲 MySql中的 database。
類型
一個索引能夠有多個類型。例如一個索引下能夠有文章類型,也能夠有用戶類型,也能夠有評論類型。在一個索引中不能再建立多個類型,在之後的版本中將刪除類型的整個概念。
在Elasticsearch 7.0.0或更高版本中建立的索引再也不接受
_default_
映射。在6.x中建立的索引將繼續像之前同樣在Elasticsearch 6.x中運行。在7.0中的API中不推薦使用類型,對索引建立,放置映射,獲取映射,放置模板,獲取模板和獲取字段映射API進行重大更改。
文檔
一個文檔是一個可被索引的基礎信息單元。好比,你能夠擁有某一個客戶的文檔,某一個產品的一個文檔,固然,也能夠擁有某個訂單的一個文檔。文檔以JSON(Javascript Object Notation)格式來表示,而JSON是一個處處存在的互聯網數據交互格式。
在一個index/type裏面,你能夠存儲任意多的文檔。注意,儘管一個文檔,物理上存在於一個索引之中,文檔必須被索引/賦予一個索引的type。
分片和副本
索引可能存儲大量數據,這些數據可能會c超出單個節點的硬件限制。例如,佔用1TB磁盤空間的10億個文檔的單個索引可能不適合單個節點的磁盤,或者速度太慢,沒法單獨知足單個節點的搜索請求。
爲了解決這個問題,ElasticSearch提供了將索引細分爲多個片斷(稱爲碎片)的能力。建立索引時,只需定義所需的碎片數量。每一個分片(shard)自己就是一個徹底功能性和獨立的「索引」,能夠託管在集羣中的任何節點上。
爲何要分片?
它容許您水平拆分/縮放內容量
它容許您跨碎片(可能在多個節點上)分佈和並行操做,從而提升性能/吞吐量
如何分配分片以及如何將其文檔聚合回搜索請求的機制徹底由ElasticSearch管理,而且對做爲用戶的您是透明的。
在隨時可能發生故障的網絡/雲環境中,很是有用,強烈建議在碎片/節點以某種方式脫機或因任何緣由消失時使用故障轉移機制。爲此,ElasticSearch容許您將索引分片的一個或多個副本複製成所謂的副本分片,簡稱爲副本分片。
爲何要有副本?
當分片/節點發生故障時提供高可用性。所以,須要注意的是,副本分片永遠不會分配到複製它的原始/主分片所在的節點上。
容許您擴展搜索量/吞吐量,由於能夠在全部副本上並行執行搜索。
總而言之,每一個索引能夠分割成多個分片。索引也能夠零次(意味着沒有副本)或屢次複製。複製後,每一個索引將具備主分片(從中複製的原始分片)和副本分片(主分片的副本)。
能夠在建立索引時爲每一個索引定義分片和副本的數量。建立索引後,您還能夠隨時動態更改副本的數量。您可使用收縮和拆分API更改現有索引的分片數量,建議在建立索引時就考慮好分片和副本的數量。
默認狀況下,ElasticSearch中的每一個索引都分配一個主分片和一個副本,這意味着若是集羣中至少有兩個節點,則索引將有一個主分片和另外一個副本分片(一個完整副本),每一個索引總共有兩個分片。
每一個ElasticSearch分片都是一個Lucene索引。在一個Lucene索引中,能夠有最多數量的文檔。從Lucene-5843起,限制爲2147483519(=integer.max_value-128)個文檔。您可使用 api監視碎片大小(之後會講到)。
接下來讓咱們開始有趣的部分吧...
二進制文件可從www.slastic.co/downloads以及過去發佈的全部版本中得到。對於每一個版本,Windows、Linux和MacOS以及Linux的DEB和RPM軟件包以及Windows的MSI安裝軟件包都提供了與平臺相關的存檔版本。
Linux
簡單起見,咱們使用tarb包進行安裝
下載ElasticSearch 7.1.1 Linux tar。
curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.1.1-linux-x86_64.tar.gz複製代碼
解壓
tar -xvf elasticsearch-7.1.1-linux-x86_64.tar.gz複製代碼
解壓完成將在當前目錄中建立一組文件和文件夾,而後咱們進入bin目錄。
cd elasticsearch-7.1.1/bin複製代碼
啓動節點和單個集羣
./elasticsearch複製代碼
Windows
對於Windows用戶,咱們建議使用msi安裝程序包。該包包含一個圖形用戶界面(GUI),指導您完成安裝過程。
而後雙擊下載的文件以啓動GUI。在第一個屏幕中,選擇安裝目錄:
而後選擇是做爲服務安裝,仍是根據須要手動啓動ElasticSearch。要與Linux示例保持一致,請選擇不做爲服務安裝:
對於配置,只需保留默認值:
取消選中全部插件以不安裝任何插件:
單擊「安裝」按鈕後,將安裝ElasticSearch:
默認狀況下,ElasticSearch將安裝在%ProgramFiles%\Elastic\ElasticSearch。進入安裝目錄,打開命令提示符,輸入
.\elasticsearch.exe複製代碼
成功運行節點
若是安裝一切順利,您將看到下面的一堆消息:
[2018-09-13T12:20:01,766][INFO ][o.e.e.NodeEnvironment ] [localhost.localdomain] using [1] data paths, mounts [[/home (/dev/mapper/fedora-home)]], net usable_space [335.3gb], net total_space [410.3gb], types [ext4][2018-09-13T12:20:01,772][INFO ][o.e.e.NodeEnvironment ] [localhost.localdomain] heap size [990.7mb], compressed ordinary object pointers [true][2018-09-13T12:20:01,774][INFO ][o.e.n.Node ] [localhost.localdomain] node name [localhost.localdomain], node ID [B0aEHNagTiWx7SYj-l4NTw][2018-09-13T12:20:01,775][INFO ][o.e.n.Node ] [localhost.localdomain] version[7.1.1], pid[13030], build[oss/zip/77fc20e/2018-09-13T15:37:57.478402Z], OS[Linux/4.16.11-100.fc26.x86_64/amd64], JVM["Oracle Corporation"/OpenJDK 64-Bit Server VM/10/10+46][2018-09-13T12:20:01,775][INFO ][o.e.n.Node ] [localhost.localdomain] JVM arguments [-Xms1g, -Xmx1g, -XX:+UseConcMarkSweepGC, -XX:CMSInitiatingOccupancyFraction=75, -XX:+UseCMSInitiatingOccupancyOnly, -XX:+AlwaysPreTouch, -Xss1m, -Djava.awt.headless=true, -Dfile.encoding=UTF-8, -Djna.nosys=true, -XX:-OmitStackTraceInFastThrow, -Dio.netty.noUnsafe=true, -Dio.netty.noKeySetOptimization=true, -Dio.netty.recycler.maxCapacityPerThread=0, -Dlog4j.shutdownHookEnabled=false, -Dlog4j2.disable.jmx=true, -Djava.io.tmpdir=/tmp/elasticsearch.LN1ctLCi, -XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath=data, -XX:ErrorFile=logs/hs_err_pid%p.log, -Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m, -Djava.locale.providers=COMPAT, -XX:UseAVX=2, -Dio.netty.allocator.type=unpooled, -Des.path.home=/home/manybubbles/Workspaces/Elastic/master/elasticsearch/qa/unconfigured-node-name/build/cluster/integTestCluster node0/elasticsearch-7.0.0-alpha1-SNAPSHOT, -Des.path.conf=/home/manybubbles/Workspaces/Elastic/master/elasticsearch/qa/unconfigured-node-name/build/cluster/integTestCluster node0/elasticsearch-7.0.0-alpha1-SNAPSHOT/config, -Des.distribution.flavor=oss, -Des.distribution.type=zip][2018-09-13T12:20:02,543][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [aggs-matrix-stats][2018-09-13T12:20:02,543][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [analysis-common][2018-09-13T12:20:02,543][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [ingest-common][2018-09-13T12:20:02,544][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [lang-expression][2018-09-13T12:20:02,544][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [lang-mustache][2018-09-13T12:20:02,544][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [lang-painless][2018-09-13T12:20:02,544][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [mapper-extras][2018-09-13T12:20:02,544][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [parent-join][2018-09-13T12:20:02,544][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [percolator][2018-09-13T12:20:02,544][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [rank-eval][2018-09-13T12:20:02,544][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [reindex][2018-09-13T12:20:02,545][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [repository-url][2018-09-13T12:20:02,545][INFO ][o.e.p.PluginsService ] [localhost.localdomain] loaded module [transport-netty4][2018-09-13T12:20:02,545][INFO ][o.e.p.PluginsService ] [localhost.localdomain] no plugins loaded[2018-09-13T12:20:04,657][INFO ][o.e.d.DiscoveryModule ] [localhost.localdomain] using discovery type [zen][2018-09-13T12:20:05,006][INFO ][o.e.n.Node ] [localhost.localdomain] initialized[2018-09-13T12:20:05,007][INFO ][o.e.n.Node ] [localhost.localdomain] starting ...[2018-09-13T12:20:05,202][INFO ][o.e.t.TransportService ] [localhost.localdomain] publish_address {127.0.0.1:9300}, bound_addresses {[::1]:9300}, {127.0.0.1:9300}[2018-09-13T12:20:05,221][WARN ][o.e.b.BootstrapChecks ] [localhost.localdomain] max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535][2018-09-13T12:20:05,221][WARN ][o.e.b.BootstrapChecks ] [localhost.localdomain] max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144][2018-09-13T12:20:08,355][INFO ][o.e.c.s.MasterService ] [localhost.localdomain] elected-as-master ([0] nodes joined)[, ], reason: master node changed {previous [], current [{localhost.localdomain}{B0aEHNagTiWx7SYj-l4NTw}{hzsQz6CVQMCTpMCVLM4IHg}{127.0.0.1}{127.0.0.1:9300}{testattr=test}]}[2018-09-13T12:20:08,360][INFO ][o.e.c.s.ClusterApplierService] [localhost.localdomain] master node changed {previous [], current [{localhost.localdomain}{B0aEHNagTiWx7SYj-l4NTw}{hzsQz6CVQMCTpMCVLM4IHg}{127.0.0.1}{127.0.0.1:9300}{testattr=test}]}, reason: apply cluster state (from master [master {localhost.localdomain}{B0aEHNagTiWx7SYj-l4NTw}{hzsQz6CVQMCTpMCVLM4IHg}{127.0.0.1}{127.0.0.1:9300}{testattr=test} committed version [1] source [elected-as-master ([0] nodes joined)[, ]]])[2018-09-13T12:20:08,384][INFO ][o.e.h.n.Netty4HttpServerTransport] [localhost.localdomain] publish_address {127.0.0.1:9200}, bound_addresses {[::1]:9200}, {127.0.0.1:9200}[2018-09-13T12:20:08,384][INFO ][o.e.n.Node ] [localhost.localdomain] started複製代碼
咱們能夠看到名爲「6-bjhwl」的節點(在您的示例中是一組不一樣的字符)已經啓動,並將本身選爲單個集羣中的主機。如今還不用擔憂master是什麼意思。這裏最重要的是,咱們已經在一個集羣中啓動了一個節點。
如前所述,咱們能夠覆蓋集羣或節點名。當啓動ElasticSearch時,能夠從命令行執行此操做,以下所示:
./elasticsearch -Ecluster.name=my_cluster_name -Enode.name=my_node_name複製代碼
還請注意標記爲http的行,其中包含可從中訪問節點的http地址(192.168.8.112)和端口(9200)的信息。默認狀況下,ElasticSearch使用端口9200提供對其RESTAPI的訪問。若有必要,此端口可配置。
使用REST API
如今咱們已經啓動並運行了節點(和集羣),下一步就是了解如何與之通訊。幸運的是,ElasticSearch提供了一個很是全面和強大的RESTAPI,您可使用它與集羣進行交互。可使用API執行的少數操做以下:
檢查集羣、節點和索引的運行情況、狀態和統計信息。
管理集羣、節點和索引數據和元數據。
對索引執行CRUD(建立、讀取、更新和刪除)和搜索操做。
執行高級搜索操做,如分頁、排序、篩選、腳本編寫、聚合和許多其餘操做。
讓咱們從一個基本的健康檢查開始,咱們可使用它來查看集羣的運行狀況。咱們將使用curl來實現這一點,但您可使用任何容許您進行HTTP/REST調用的工具。假設咱們仍然在啓動ElasticSearch並打開另外一個命令shell窗口的同一個節點上。
爲了檢查集羣的運行情況,咱們將使用_cat
API。您能夠在Kibana的控制檯中運行下面的命令,方法是單擊「在控制檯中查看」,或者使用curl,方法是單擊下面的「複製爲curl」連接並將其粘貼到終端中。
curl -X GET "localhost:9200/_cat/health?v"複製代碼
響應結果:
epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent1475247709 17:01:49 elasticsearch green 1 1 0 0 0 0 0 0 - 100.0%複製代碼
咱們能夠看到名爲「elasticsearch」的集羣處於綠色狀態。每當咱們請求集羣健康時,咱們要麼獲得綠色、黃色,要麼獲得紅色。
綠色-一切正常(集羣功能齊全)
黃色-全部數據均可用,但某些副本還沒有分配(羣集徹底正常工做)
紅色-因爲任何緣由,某些數據不可用(羣集部分正常工做)
注意:當集羣爲紅色時,它將繼續提供來自可用分片的搜索請求,但您可能須要儘快修復它,由於存在未分配的分片。
從上面的響應中,咱們能夠看到總共1個節點,而且咱們有0個碎片,由於咱們在其中尚未數據。請注意,因爲咱們使用的是默認羣集名稱(ElasticSearch),而且因爲ElasticSearch默認狀況下發如今同一臺計算機上查找其餘節點,所以您可能會意外啓動計算機上的多個節點,並使它們都加入單個羣集。在這個場景中,您可能會在上面的響應中看到多個節點。
咱們還能夠獲得集羣中的節點列表:
curl -X GET "localhost:9200/_cat/nodes?v"複製代碼
響應結果:
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name127.0.0.1 10 5 5 4.46 mdi * PB2SGZY複製代碼
咱們能夠看到一個名爲「pb2sgzy」的節點,它是當前集羣中的單個節點。
如今讓咱們來看看咱們的索引:
curl -X GET "localhost:9200/_cat/indices?v"複製代碼
響應結果:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size複製代碼
這就意味着咱們在集羣中尚未索引。
如今,讓咱們建立一個名爲「customer」的索引,而後再次列出全部索引:
curl -X PUT "localhost:9200/customer?pretty"curl -X GET "localhost:9200/_cat/indices?v"複製代碼
第一個命令使用put動詞建立名爲「customer」的索引。咱們只需在調用的末尾附加pretty
命令它漂亮地打印JSON響應(若是有的話)。
響應結果:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.sizeyellow open customer 95SQ4TSUT7mWBT7VNHH67A 1 1 0 0 260b 260b複製代碼
第二個命令的結果告訴咱們,咱們如今有一個名爲customer的索引,它有一個主碎片和一個副本(默認值),其中包含零個文檔。
您可能還會注意到客戶索引中有一個黃色的健康標籤。回想咱們以前的討論,黃色意味着一些副本還沒有分配。此索引起生這種狀況的緣由是,默認狀況下,ElasticSearch爲此索引建立了一個副本。由於目前只有一個節點在運行,因此在另外一個節點加入集羣以前,還不能分配一個副本(爲了高可用性)。一旦該副本分配到第二個節點上,該索引的運行情況將變爲綠色。
如今咱們把一些東西放到客戶索引中。咱們將在客戶索引中索引一個簡單的客戶文檔,其ID爲1,以下所示:
curl -X PUT "localhost:9200/customer/_doc/1?pretty" -H 'Content-Type: application/json' -d'{ "name": "John Doe"}'複製代碼
響應結果:
{ "_index" : "customer", "_type" : "_doc", "_id" : "1", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1}複製代碼
從上面,咱們能夠看到在客戶索引中成功地建立了一個新的客戶文檔。文檔還有一個內部ID 1,咱們在索引時指定了它。
須要注意的是,ElasticSearch不要求您在索引文檔以前先顯式建立索引。在上一個示例中,若是客戶索引以前不存在,那麼ElasticSearch將自動建立該索引。
如今讓咱們檢索剛纔索引的文檔:
curl -X GET "localhost:9200/customer/_doc/1?pretty"複製代碼
響應結果:
{ "_index" : "customer", "_type" : "_doc", "_id" : "1", "_version" : 1, "_seq_no" : 25, "_primary_term" : 1, "found" : true, "_source" : { "name": "John Doe" }}複製代碼
除了一個字段以外found
,這裏沒有發現任何異常的地方,說明咱們找到了一個具備請求的ID 1的文檔和另外一個字段_source
,它返回了咱們從上一步索引的完整JSON文檔。
如今,讓咱們刪除剛剛建立的索引,而後再次列出全部索引:
curl -X DELETE "localhost:9200/customer?pretty"curl -X GET "localhost:9200/_cat/indices?v"複製代碼
響應結果:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size複製代碼
這意味着索引被成功地刪除了,如今咱們又回到了開始時集羣中什麼都沒有的地方。
在咱們繼續以前,讓咱們再仔細看看咱們迄今爲止學到的一些API命令:
#建立索引curl -X PUT "localhost:9200/customer"#建立文檔(添加數據)curl -X PUT "localhost:9200/customer/_doc/1" -H 'Content-Type: application/json' -d'{ "name": "John Doe"}'#查詢文檔(查詢數據)curl -X GET "localhost:9200/customer/_doc/1"#刪除文檔(刪除數據)curl -X DELETE "localhost:9200/customer"複製代碼
若是咱們仔細研究上述命令,咱們實際上能夠看到在ElasticSearch中如何訪問數據的模式。這種模式能夠歸納以下:
<HTTP Verb> /<Index>/<Endpoint>/<ID>複製代碼
這種REST訪問模式在全部API命令中都很是廣泛,若是您能記住它,那麼您將在掌握ElasticSearch方面有一個很好的開端。
ElasticSearch提供近實時的數據操做和搜索功能。默認狀況下,從索引/更新/刪除數據到數據出如今搜索結果中,預計一秒鐘的延遲(刷新間隔)。這是與其餘平臺(如SQL)的一個重要區別,在SQL中,數據在事務完成後當即可用。
建立/替換文檔(修改數據)
咱們之前見過如何索引單個文檔。讓咱們再次回憶一下這個命令:
curl -X PUT "localhost:9200/customer/_doc/1?pretty" -H 'Content-Type: application/json' -d'{ "name": "John Doe"}'複製代碼
一樣,上面將把指定的文檔索引到客戶索引中,ID爲1。若是咱們用不一樣的(或相同的)文檔再次執行上述命令,那麼ElasticSearch將在現有文檔的基礎上替換(即從新建立)一個ID爲1的新文檔:
curl -X PUT "localhost:9200/customer/_doc/1?pretty" -H 'Content-Type: application/json' -d'{ "name": "Jane Doe"}'複製代碼
上面將ID爲1的文檔的名稱從「John Doe」更改成「Jane Doe」。另外一方面,若是咱們使用不一樣的ID,則會建立新的文檔,而且索引中已有的文檔將保持不變。
上面的索引是一個ID爲2的新文檔。
建立時,ID是可填可不填的。若是未指定,ElasticSearch將生成一個隨機ID。ElasticSearch生成的實際ID(或在前面的示例中顯式指定的任何內容)做爲索引API調用的一部分返回。
此示例演示如何索引沒有顯式ID的文檔:
curl -X POST "localhost:9200/customer/_doc?pretty" -H 'Content-Type: application/json' -d'{ "name": "Jane Doe"}'複製代碼
注意,在上述狀況下,咱們使用的是post
動詞而不是put
,由於咱們沒有指定ID。
除了可以添加和替換文檔以外,咱們也能夠更新文檔。請注意,Elasticsearch實際上並無在底層執行覆蓋更新。而是先刪除舊文檔,再添加一條新文檔。
這個例子把原來ID爲1的名字修改爲了Jane Doe,詳情請看下面的例子:
curl -X POST "localhost:9200/customer/_update/1?pretty" -H 'Content-Type: application/json' -d'{ "doc": { "name": "Jane Doe" }}'複製代碼
此示例演示如何經過將名稱字段更改成「Jane Doe」來更新之前的文檔(ID爲1),同時向其添加年齡字段:
curl -X POST "localhost:9200/customer/_update/1?pretty" -H 'Content-Type: application/json' -d'{ "doc": { "name": "Jane Doe", "age": 20 }}'複製代碼
也可使用簡單的腳本執行更新,此示例使用腳本將年齡增長5:
curl -X POST "localhost:9200/customer/_update/1?pretty" -H 'Content-Type: application/json' -d'{ "script" : "ctx._source.age += 5"}'複製代碼
Elasticsearch提供了給定條件下更新多個文檔的功能,就像Sql中的updata ... where ...
後面的章節咱們會詳細介紹。
刪除文檔至關簡單。
此示例顯示如何刪除ID爲2的之前的客戶:
curl -X DELETE "localhost:9200/customer/_doc/2?pretty"複製代碼
請參閱_delete_by_query API來刪除與特定查詢匹配的全部文檔。值得注意的是,刪除整個索引比使用delete By Query API刪除全部文檔要有效得多。 _delete_by_query API
會在後面詳細介紹。
除了可以索引、更新和刪除單個文檔以外,Elasticsearch還提供了使用_bulk API批量執行上述操做的能力。此功能很是重要,由於它提供了一種很是有效的機制,能夠在儘量少的網絡往返的狀況下儘量快地執行多個操做。
做爲一個簡單的例子,下面的調用在一個批量操做中索引兩個文檔(ID 1 - John Doe和ID 2 - Jane Doe):
curl -X POST "localhost:9200/customer/_bulk?pretty" -H 'Content-Type: application/json' -d'{"index":{"_id":"1"}}{"name": "John Doe" }{"index":{"_id":"2"}}{"name": "Jane Doe" }'複製代碼
此例更新第一個文檔(ID爲1),而後在一次批量操做中刪除第二個文檔(ID爲2):
curl -X POST "localhost:9200/customer/_bulk?pretty" -H 'Content-Type: application/json' -d'{"update":{"_id":"1"}}{"doc": { "name": "John Doe becomes Jane Doe" } }{"delete":{"_id":"2"}}'複製代碼
請注意,對於delete操做,它以後沒有對應的源文檔,由於刪除操做只須要刪除文檔的ID。
批量API不會由於某個操做失敗而失敗(有錯誤也會執行下去,最後會返回每一個操做的狀態)。若是一個操做因爲某種緣由失敗,它將在失敗以後繼續處理其他的操做。當bulk API返回時,它將爲每一個操做提供一個狀態(與發送操做的順序相同),以便檢查某個特定操做是否失敗。
樣本數據
如今咱們已經瞭解了基本知識,讓咱們嘗試使用更真實的數據集。我準備了一個虛構的JSON客戶銀行帳戶信息文檔示例。每一個文檔都有如下內容:
{ "account_number": 0, "balance": 16623, "firstname": "Bradshaw", "lastname": "Mckenzie", "age": 29, "gender": "F", "address": "244 Columbus Place", "employer": "Euron", "email": "bradshawmckenzie@euron.com", "city": "Hobucken", "state": "CO"}複製代碼
該數據是使用www.json- generator.com/生成的,所以請忽略數據的實際值和語義,由於它們都是隨機生成的。
加載樣本數據
您能夠從這裏下載示例數據集(accounts.json)。將其提取到當前目錄,而後按以下方式將其加載到集羣中:
curl -H "Content-Type: application/json" -XPOST "localhost:9200/bank/_bulk?pretty&refresh" --data-binary "@accounts.json"curl "localhost:9200/_cat/indices?v"複製代碼
響應結果:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.sizeyellow open bank l7sSYV2cQXmu6_4rJWVIww 5 1 1000 0 128.6kb 128.6kb複製代碼
這意味着咱們剛剛成功地將索引的1000個文檔批量存儲到銀行索引中。
如今讓咱們從一些簡單的搜索開始。運行搜索有兩種基本方法:
一種是經過REST請求URI發送搜索參數。
另外一種是經過REST請求體發送搜索參數。
請求體方法容許您更富表現力,還能夠以更可讀的JSON格式定義搜索。咱們將嘗試請求URI方法的一個示例,可是在本教程的其他部分中,咱們將只使用請求體方法。
用於搜索的REST API能夠從_search
端點訪問。這個例子返回銀行索引中的全部文檔:
curl -X GET "localhost:9200/bank/_search?q=*&sort=account_number:asc&pretty"複製代碼
讓咱們首先分析一下搜索調用。咱們正在銀行索引中搜索(_search
),q=*
參數指示Elasticsearch匹配索引中的全部文檔。sort=account_number:asc
參數指示使用每一個文檔的account_number字段按升序對結果排序。一樣,pretty
參數只告訴Elasticsearch返回打印得很漂亮的JSON結果。
相應結果(部分顯示):
{ "took" : 63, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value": 1000, "relation": "eq" }, "max_score" : null, "hits" : [ { "_index" : "bank", "_type" : "_doc", "_id" : "0", "sort": [0], "_score" : null, "_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"} }, { "_index" : "bank", "_type" : "_doc", "_id" : "1", "sort": [1], "_score" : null, "_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"} }, ... ] }}複製代碼
關於迴應,咱們能夠看到:
took
Elasticsearch執行搜索所用的時間(以毫秒爲單位)
timed_out
告訴咱們搜索是否超時
_shards
告訴咱們搜索了多少碎片,以及成功/失敗搜索碎片的計數
hits
搜索結果
hits.total
包含與搜索條件匹配的文檔總數相關的信息的對象
hits.total.value
總命中數的值。
hits.total.relation
:hits.total.value
值是準確的命中次數,在這種狀況下它等於eq
或總命中次數的下界(大於或等於),在這種狀況下它等於gte
。
hits.hits
實際的搜索結果數組(默認爲前10個文檔)
hits.sort
結果排序鍵(若是按分數排序,則丟失)
hits._score
和 max_score
——暫時忽略這些字段
hits.total
的準確度是由請求參數track_total_hits
控制,當track_total_hits
設置爲true時,請求將精確地跟蹤總命中「relation」:「eq」
。默認值爲10,000,這意味着總命中數能夠精確地跟蹤到10,000個文檔。經過顯式地將track_total_hits
設置爲true,能夠強制進行準確的計數。有關詳細信息,後面章節咱們會進行介紹。
這是使用請求體搜索的方式:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "match_all": {} }, "sort": [ { "account_number": "asc" } ]}'複製代碼
這裏的不一樣之處在於,咱們沒有在URI中傳遞q=*,而是向_search API提供了json風格的查詢請求體。咱們將在下一節中討論這個JSON查詢。
重要的是要了解,一旦您得到了搜索結果,Elasticsearch就會徹底處理請求,而且不會維護任何類型的服務器端資源,也不會在結果中打開遊標。這是許多其餘平臺如SQL造成鮮明對比,你最初可能獲得部分的子集查詢結果預先而後不斷返回到服務器,若是你想獲取(或頁面)其他的結果使用某種狀態的服務器端遊標。
Elasticsearch提供了一種JSON風格的查詢語言,您可使用它來執行查詢。這稱爲Query DSL。查詢語言很是全面,乍一看可能有些嚇人,但實際上學習它的最佳方法是從幾個基本示例開始。
回到上一個例子,咱們執行了這個查詢:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "match_all": {} }}'複製代碼
仔細分析上面的內容,query
部分告訴咱們進行查詢操做,match_all
只是咱們想要運行的查詢類型,match_all只是搜索指定索引中的全部文檔。
除了查詢參數,咱們還能夠傳遞其餘參數來影響搜索結果。在上一小節的最後,咱們傳入了sort,這裏咱們傳入了size:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "match_all": {} }, "size": 1}'複製代碼
請注意,若是未指定大小,則默認爲10。
下面的例子執行match_all
並返回文檔10到19(from和size能夠類比mysql中的limit ? ?):
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "match_all": {} }, "from": 10, "size": 10}'複製代碼
from參數(基於0)指定從哪一個文檔索引開始,size參數指定從from參數開始返回多少文檔。該特性在實現搜索結果分頁時很是有用。
注意,若是沒有指定from,則默認值爲0。
本這個例子執行match_all
操做,並按賬戶餘額降序對結果進行排序,並返回前10個(默認大小)文檔。
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "match_all": {} }, "sort": { "balance": { "order": "desc" } }}'複製代碼
既然咱們已經看到了一些基本的搜索參數,那麼讓咱們深刻研究一下Query DSL。讓咱們先看看返回的文檔字段。默認狀況下,做爲全部搜索的一部分,返回完整的JSON文檔。這被稱爲「源」(搜索命中中的_source
字段)。若是咱們不但願返回整個源文檔,咱們能夠只請求從源中返回幾個字段。
此示例顯示如何從搜索中返回兩個字段,即賬號和餘額(在!source
內):
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "match_all": {} }, "_source": ["account_number", "balance"]}'複製代碼
注意,上面的示例只減小了_source
_字段。它仍然只返回一個名爲_source
的字段,可是其中只包含account_number和balance字段。
若是您以前有了解過MySql,那麼上面的內容在概念上與SQL SELECT from field list有些相似。
如今讓咱們進入查詢部分。在前面,咱們已經瞭解瞭如何使用match_all
查詢來匹配全部文檔。如今讓咱們引入一個名爲match
查詢的新查詢,它能夠被看做是基本的字段搜索查詢(即針對特定字段或字段集進行的搜索)。
本例返回編號爲20的賬戶
類比mysql match相似於mysql 中的條件查詢。
例如返回編號爲20的賬戶:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "match": { "account_number": 20 } }}'複製代碼
此示例返回地址中包含「mill」的全部賬戶:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "match": { "address": "mill" } }}'複製代碼
此示例返回地址中包含「mill」或「lane」的全部賬戶:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "match": { "address": "mill lane" } }}'複製代碼
match (match_phrase)的一個變體,它返回地址中包含短語「mill lane」的全部賬戶:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "match_phrase": { "address": "mill lane" } }}'複製代碼
注意:match 中若是加空格,那麼會被認爲兩個單詞,包含任意一個單詞將被查詢到
match_parase 將忽略空格,將該字符認爲一個總體,會在索引中匹配包含這個總體的文檔。
如今讓咱們介紹bool查詢。bool查詢容許咱們使用布爾邏輯將較小的查詢組合成較大的查詢。
若是您熟悉mysql,那麼你就會發現布爾查詢其實至關於 and or not...
這個例子包含兩個匹配查詢,返回地址中包含「mill」和「lane」的全部賬戶:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "bool": { "must": [ { "match": { "address": "mill" } }, { "match": { "address": "lane" } } ] } }}'複製代碼
在上面的示例中,bool must子句指定了全部必須爲true的查詢,則將文檔視爲匹配。
相反,這個例子包含兩個匹配查詢,並返回地址中包含「mill」或「lane」的全部賬戶:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "bool": { "should": [ { "match": { "address": "mill" } }, { "match": { "address": "lane" } } ] } }}'複製代碼
在上面的示例中,bool should子句指定了一個查詢列表,其中任何一個查詢必須爲真,才能將文檔視爲匹配。
這個例子包含兩個匹配查詢,返回地址中既不包含「mill」也不包含「lane」的全部賬戶:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "bool": { "must_not": [ { "match": { "address": "mill" } }, { "match": { "address": "lane" } } ] } }}'複製代碼
在上面的例子中,bool must_not子句指定了一個查詢列表,其中沒有一個查詢必須爲真,才能將文檔視爲匹配。
咱們能夠在bool查詢中同時組合must、should和must_not子句。此外,咱們能夠在這些bool子句中組合bool查詢,以模擬任何複雜的多級布爾邏輯。
這個例子返回全部40歲但不居住在ID(aho)的人的帳戶:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "bool": { "must": [ { "match": { "age": "40" } } ], "must_not": [ { "match": { "state": "ID" } } ] } }}'複製代碼
在上一節中,咱們跳過了一個名爲document score(搜索結果中的_score字段)的小細節。分數是一個數值,它是衡量文檔與咱們指定的搜索查詢匹配程度的一個相對指標。分數越高,文檔越相關,分數越低,文檔越不相關。
可是查詢並不老是須要生成分數,特別是當它們只用於「過濾」文檔集時。Elasticsearch會自動優化查詢執行,以免計算無用的分數。
咱們在上一節中介紹的bool查詢還支持filter子句,它容許咱們使用查詢來限制將由其餘子句匹配的文檔,而不改變計算分數的方式。做爲一個示例,讓咱們介紹範圍查詢,它容許咱們根據一系列值篩選文檔。這一般用於數字或日期篩選。
本例使用bool查詢返回餘額在20000到30000之間的全部賬戶,包括餘額。換句話說,咱們但願找到餘額大於或等於20000,小於或等於30000的帳戶。
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "query": { "bool": { "must": { "match_all": {} }, "filter": { "range": { "balance": { "gte": 20000, "lte": 30000 } } } } }}'複製代碼
經過分析上面的內容,bool查詢包含一個match_all
查詢(查詢部分)和一個range
查詢(篩選部分)。咱們能夠將任何其餘查詢替換到查詢和篩選器部分中。在上面的例子中,範圍查詢很是有意義,由於落入範圍的文檔都「相等」匹配,沒有文檔比有更有意義(由於是篩選過的)。
除了match_all、match、bool和range
查詢以外,還有許多其餘可用的查詢類型,咱們在這裏不深刻討論它們。既然咱們已經對它們的工做原理有了基本的瞭解,那麼在學習和試驗其餘查詢類型時應用這些知識應該不會太難。
聚合提供了對數據進行分組和提取統計信息的能力。考慮聚合最簡單的方法是大體將其等同於SQL GROUP by和SQL聚合函數。在Elasticsearch中,您能夠執行返回命中的搜索,同時在一個響應中返回與全部命中分離的聚合結果。這是很是強大和高效的,由於您能夠運行查詢和多個聚合,並一次性得到這兩個(或任何一個)操做的結果,從而避免使用簡潔和簡化的API進行網絡往返。
首先,這個示例按狀態對全部賬戶進行分組,而後返回按count降序排列的前10個(默認)狀態(也是默認):
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "size": 0, "aggs": { "group_by_state": { "terms": { "field": "state.keyword" } } }}'複製代碼
在SQL中,上述聚合在概念上與:
SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC LIMIT 10;複製代碼
{ "took": 29, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped" : 0, "failed": 0 }, "hits" : { "total" : { "value": 1000, "relation": "eq" }, "max_score" : null, "hits" : [ ] }, "aggregations" : { "group_by_state" : { "doc_count_error_upper_bound": 20, "sum_other_doc_count": 770, "buckets" : [ { "key" : "ID", "doc_count" : 27 }, { "key" : "TX", "doc_count" : 27 }, { "key" : "AL", "doc_count" : 25 }, { "key" : "MD", "doc_count" : 25 }, { "key" : "TN", "doc_count" : 23 }, { "key" : "MA", "doc_count" : 21 }, { "key" : "NC", "doc_count" : 21 }, { "key" : "ND", "doc_count" : 21 }, { "key" : "ME", "doc_count" : 20 }, { "key" : "MO", "doc_count" : 20 } ] } }}複製代碼
咱們能夠看到ID(Idaho)有27個賬戶,其次是TX(Texas)的27個賬戶,而後是AL(Alabama)的25個賬戶,依此類推。
注意,咱們將size=0設置爲不顯示搜索結果,由於咱們只想看到響應中的聚合結果。
基於前面的彙總,本示例按狀態計算平均賬戶餘額(一樣只針對按count降序排列的前10個狀態):
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "size": 0, "aggs": { "group_by_state": { "terms": { "field": "state.keyword" }, "aggs": { "average_balance": { "avg": { "field": "balance" } } } } }}'複製代碼
注意,咱們如何將average_balance聚合嵌套在group_by_state聚合中。這是全部聚合的常見模式。您能夠在聚合中任意嵌套聚合,以從數據中提取所需的結果。
基於以前的聚合,咱們如今按降序對平均餘額排序:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "size": 0, "aggs": { "group_by_state": { "terms": { "field": "state.keyword", "order": { "average_balance": "desc" } }, "aggs": { "average_balance": { "avg": { "field": "balance" } } } } }}'複製代碼
這個例子展現了咱們如何按照年齡等級(20-29歲,30-39歲,40-49歲)分組,而後按性別分組,最後獲得每一個年齡等級,每一個性別的平均帳戶餘額:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'{ "size": 0, "aggs": { "group_by_age": { "range": { "field": "age", "ranges": [ { "from": 20, "to": 30 }, { "from": 30, "to": 40 }, { "from": 40, "to": 50 } ] }, "aggs": { "group_by_gender": { "terms": { "field": "gender.keyword" }, "aggs": { "average_balance": { "avg": { "field": "balance" } } } } } } }}'複製代碼
還有許多其餘聚合功能,咱們在這裏不會詳細討論。若是您想作進一步的實驗,那麼聚合參考指南是一個很好的開始。
彈性搜索既是一個簡單的產品,也是一個複雜的產品。到目前爲止,咱們已經瞭解了它是什麼、如何查看它的內部以及如何使用一些RESTapi來使用它。但願本教程能讓您更好地理解Elasticsearch是什麼,更重要的是,它激發了您進一步試驗它的其餘優秀特性!