在 Java 应用程序中使用 Elasticsearch: 高性能 RESTful 搜索引擎和文档存储快速入门指南
如果您使用過 Apache Lucene 或 Apache Solr,就會知道它們的使用體驗非常有趣。尤其在您需要擴展基于 Lucene 或 Solr 的解決方案時,您就會了解?Elasticsearch?項目背后的動機。Elasticsearch(構(gòu)建于 Lucene 之上)在一個容易管理的包中提供了高性能的全文搜索功能,支持開箱即用地集群化擴展。您可以通過標準的 REST API 或從特定于編程語言的客戶端庫與 Elasticsearch 進行交互。
本教程將展示 Elasticsearch 的實際工作原理。首先從命令行訪問該 REST API 來了解它的基本信息。然后設(shè)置一個本地 Elasticsearch 服務器,并從一個簡單的 Java 應用程序與它交互。請參見?下載?部分,獲取有關(guān)的示例代碼。
前提條件
要理解本教程的所有示例,需要在您的系統(tǒng)上安裝 Elasticsearch。下載針對您的平臺的?最新 Elastic Search 程序包。將該包解壓到一個方便的位置。在 UNIX 或 Linux 上,通過以下命令啟動該實例:
/elastic-search-dir/bin/elasticsearch在 Windows 上,運行
/elastic-search-dir/bin/elasticsearch.bat在看到日志消息?started?時,該節(jié)點已準備好接受請求。
對于 Java 示例,還需要安裝?Eclipse?和?Apache Maven。如果您的系統(tǒng)上還沒有它們,請下載和安裝它們。
您還需要 cURL。在 Microsoft Windows 上,我使用?Git Bash?shell 來運行 cURL。
回頁首
使用 cURL 執(zhí)行 REST 命令
可以對 Elasticsearch 發(fā)出 cURL 請求,這樣很容易從命令行 shell 體驗該框架。
“Elasticsearch 是無模式的。它可以接受您提供的任何命令,并處理它以供以后查詢。”
Elasticsearch 是無模式的,這意味著它可以接受您提供的任何命令,并處理它以供以后查詢。Elasticsearch 中的所有內(nèi)容都被存儲為文檔,所以您的第一個練習是存儲一個包含歌詞的文檔。首先創(chuàng)建一個索引,它是您的所有文檔類型的容器 — 類似于 MySQL 等關(guān)系數(shù)據(jù)庫中的數(shù)據(jù)庫。然后,將一個文檔插入該索引中,以便可以查詢該文檔的數(shù)據(jù)。
創(chuàng)建一個索引
Elasticsearch 命令的一般格式是:REST VERBHOST:9200/index/doc-type— 其中?REST VERB?是?PUT、GET?或?DELETE。(使用 cURL?-X?動詞前綴來明確指定 HTTP 方法。)
要創(chuàng)建一個索引,可在您的 shell 中運行以下命令:
curl -XPUT "http://localhost:9200/music/"模式可選
盡管 Elasticsearch 是無模式的,但它在幕后使用了 Lucene,后者使用了模式。不過 Elasticsearch 為您隱藏了這種復雜性。實際上,您可以將 Elasticsearch 文檔類型簡單地視為子索引或表名稱。但是,如果您愿意,可以指定一個模式,所以您可以將它視為一種模式可選的數(shù)據(jù)存儲。
插入一個文檔
要在?/music?索引下創(chuàng)建一個類型,可插入一個文檔。在第一個示例中,您的文檔包含數(shù)據(jù)(包含一行)“Deck the Halls” 的歌詞,這是一首最初由威爾士詩人 John Ceirog Hughes 于 1885 年編寫的傳統(tǒng)的圣誕歌曲。
要將包含 “Deck the Halls” 的文檔插入索引中,可運行以下命令(將該命令和本教程的其他 cURL 命令都鍵入到一行中):
curl -XPUT "http://localhost:9200/music/songs/1" -d ' { "name": "Deck the Halls", "year": 1885, "lyrics": "Fa la la la la" }'前面的命令使用?PUT?動詞將一個文檔添加到?/songs?文檔類型,并為該文檔分配 ID 1。URL 路徑顯示為?index/doctype/ID。
查看文檔
要查看該文檔,可使用簡單的?GET?命令:
curl -XGET "http://localhost:9200/music/songs/1"Elasticsearch 使用您之前?PUT?進索引中的 JSON 內(nèi)容作為響應:
{"_index":"music","_type":"songs","_id":"1","_version":1,"found":true,"_source": { "name": "Deck the Halls", "year": 1885, "lyrics": "Fa la la la la" }}更新文檔
如果您認識到日期寫錯了,并想將它更改為 1886 怎么辦?可運行以下命令來更新文檔:
curl -XPUT "http://localhost:9200/music/lyrics/1" -d '{ "name": "Deck the Halls", "year": 1886, "lyrics": "Fa la la la la" }'因為此命令使用了相同的唯一 ID 1,所以該文檔會被更新。
刪除文檔(但暫時不要刪除)
暫時不要刪除該文檔,知道如何刪除它就行了:
curl -XDELETE "http://localhost:9200/music/lyrics/1"從文件插入文檔
這是另一個技巧。您可以使用一個文件的內(nèi)容來從命令行插入文檔。嘗試此方法,添加另一首針對傳統(tǒng)歌曲 “Ballad of Casey Jones” 的文檔。將清單 1 復制到一個名為 caseyjones.json 的文件中;也可以使用示例代碼包中的 caseyjones.json 文件(參見?下載)。將該文件放在任何方便對它運行 cURL 命令的地方。(在下載的代碼中,該文件位于根目錄中。)
清單 1. “Ballad of Casey Jones” 的 JSON 文檔
{"artist": "Wallace Saunders","year": 1909,"styles": ["traditional"],"album": "Unknown","name": "Ballad of Casey Jones","lyrics": "Come all you rounders if you want to hear The story of a brave engineer Casey Jones was the rounder's name.... Come all you rounders if you want to hear The story of a brave engineer Casey Jones was the rounder's name On the six-eight wheeler, boys, he won his fame The caller called Casey at half past four He kissed his wife at the station door He mounted to the cabin with the orders in his hand And he took his farewell trip to that promis'd landChorus: Casey Jones--mounted to his cabin Casey Jones--with his orders in his hand Casey Jones--mounted to his cabin And he took his... land" }運行以下命令,將此文檔?PUT?到您的?music?索引中:
$ curl -XPUT "http://localhost:9200/music/lyrics/2" -d @caseyjones.json在該索引中時,將清單 2 的內(nèi)容(包含另一手民歌 “Walking Boss”)保存到 walking.json 文件中。
清單 2. “Walking Boss” JSON
{"artist": "Clarence Ashley","year": 1920"name": "Walking Boss","styles": ["folk","protest"],"album": "Traditional","lyrics": "Walkin' boss Walkin' boss Walkin' boss I don't belong to youI belong I belong I belong To that steel driving crewWell you work one day Work one day Work one day Then go lay around the shanty two" }將此文檔推送到索引中:
$ curl -XPUT "http://localhost:9200/music/lyrics/3" -d @walking.json搜索 REST API
是時候運行一次基本查詢了,此查詢比您運行來查找 “Get the Halls” 文檔的簡單?GET?要復雜一些。文檔 URL 有一個內(nèi)置的?_search?端點用于此用途。在歌詞中找到所有包含單詞?you?的歌曲:
curl -XGET "http://localhost:9200/music/lyrics/_search?q=lyrics:'you'"q?參數(shù)表示一個查詢。
響應是:
{"took":107,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":2,"max _score":0.15625,"hits":[{"_index":"music","_type":"songs","_id":"2","_ score":0.15625,"_source":{"artist": "Wallace Saunders","year": 1909,"styles": ["traditional"],"album": "Unknown","name": "Ballad of Casey Jones","lyrics": "Come all you rounders if you want to hear The story of a brave engineer Casey Jones was the rounder's name.... Come all you rounders if you want to hear The story of a brave engineer Casey Jones was the rounder's name On the six-eight wheeler, boys, he won his fame The caller called Casey at half past four He kissed his wife at the station door He mounted to the cabin with the orders in his hand And he took his farewell trip to that promis'd land Chorus: Casey Jones--mounted to his cabin Casey Jones--with his orders in his hand Casey Jones--mounted to his cabin And he took his... land" }},{"_index":"music","_type":"songs","_id":"3","_score":0.06780553,"_source":{"artist": "Clarence Ashley","year": 1920,"name": "Walking Boss","styles": ["folk","protest"],"album": "Traditional","lyrics": "Walkin' boss Walkin' boss Walkin' boss I don't belong to you I belong I belong I belong To that steel driving crew Well you work one day Work one day Work one day Then go lay around the shanty two"}}]}}使用其他比較符
還有其他各種比較符可供使用。例如,找到所有 1900 年以前編寫的歌曲:
curl -XGET "http://localhost:9200/music/lyrics/_search?q=year:<1900此查詢將返回完整的 “Casey Jones” 和 “Walking Boss” 文檔。
限制字段
要限制您在結(jié)果中看到的字段,可將?fields?參數(shù)添加到您的查詢中:
curl -XGET "http://localhost:9200/music/lyrics/_search?q=year:>1900&fields=year"檢查搜索返回對象
清單 3 給出了 Elasticsearch 從前面的查詢返回的數(shù)據(jù)。
清單 3. 查詢結(jié)果
{"took": 6,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 2,"max_score": 1.0,"hits": [{"_index": "music","_type": "lyrics","_id": "1","_score": 1.0,"fields": {"year": [1920]}}, {"_index": "music","_type": "lyrics","_id": "3","_score": 1.0,"fields": {"year": [1909]}}]} }在結(jié)果中,Elasticsearch 提供了多個 JSON 對象。第一個對象包含請求的元數(shù)據(jù):看看該請求花了多少毫秒 (took) 和它是否超時 (timed_out)。_shards?字段需要考慮 Elasticsearch 是一個集群化服務的事實。甚至在這個單節(jié)點本地部署中,Elasticsearch 也在邏輯上被集群化為分片。
繼續(xù)查看清單 3 中的搜索結(jié)果,可以觀察到?hits?對象包含:
- total?字段,它會告訴您獲得了多少個結(jié)果
- max_score,用于全文搜索
- 實際結(jié)果
實際結(jié)果包含?fields?屬性,因為您將?fields?參數(shù)添加到了查詢中。否則,結(jié)果中會包含?source,而且包含完整的匹配文檔。_index、_type?和?_id?的用途不言自明;_score?指的是全文搜索命中長度。這 4 個字段始終會在結(jié)果中返回。
使用 JSON 查詢 DSL
基于查詢字符串的搜索很快會變得很復雜。對于更高級的查詢,Elasticsearch 提供了一種完全基于 JSON 的特定于領(lǐng)域的語言 (DSL)。例如,要搜索?album?值為?traditional?的每首歌曲,可創(chuàng)建一個包含以下內(nèi)容的 query.json 文件:
{"query" : {"match" : {"album" : "Traditional"}} }然后運行:
curl -XGET "http://localhost:9200/music/lyrics/_search" -d @query.json回頁首
從 Java 代碼使用 Elasticsearch
“Elasticsearch 強大功能會在通過語言 API 使用它時體現(xiàn)出來。”
Elasticsearch 強大功能會在通過語言 API 使用它時體現(xiàn)出來。現(xiàn)在我將介紹 Java API,您將從一個應用程序執(zhí)行搜索。請參見?下載?部分,獲取相關(guān)的示例代碼。該應用程序使用了 Spark 微型框架,所以可以很快設(shè)置它。
示例應用程序
為一個新項目創(chuàng)建一個目錄,然后運行(將該命令鍵入到一行上):
mvn archetype:generate -DgroupId=com.dw -DartifactId=es-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false要生成一個項目來在 Eclipse 中使用,可通過?cd?進入 Maven 創(chuàng)建的項目目錄,并運行?mvn eclipse:eclipse。
在 Eclipse 中,選擇?File?>?Import?>?Existing Project into Workspace。導航到您使用 Maven 的文件夾,選擇該項目,單擊?Finish。
在 Eclipse 中,您可以看到一個基本的 Java 項目布局,包括根目錄中的 pom.xml 文件和一個 com.dw.App.java 主要類文件。將您所需的依賴項添加到 pom.xml 文件中。清單 4 給出了完整的 pom.xml 文件。
清單 4. 完整的 pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.dw</groupId><artifactId>es-demo</artifactId><packaging>jar</packaging><version>1.0-SNAPSHOT</version><name>es-demo</name><url>http://maven.apache.org</url><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><compilerVersion>1.8</compilerVersion><source>1.8</source><target>1.8</target></configuration></plugin></plugins></build><dependencies><dependency><groupId>com.sparkjava</groupId><artifactId>spark-core</artifactId><version>2.3</version> </dependency> <dependency><groupId>com.sparkjava</groupId><artifactId>spark-template-freemarker</artifactId><version>2.3</version> </dependency> <dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>2.1.1</version> </dependency></dependencies> </project>清單 4 中的依賴項獲取 Spark 框架核心、Spark Freemarker 模板支持和 Elasticsearch。另請注意,我將?<source>?版本設(shè)置為 Java 8,Spark 需要該版本(因為它大量使用了 lambda)。
我不知道您的情況,但我不久前構(gòu)建了許多 RESTful 應用程序,所以為了改變以下步調(diào),您將為應用程序提供一個更加傳統(tǒng)的 “提交和加載 (submit-and-load)” UI。
在 Eclipse 中,在導航器中右鍵單擊項目,選擇?Configure?>?Convert to Maven Project,以便 Eclipse 可以解析 Maven 依賴項。轉(zhuǎn)到項目,右鍵單擊該項目,然后選擇?Maven?>?Update Project。
Java 客戶端配置
Elasticsearch 的 Java 客戶端非常強大;它可以建立一個嵌入式實例并在必要時運行管理任務。但我在這里將重點介紹如何運行針對您已運行的節(jié)點的應用程序任務。
運行一個 Java 應用程序和 Elasticsearch 時,有兩種操作模式可供使用。該應用程序可在 Elasticsearch 集群中扮演更加主動或更加被動的角色。在更加主動的情況下(稱為 Node Client),應用程序?qū)嵗龑募航邮照埱?#xff0c;確定哪個節(jié)點應處理該請求,就像正常節(jié)點所做的一樣。(應用程序甚至可以托管索引和處理請求。)另一種模式稱為 Transport Client,它將所有請求都轉(zhuǎn)發(fā)到另一個 Elasticsearch 節(jié)點,由后者來確定最終目標。
獲取 Transport Client
對于演示應用程序,(通過 App.java 中執(zhí)行的初始化)選擇 Transport Client,并保持 Elasticsearch 執(zhí)行最低級別的處理:
Client client = TransportClient.builder().build().addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300));如果連接到一個 Elasticsearch 集群,構(gòu)建器可以接受多個地址。(在本例中,您只有一個 localhost 節(jié)點。)連接到端口 9300,而不是像之前在 REST API 的 cURL 中一樣連接到 9200。Java 客戶端將會使用這個特殊端口,使用端口 9200 不起作用。(其他 Elasticsearch 客戶端,Python 客戶端就是其中之一,將會 使用 9200 來訪問 REST API。)
在服務器啟動時創(chuàng)建該客戶端,并在整個請求處理過程中使用它。Spark 通過 Mustache 模板引擎的 Java 實現(xiàn)來呈現(xiàn)該頁面,而且 Spark 定義了請求端點 — 但我不會太多地解釋這些簡單的用例。(請參見?參考資料,獲取 Spark 的詳細信息的鏈接。)
該應用程序的索引頁面顯示了 Java 客戶端的功能:
UI:
- 呈現(xiàn)現(xiàn)有歌曲的列表
- 提供一個添加歌曲的按鈕
- 實現(xiàn)按藝術(shù)家和歌詞進行搜索
- 返回突出顯示了匹配內(nèi)容的結(jié)果
搜索和處理結(jié)果
在清單 5 中,根 URL?/?被映射到 index.mustache 頁面。
清單 5. 基本搜索
Spark.get("/", (request, response) -> {SearchResponse searchResponse = client.prepareSearch("music").setTypes("lyrics").execute().actionGet();SearchHit[] hits = searchResponse.getHits().getHits();Map<String, Object> attributes = new HashMap<>();attributes.put("songs", hits);return new ModelAndView(attributes, "index.mustache");}, new MustacheTemplateEngine());清單 5 中的有趣部分始于:
SearchResponse searchResponse = client.prepareSearch("music").setTypes("lyrics").execute().actionGet();這一行顯示了搜索 API 的簡單用法。使用?prepareSearch?方法指定一個索引(在本例中為?music),然后執(zhí)行查詢。查詢基本上顯示為 “Give me all of the records in the?music?index.”。另外,將文檔類型設(shè)置為?lyrics,但在這個簡單用例中沒有必要這么做,因為索引僅包含一種文檔類型。在更大的應用程序,需要執(zhí)行這種設(shè)置。這個 API 調(diào)用類似于您之前看到的?curl -XGET "http://localhost:9200/music/lyrics/_search"?調(diào)用。
SearchResponse?對象包含有趣的功能(例如命中數(shù)量和評分),但就目前而言,您只想要一個結(jié)果數(shù)組,可使用searchResponse.getHits().getHits();?獲得它。
最后,將結(jié)果數(shù)組添加到視圖上下文中,并讓 Mustache 呈現(xiàn)它。Mustache 模板如下所示:
清單 6. index.mustache
<html> <body> <form name="" action="/search"><input type="text" name="artist" placeholder="Artist"></input><input type="text" name="query" placeholder="lyric"></input><button type="submit">Search</button> </form> <button οnclick="window.location='/add'">Add</button> <ul> {{#songs}}<li>{{id}} - {{getSource.name}} - {{getSource.year}}{{#getHighlightFields}} -{{#lyrics.getFragments}}{{#.}}{{{.}}}{{/.}}{{/lyrics.getFragments}}{{/getHighlightFields}}</li> {{/songs}} </ul></body> </html>突出顯示高級查詢和匹配內(nèi)容
要支持突出顯示更高級的查詢和匹配內(nèi)容,可以使用?/search,如下所示:
清單 7. 搜索和突出顯示
Spark.get("/search", (request, response) -> {SearchRequestBuilder srb = client.prepareSearch("music").setTypes("lyrics");String lyricParam = request.queryParams("query");QueryBuilder lyricQuery = null;if (lyricParam != null && lyricParam.trim().length() > 0){lyricQuery = QueryBuilders.matchQuery("lyrics", lyricParam);}String artistParam = request.queryParams("artist");QueryBuilder artistQuery = null;if (artistParam != null && artistParam.trim().length() > 0){artistQuery = QueryBuilders.matchQuery("artist", artistParam);}if (lyricQuery != null && artistQuery == null){srb.setQuery(lyricQuery).addHighlightedField("lyrics", 0, 0);} else if (lyricQuery == null && artistQuery != null){srb.setQuery(artistQuery);} else if (lyricQuery != null && artistQuery != null){srb.setQuery(QueryBuilders.andQuery(artistQuery, lyricQuery)).addHighlightedField("lyrics", 0, 0);}SearchResponse searchResponse = srb.execute().actionGet();SearchHit[] hits = searchResponse.getHits().getHits();Map<String, Object> attributes = new HashMap<>();attributes.put("songs", hits);return new ModelAndView(attributes, "index.mustache"); }, new MustacheTemplateEngine());在清單 7 中,要注意的第一個有趣的 API 用法是?QueryBuilders.matchQuery("lyrics", lyricParam);。這是您設(shè)置對?lyrics?字段的查詢的地方。另外要注意的是?QueryBuilders.andQuery(artistQuery, lyricQuery),它是將查詢的?artist?和?lyrics?部分合并到 AND 查詢中的一種方法。
.addHighlightedField("lyrics", 0, 0);?調(diào)用告訴 Elasticsearch 生成?lyrics?字段上的搜索命中突出顯示結(jié)果。第二和第三個參數(shù)分別指定無線大小的分段和無限數(shù)量的分段。
在呈現(xiàn)搜索結(jié)果時,將突出顯示結(jié)果放入 HTML 中。使用 Elasticsearch 就能生成有效的 HTML,使用?<em>?標記來突出顯示匹配字符串所在的位置。
插入文檔
讓我們來看看如何以編程方式將文檔插入索引中。清單 8 給出了添加過程。
清單 8. 插入索引中
Spark.post("/save", (request, response) -> {StringBuilder json = new StringBuilder("{");json.append("\"name\":\""+request.raw().getParameter("name")+"\",");json.append("\"artist\":\""+request.raw().getParameter("artist")+"\",");json.append("\"year\":"+request.raw().getParameter("year")+",");json.append("\"album\":\""+request.raw().getParameter("album")+"\",");json.append("\"lyrics\":\""+request.raw().getParameter("lyrics")+"\"}");IndexRequest indexRequest = new IndexRequest("music", "lyrics",UUID.randomUUID().toString());indexRequest.source(json.toString());IndexResponse esResponse = client.index(indexRequest).actionGet();Map<String, Object> attributes = new HashMap<>();return new ModelAndView(attributes, "index.mustache");}, new MustacheTemplateEngine());使用?StringBuilder?直接生成一個 JSON 字符串來創(chuàng)建它。在生產(chǎn)應用程序中,可使用 Boon 或 Jackson 等庫。
執(zhí)行 Elasticsearch 工作的部分是:
IndexRequest indexRequest = new IndexRequest("music", "lyrics", UUID.randomUUID().toString());在本例中,使用了 UUID 來生成 ID。
回頁首
結(jié)束語
您已快速掌握了如何從命令行和在 Java 應用程序中使用 Elasticsearch。您現(xiàn)在已經(jīng)熟悉了索引、查詢、突出顯示和多字段搜索。Elasticsearch 在一個相對容易使用的包中提供了大量的功能。作為一個項目,Elasticsearch 帶來了一些您可能也會感興趣的結(jié)果。具體地講,所謂的 ELK 堆棧,即 Elasticsearch、Logstash(用于日志管理)和 Kibana(用于報告/可視化),正在迅速發(fā)展。
回頁首
下載
| es-demo.zip | 15KB |
參考資料
學習
- Elasticsearch:訪問 Elastic 主頁來獲取豐富的信息和文檔。
- GitHub 上的 Elasticsearch:即 Elasticsearch GitHub 項目。
- Elasticsearch Java:了解 Elasticsearch Java API 的詳細應用范圍。
- “使用 ELK 堆棧集中化 IBM? Bluemix? 應用程序的日志”(Lee Tice 等人合著,developerWorks,2015 年 1 月):了解如何在 IBM Bluemix 中使用 ELK。
- “Java 微型框架入門,第 1 部分”(Matthew Tyson,Javaworld.com,2015 年 10 月)和 “Java 微型框架入門,第 3 部分:Spark”(Matthew Tyson,Javaworld.com,2016 年 1 月):深入了解 Java Spark Framework。
- developerWorks Java 技術(shù)專區(qū):這里有數(shù)百篇關(guān)于 Java 編程各個方面的文章。
總結(jié)
以上是生活随笔為你收集整理的在 Java 应用程序中使用 Elasticsearch: 高性能 RESTful 搜索引擎和文档存储快速入门指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSS的表白:七夕巧克力心
- 下一篇: Elasticsearch 教程--入门