Elasticsearch7.1中文文檔-第一章-入門

入門

引言

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窗口的同一個節點上。

爲了檢查集羣的運行情況,咱們將使用_catAPI。您能夠在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個文檔批量存儲到銀行索引中。

搜索API

如今讓咱們從一些簡單的搜索開始。運行搜索有兩種基本方法:

  • 一種是經過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"}    }, ...    ]  }}複製代碼

關於迴應,咱們能夠看到:

  • tookElasticsearch執行搜索所用的時間(以毫秒爲單位)

  • timed_out告訴咱們搜索是否超時

  • _shards告訴咱們搜索了多少碎片,以及成功/失敗搜索碎片的計數

  • hits搜索結果

  • hits.total包含與搜索條件匹配的文檔總數相關的信息的對象

  • hits.total.value總命中數的值。

  • hits.total.relation :hits.total.value值是準確的命中次數,在這種狀況下它等於eq或總命中次數的下界(大於或等於),在這種狀況下它等於gte

  • hits.hits 實際的搜索結果數組(默認爲前10個文檔)

  • hits.sort結果排序鍵(若是按分數排序,則丟失)

  • hits._scoremax_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查詢以外,還有許多其餘可用的查詢類型,咱們在這裏不深刻討論它們。既然咱們已經對它們的工做原理有了基本的瞭解,那麼在學習和試驗其餘查詢類型時應用這些知識應該不會太難。

執行聚合(類比mysql 聚合函數)

聚合提供了對數據進行分組和提取統計信息的能力。考慮聚合最簡單的方法是大體將其等同於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是什麼,更重要的是,它激發了您進一步試驗它的其餘優秀特性!

相關文章
相關標籤/搜索