适用于Java开发人员的Elasticsearch:Java的Elasticsearch
本文是我們學(xué)院課程的一部分,該課程的標(biāo)題為Java開發(fā)人員的Elasticsearch教程 。
在本課程中,我們提供了一系列教程,以便您可以開發(fā)自己的基于Elasticsearch的應(yīng)用程序。 我們涵蓋了從安裝和操作到Java API集成和報(bào)告的廣泛主題。 通過我們簡單易懂的教程,您將能夠在最短的時(shí)間內(nèi)啟動(dòng)并運(yùn)行自己的項(xiàng)目。 在這里查看 !
1.簡介
在本教程的上半部分,我們僅通過命令行工具通過利用其大量RESTful API來掌握與Elasticsearch建立有意義的對話的技能。 這是非常少的知識(shí),但是,當(dāng)您開發(fā)Java / JVM應(yīng)用程序時(shí),您將需要比命令行更好的選擇。 幸運(yùn)的是, Elasticsearch在這一領(lǐng)域提供了多種產(chǎn)品。
目錄
1.簡介 2.使用Java客戶端API 3.使用Java Rest客戶端 4.使用測試套件 5。結(jié)論 6.接下來在本教程的這一部分中,我們將學(xué)習(xí)如何通過本地Java API與Elasticsearch進(jìn)行通信。 我們的方法是編寫代碼并在幾個(gè)Java應(yīng)用程序上工作,使用Apache Maven進(jìn)行構(gòu)建管理,使用出色的Spring Framework進(jìn)行依賴關(guān)系接線和控制反轉(zhuǎn) ,并使用出色的JUnit / AssertJ作為測試支架。
2.使用Java客戶端API
從早期版本開始, Elasticsearch隨每個(gè)發(fā)行版一起分發(fā)專用的Java客戶端API ,也稱為傳輸客戶端。 它談到了Elasticsearch本機(jī)傳輸協(xié)議,因此施加了這樣的約束:客戶端庫的版本應(yīng)至少與您使用的Elasticsearch發(fā)行版的主要版本匹配(理想情況下,客戶端應(yīng)具有完全相同的版本)。
當(dāng)我們使用Elasticsearch版本5.2.0 ,將相應(yīng)的客戶端版本依賴項(xiàng)添加到我們的pom.xml文件中是有意義的。
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>transport</artifactId><version>5.2.0</version> </dependency>由于我們選擇了Spring Framework來驅(qū)動(dòng)我們的應(yīng)用程序,因此實(shí)際上我們唯一需要的就是傳輸客戶端配置。
@Configuration public class ElasticsearchClientConfiguration {@Bean(destroyMethod = "close")TransportClient transportClient() throws UnknownHostException {return new PreBuiltTransportClient(Settings.builder()-.put(ClusterName.CLUSTER_NAME_SETTING.getKey(), "es-catalog").build()).addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300));} }PreBuiltTransportClient遵循構(gòu)建器模式 (與我們很快將要看到的大多數(shù)類一樣)來構(gòu)造TransportClient實(shí)例,一旦存在,我們就可以使用Spring Framework支持的注入技術(shù)來訪問它:
@Autowired private TransportClient client;CLUSTER_NAME_SETTING值得我們注意:它應(yīng)該與我們要連接的Elasticsearch集群的名稱完全匹配,在本例中為es-catalog 。
太好了,我們已經(jīng)初始化了傳輸客戶端,那么該如何處理呢? 本質(zhì)上,傳輸客戶端公開了很多方法(遵循流暢的界面樣式),以打開對Java代碼中所有Elasticsearch API的訪問。 要邁出第一步,應(yīng)該注意的是,傳輸客戶端在常規(guī)API和管理API之間有明確的分隔。 后者可以通過在傳輸客戶端實(shí)例上調(diào)用admin()方法獲得。
在翻開袖子弄臟手之前,有必要提到, Elasticsearch Java API設(shè)計(jì)為完全異步的,因此它們圍繞兩個(gè)關(guān)鍵抽象: ActionFuture<?>和ListenableActionFuture<?> 。 實(shí)際上, ActionFuture<?>只是一個(gè)普通的Java Future <?> ,其中添加了一些少數(shù)方法,請繼續(xù)關(guān)注。 另一方面, ListenableActionFuture<?>是更強(qiáng)大的抽象,具有執(zhí)行回調(diào)并將執(zhí)行結(jié)果通知調(diào)用方的能力。
選擇一種樣式完全取決于您的應(yīng)用程序需求,因?yàn)檫@兩種樣式都有其優(yōu)缺點(diǎn)。 事不宜遲,讓我們繼續(xù)前進(jìn),確保我們的Elasticsearch集群運(yùn)行狀況良好并已準(zhǔn)備就緒。
final ClusterHealthResponse response = client.admin().cluster().health(Requests.clusterHealthRequest().waitForGreenStatus().timeout(TimeValue.timeValueSeconds(5))).actionGet();assertThat(response.isTimedOut()).withFailMessage("The cluster is unhealthy: %s", response.getStatus()).isFalse();該示例非常簡單明了。 我們要做的是向Elasticsearch集群查詢其狀態(tài),同時(shí)明確要求最多等待5 seconds以使?fàn)顟B(tài)變?yōu)間reen (如果不是這種情況)。 在client.admin().cluster().health(...) , client.admin().cluster().health(...)返回ActionFuture<?>所以我們必須調(diào)用actionGet方法之一來獲取響應(yīng)。
這是使用Elasticsearch Java API的另一種稍有不同的方式,這次使用了prepareXxx方法家族。
final ClusterHealthResponse response = client.admin().cluster().prepareHealth().setWaitForGreenStatus().setTimeout(TimeValue.timeValueSeconds(5)).execute().actionGet();assertThat(response.isTimedOut()).withFailMessage("The cluster is unhealthy: %s", response.getStatus()).isFalse();盡管這兩個(gè)代碼段均導(dǎo)致絕對相同的結(jié)果,但后一個(gè)代碼段是在鏈的末尾調(diào)用client.admin().cluster().prepareHealth().execute()方法,該方法返回ListenableActionFuture<?> 。 在這個(gè)例子中,它并沒有太大的區(qū)別,但是請牢記這一點(diǎn),因?yàn)槲覀儗⒖吹礁嘤腥さ挠美?#xff0c;其中這樣的細(xì)節(jié)實(shí)際上會(huì)改變游戲規(guī)則。
最后,最后但并非最不重要的一點(diǎn)是,任何API的異步特性(并且Elasticsearch Java API也不例外)假定對該操作的調(diào)用將花費(fèi)一些時(shí)間,并且調(diào)用者有責(zé)任決定如何處理該操作。 到目前為止,我們僅在ActionFuture<?>實(shí)例上調(diào)用actionGet ,它將有效地將異步執(zhí)行轉(zhuǎn)換為阻塞(或ActionFuture<?> ,同步)調(diào)用。 此外,我們沒有在同意放棄之前等待執(zhí)行完成的時(shí)間方面指定期望。 我們可以做得更好,在本節(jié)的其余部分中,我們將解決這兩點(diǎn)。
一旦我們的Elasticsearch集群狀態(tài)全部變?yōu)間reen ,就該創(chuàng)建一些索引了,就像我們在本教程的上一部分中所做的一樣,但是這次僅使用Java API。 創(chuàng)建catalog索引之前,最好確保catalog索引尚不存在。
final IndicesExistsResponse response = client.admin().indices().prepareExists("catalog").get(TimeValue.timeValueMillis(100));if (!response.isExists()) {... }請注意,在上面的代碼段中,我們提供了完成操作的顯式超時(shí)get(TimeValue.timeValueMillis(100)) ,這實(shí)際上是execute().actionGet(TimeValue.timeValueMillis(100))的快捷方式。
對于catalog索引設(shè)置和映射類型,我們將使用與本教程上半部分相同的JSON文件catalog-index.json 。 我們將遵循Apache Maven約定將其放置在src/test/resources文件夾中。
@Value("classpath:catalog-index.json") private Resource index;幸運(yùn)的是, Spring Framework大大簡化了對類路徑資源的注入,因此我們在這里不需要做太多事情就可以訪問catalog-index.json內(nèi)容并將其直接提供給Elasticsearch Java API。
try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {Streams.copy(index.getInputStream(), out);final CreateIndexResponse response = client.admin().indices().prepareCreate("catalog").setSource(out.toByteArray()).setTimeout(TimeValue.timeValueSeconds(1)).get(TimeValue.timeValueSeconds(2));assertThat(response.isAcknowledged()).withFailMessage("The index creation has not been acknowledged").isTrue(); }該代碼塊說明了利用setSource方法調(diào)用來處理Elasticsearch Java API的另一種方法。 簡而言之,我們只是以不透明的Blob(或字符串)的形式自己提供請求有效負(fù)載,并將其按原樣發(fā)送到Elasticsearch節(jié)點(diǎn)。 但是,我們可以改用純Java數(shù)據(jù)結(jié)構(gòu),例如:
final CreateIndexResponse response = client.admin().indices().prepareCreate("catalog").setSettings(...).setMapping("books", ...).setMapping("authors", ...).setTimeout(TimeValue.timeValueSeconds(1)).get(TimeValue.timeValueSeconds(2));好的,到此,我們將結(jié)束傳輸客戶端管理API并切換到文檔和搜索API,因?yàn)槟ǔ?huì)使用這些API。 我們記得, Elasticsearch說的是JSON,因此我們必須以某種方式使用Java將書籍和作者轉(zhuǎn)換為JSON表示形式。 實(shí)際上, Elasticsearch Java API通過支持對名為XContent的內(nèi)容的通用抽象來提供XContent ,例如:
final XContentBuilder source = JsonXContent.contentBuilder().startObject().field("title", "Elasticsearch: The Definitive Guide. ...").startArray("categories").startObject().field("name", "analytics").endObject().startObject().field("name", "search").endObject().startObject().field("name", "database store").endObject().endArray().field("publisher", "O'Reilly").field("description", "Whether you need full-text search or ...").field("published_date", new LocalDate(2015, 02, 07).toDate()).field("isbn", "978-1449358549").field("rating", 4).endObject();具有文檔表示形式后,我們可以將其發(fā)送給Elasticsearch進(jìn)行索引。 為了兌現(xiàn)承諾,這次我們希望采用真正的異步方式,而不是等待響應(yīng),而是以ActionListener<IndexResponse>的形式提供通知回調(diào)。
client.prepareIndex("catalog", "books").setId("978-1449358549").setContentType(XContentType.JSON).setSource(source).setOpType(OpType.INDEX).setRefreshPolicy(RefreshPolicy.WAIT_UNTIL).setTimeout(TimeValue.timeValueMillis(100)).execute(new ActionListener() {@Overridepublic void onResponse(IndexResponse response) {LOG.info("The document has been indexed with the result: {}", response.getResult());}@Overridepublic void onFailure(Exception ex) {LOG.error("The document has been not been indexed", ex);}});很好,所以我們在books有了第一個(gè)文件! 那authors呢? 提醒一下,這本書有多個(gè)作者,因此是使用文檔批量索引的絕佳時(shí)機(jī)。
final XContentBuilder clintonGormley = JsonXContent.contentBuilder().startObject().field("first_name", "Clinton").field("last_name", "Gormley").endObject();final XContentBuilder zacharyTong = JsonXContent.contentBuilder().startObject().field("first_name", "Zachary").field("last_name", "Tong").endObject();XContent部分很清楚,坦白地說,您可能永遠(yuǎn)都不會(huì)使用這種選項(xiàng),而是希望對真實(shí)的類進(jìn)行建模,并使用一種出色的Java庫來自動(dòng)進(jìn)行JSON轉(zhuǎn)換。 但是以下片段非常有趣。
final BulkResponse response = client.prepareBulk().add(Requests.indexRequest("catalog").type("authors").id("1").source(clintonGormley).parent("978-1449358549").opType(OpType.INDEX)).add(Requests.indexRequest("catalog").type("authors").id("2").source(zacharyTong).parent("978-1449358549").opType(OpType.INDEX)).setRefreshPolicy(RefreshPolicy.WAIT_UNTIL).setTimeout(TimeValue.timeValueMillis(500)).get(TimeValue.timeValueSeconds(1));assertThat(response.hasFailures()).withFailMessage("Bulk operation reported some failures: %s", response.buildFailureMessage()).isFalse();我們正在單批發(fā)送兩個(gè)針對authors集合的索引請求。 您可能想知道這個(gè)parent("978-1449358549")含義,要回答這個(gè)問題,我們必須回想起, books和authors是使用父母/子女關(guān)系建模的。 因此,在這種情況下, parent鍵是對books各個(gè)父文檔的引用(通過_id屬性)。
做得好,所以我們知道如何使用索引以及如何使用Elasticsearch傳輸客戶端Java API對文檔建立索引。 現(xiàn)在是搜索時(shí)間!
final SearchResponse response = client.prepareSearch("catalog").setTypes("books").setSearchType(SearchType.DFS_QUERY_THEN_FETCH).setQuery(QueryBuilders.matchAllQuery()).setFrom(0).setSize(10).setTimeout(TimeValue.timeValueMillis(100)).get(TimeValue.timeValueMillis(200));assertThat(response.getHits().hits()).withFailMessage("Expecting at least one book to be returned").isNotEmpty();可以提出的最簡單的搜索標(biāo)準(zhǔn)是匹配所有文檔,這就是我們在上面的摘錄中所做的(請注意,我們明確將返回的結(jié)果數(shù)限制為10文檔)。
幸運(yùn)的是, Elasticsearch Java API以QueryBuilders和QueryBuilder類的形式全面實(shí)現(xiàn)了Query DSL ,因此編寫(和維護(hù))復(fù)雜的查詢非常容易。 作為練習(xí),我們將構(gòu)建與本教程的上一部分相同的復(fù)合查詢:
final QueryBuilder query = QueryBuilders.boolQuery().must(QueryBuilders.rangeQuery("rating").gte(4)).must(QueryBuilders.nestedQuery("categories", QueryBuilders.matchQuery("categories.name", "analytics"),ScoreMode.Total)).must(QueryBuilders.hasChildQuery("authors", QueryBuilders.termQuery("last_name", "Gormley"),ScoreMode.Total));該代碼看起來漂亮,簡潔,易于閱讀。 如果您熱衷于使用Java編程語言的靜態(tài)導(dǎo)入功能,則查詢看起來會(huì)更加緊湊。
final SearchResponse response = client.prepareSearch("catalog").setTypes("books").setSearchType(SearchType.DFS_QUERY_THEN_FETCH).setQuery(query).setFrom(0).setSize(10).setFetchSource(new String[] { "title", "publisher" }, /* includes */ new String[0] /* excludes */).setTimeout(TimeValue.timeValueMillis(100)).get(TimeValue.timeValueMillis(200));assertThat(response.getHits().hits()).withFailMessage("Expecting at least one book to be returned").extracting("sourceAsString", String.class).hasOnlyOneElementSatisfying(source -> {assertThat(source).contains("Elasticsearch: The Definitive Guide.");});為了使兩個(gè)版本的查詢保持相同,我們還通過setFetchSource方法提示搜索請求,我們只對返回文檔源的title和Publisher屬性感興趣。
好奇的讀者可能想知道如何將聚合與搜索請求一起使用。 這是一個(gè)非常好的話題,所以讓我們先討論一下。 與Query DSL一起 , Elasticsearch Java API還提供了聚合DSL ,圍繞AggregationBuilders和AggregationBuilder類展開。 例如,這就是我們可以通過publisher屬性構(gòu)建存儲(chǔ)桶聚合的方法。
final AggregationBuilder aggregation = AggregationBuilders.terms("publishers").field("publisher").size(10);定義好聚合之后,我們可以使用addAggregation方法調(diào)用將它們注入搜索請求中,如下面的代碼片段所示:
final SearchResponse response = client.prepareSearch("catalog").setTypes("books").setSearchType(SearchType.DFS_QUERY_THEN_FETCH).setQuery(QueryBuilders.matchAllQuery()).addAggregation(aggregation).setFrom(0).setSize(10).setTimeout(TimeValue.timeValueMillis(100)).get(TimeValue.timeValueMillis(200));final StringTerms publishers = response.getAggregations().get("publishers"); assertThat(publishers.getBuckets()).extracting("keyAsString", String.class).contains("O'Reilly");匯總的結(jié)果在響應(yīng)中可用,并且可以通過引用匯總名稱(例如本例中的publishers來檢索。 但是要謹(jǐn)慎并謹(jǐn)慎使用正確的聚合類型,以免以ClassCastException的形式出現(xiàn)意外。 因?yàn)橐呀?jīng)定義了發(fā)布者聚合來將術(shù)語分組到存儲(chǔ)桶中,所以我們可以安全地將其從響應(yīng)轉(zhuǎn)換為StringTerms類實(shí)例。
3.使用Java Rest客戶端
與使用Elasticsearch Java客戶端API相關(guān)的缺點(diǎn)之一是要求與您正在運(yùn)行的Elasticsearch版本(獨(dú)立版本或集群版本)二進(jìn)制兼容。
幸運(yùn)的是,自5.0.0分支的第一個(gè)版本發(fā)布以來, Elasticsearch在表上帶來了另一個(gè)選擇: Java REST client 。 它使用HTTP協(xié)議來傾訴Elasticsearch通過調(diào)用它的RESTful API端點(diǎn),是無視的版本Elasticsearch (從字面上看,它是兼容所有Elasticsearch版本)。
應(yīng)當(dāng)指出的是,盡管Java REST客戶端相當(dāng)?shù)图?jí),并且使用起來不像Java客戶端API那樣方便,但實(shí)際上并非如此。 但是,出于很多原因,人們可能更喜歡使用Java REST客戶端而不是Java客戶端API與Elasticsearch進(jìn)行通信,因此值得進(jìn)行自己的討論。 首先,讓我們將相應(yīng)的依賴項(xiàng)包含到我們的Apache Maven pom.xml文件中。
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>rest</artifactId><version>5.2.0</version> </dependency>從配置的角度來看,我們只需要通過調(diào)用RestClient.builder方法來構(gòu)造RestClient的實(shí)例。
@Configuration public class ElasticsearchClientConfiguration {@Bean(destroyMethod = "close")RestClient transportClient() {return RestClient.builder(new HttpHost("localhost", 9200)).setRequestConfigCallback(new RequestConfigCallback() {@Overridepublic Builder customizeRequestConfig(Builder builder) {return builder.setConnectTimeout(1000).setSocketTimeout(5000);}}).build();} }我們在這里取得了一些進(jìn)步,但是請?zhí)貏e注意正確超時(shí)的配置,因?yàn)镴ava REST客戶端沒有提供(至少目前)基于每個(gè)請求級(jí)別指定超時(shí)的方法。 這樣,我們可以使用Spring Framework為我們提供的相同接線技術(shù),在任何地方注入RestClient實(shí)例:
@Autowired private RestClient client;為了公平地比較Java客戶端API和Java REST客戶端 ,我們將剖析上一部分中看到的幾個(gè)示例,并通過檢查Elasticsearch集群運(yùn)行狀況來確定階段。
@Test public void esClusterIsHealthy() throws Exception {final Response response = client.performRequest(HttpGet.METHOD_NAME, "_cluster/health", emptyMap());final Object json = defaultConfiguration().jsonProvider().parse(EntityUtils.toString(response.getEntity()));assertThat(json, hasJsonPath("$.status", equalTo("green"))); }確實(shí),差異是顯而易見的。 您可能會(huì)猜到, Java REST客戶端實(shí)際上是更通用,知名和受人尊敬的Apache Http Client庫的一個(gè)瘦包裝。 響應(yīng)以字符串或字節(jié)數(shù)組的形式返回,調(diào)用者有責(zé)任將其轉(zhuǎn)換為JSON并提取必要的數(shù)據(jù)。 為了在測試斷言中處理該問題,我們已經(jīng)啟用了出色的JsonPath庫,但是您可以在這里自由選擇。
一組performRequest方法是使用Java REST客戶端 API進(jìn)行同步(或阻止)通信的典型方法。 另外,還有一類performRequestAsync方法,應(yīng)該在完全異步的流中使用。 在下一個(gè)示例中,我們將使用其中之一來將文檔編books 。
用Java語言表示類似于JSON的結(jié)構(gòu)的最簡單方法是使用普通的舊Map<String, Object>如下面的代碼片段所示。
final Map<String, Object> source = new LinkedHashMap<>(); source.put("title", "Elasticsearch: The Definitive Guide. ..."); source.put("categories", new Map[] {singletonMap("name", "analytics"),singletonMap("name", "search"),singletonMap("name", "database store")} ); source.put("publisher", "O'Reilly"); source.put("description", "Whether you need full-text search or ..."); source.put("published_date", "2015-02-07"); source.put("isbn", "978-1449358549"); source.put("rating", 4);現(xiàn)在,我們需要將此Java結(jié)構(gòu)轉(zhuǎn)換為有效的JSON字符串。 這樣做有很多方法,但是我們將利用json-smart庫,因?yàn)樗呀?jīng)可以作為JsonPath庫的傳遞依賴項(xiàng)使用 。
final HttpEntity payload = new NStringEntity(JSONObject.toJSONString(source), ContentType.APPLICATION_JSON);準(zhǔn)備好有效負(fù)載后,沒有什么可以阻止我們調(diào)用Elasticsearch的 Indexing API將一本書添加到books集合中。
client.performRequestAsync(HttpPut.METHOD_NAME, "catalog/books/978-1449358549",emptyMap(),payload,new ResponseListener() {@Overridepublic void onSuccess(Response response) {LOG.info("The document has been indexed successfully");}@Overridepublic void onFailure(Exception ex) {LOG.error("The document has been not been indexed", ex);}});這次我們決定不等待響應(yīng),而是提供一個(gè)回調(diào)( ResponseListener實(shí)例),以保持流真正異步。 最后,最好了解執(zhí)行或多或少切合實(shí)際的搜索請求并解析結(jié)果所需的內(nèi)容。
如您所料, Java REST客戶端不提供圍繞Query DSL的任何流暢的API,因此我們不得不再回退一次Map<String, Object>以便構(gòu)建搜索條件。
final Map<String, Object> authors = new LinkedHashMap<>(); authors.put("type", "authors"); authors.put("query", singletonMap("term",singletonMap("last_name", "Gormley")) );final Map<String, Object> categories = new LinkedHashMap<>(); categories.put("path", "categories"); categories.put("query",singletonMap("match", singletonMap("categories.name", "search")) );final Map<String, Object> query = new LinkedHashMap<>(); query.put("size", 10); query.put("_source", new String[] { "title", "publisher" }); query.put("query", singletonMap("bool",singletonMap("must", new Map[] {singletonMap("range",singletonMap("rating", singletonMap("gte", 4))),singletonMap("has_child", authors),singletonMap("nested", categories)})) );公開解決問題需要付出的代價(jià)是編寫許多繁瑣且容易出錯(cuò)的代碼。 在這方面, Java客戶端API的一致性和簡潔性確實(shí)產(chǎn)生了巨大的差異。 您可能會(huì)爭辯說,實(shí)際上可能有人依賴更簡單,更安全的技術(shù),例如數(shù)據(jù)傳輸對象 , 值對象 ,或者甚至具有帶有占位符的JSON搜索查詢模板,但重點(diǎn)是Java REST客戶端在此提供了一些幫助。時(shí)刻。
final HttpEntity payload = new NStringEntity(JSONObject.toJSONString(query), ContentType.APPLICATION_JSON);final Response response = client.performRequest(HttpPost.METHOD_NAME, "catalog/books/_search", emptyMap(), payload);final Object json = defaultConfiguration().jsonProvider().parse(EntityUtils.toString(response.getEntity()));assertThat(json, hasJsonPath("$.hits.hits[0]._source.title", containsString("Elasticsearch: The Definitive Guide.")));在此處添加的內(nèi)容不多,只需查閱格式的Search API文檔,然后從響應(yīng)中提取您感興趣的詳細(xì)信息,就像我們通過聲明_source的title property所做的那樣。
到此,我們結(jié)束了關(guān)于Java REST client的討論。 坦率地說,與選擇Java生態(tài)系統(tǒng)所具有的通用HTTP客戶端之一相比,使用它是否有任何好處還不清楚。 確實(shí),這確實(shí)是一個(gè)令人擔(dān)憂的問題,但是請記住, Java REST客戶端是Elasticsearch系列的新成員,希望我們很快就會(huì)看到很多激動(dòng)人心的功能。
4.使用測試套件
隨著我們的應(yīng)用程序變得越來越復(fù)雜和分散,正確的測試變得前所未有的重要。 多年來, Elasticsearch提供了卓越的測試工具 ,以簡化嚴(yán)重依賴其搜索和分析功能的應(yīng)用程序的測試。 更具體地說,您的項(xiàng)目中可能需要兩種測試:
- 單元測試 :那些正在獨(dú)立測試單個(gè)單元(例如fe類)的測試,通常不需要具有正在運(yùn)行的Elasticsearch節(jié)點(diǎn)或集群。 這些測試由ESTestCase和ESTokenStreamTestCase支持。
- 集成測試 :這些測試正在測試完整的流程,通常需要至少一個(gè)運(yùn)行的Elasticsearch節(jié)點(diǎn)(或集群,以強(qiáng)調(diào)更實(shí)際的場景)。 這些測試由ESIntegTestCase , ESSingleNodeTestCase和ESBackCompatTestCase 。
讓我們再翻一次袖子,學(xué)習(xí)如何使用Elasticsearch提供的測試支架來開發(fā)我們自己的測試套件。 我們將從聲明依賴關(guān)系開始,仍然使用Apache Maven 。
<dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-test-framework</artifactId><version>6.4.0</version><scope>test</scope> </dependency><dependency><groupId>org.elasticsearch.test</groupId><artifactId>framework</artifactId><version>5.2.0</version><scope>test</scope> </dependency>盡管這不是絕對必要的,但我們還將顯式依賴項(xiàng)添加到JUnit ,將其版本提高到4.12 。
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope><exclusions><exclusion><groupId>org.hamcrest</groupId><artifactId>hamcrest-core</artifactId></exclusion></exclusions> </dependency>我們在這里需要提請注意: Elasticsearch測試框架對依賴項(xiàng)異常敏感,確保您的應(yīng)用程序不會(huì)陷入每個(gè)Java開發(fā)人員都熟知的jar hell的問題 。 Elasticsearch測試框架所做的一項(xiàng)預(yù)檢查是確保classpath中沒有重復(fù)的類。 通常,您可能會(huì)在此過程中使用其他出色的測試庫,但如果您的Elasticsearch測試用例突然開始無法通過初始化階段,則很可能是由于檢測到j(luò)ar地獄問題,因此必須進(jìn)行一些排除。
還有一件事,很可能您需要在測試運(yùn)行期間通過將tests.security.manager屬性設(shè)置為false來關(guān)閉安全管理器。 可以通過將-Dtests.security.manager=false參數(shù)直接傳遞給JVM或使用Apache Maven插件配置來完成。
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.19.1</version><configuration><argLine>-Dtests.security.manager=false</argLine></configuration> </plugin>太棒了,所有前提條件都得到了解釋,我們都準(zhǔn)備開始開發(fā)第一個(gè)測試用例。 適用于Elasticsearch的上下文中的單元測試對于測試您自己的分析器 , 令牌生成 器 , 令牌過濾器和字符過濾器非常有用。 在這方面,我們沒有做太多事情,但是集成測試是一個(gè)截然不同的故事。 讓我們看看如何啟動(dòng)具有3節(jié)點(diǎn)的Elasticsearch集群。
@ClusterScope(numDataNodes = 3) public class ElasticsearchClusterTest extends ESIntegTestCase { }……從字面上看,就是這樣。 當(dāng)然,盡管群集已啟動(dòng),但它沒有索引或未預(yù)先配置的內(nèi)容。 讓我們添加一些測試背景,以使用相同的catalog-index.json文件創(chuàng)建catalog索引及其映射類型。
@Before public void setUpCatalog() throws IOException {try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {Streams.copy(getClass().getResourceAsStream("/catalog-index.json"), out);final CreateIndexResponse response = admin().indices().prepareCreate("catalog").setSource(out.toByteArray()).get();assertAcked(response);ensureGreen("catalog");} }如果您已經(jīng)識(shí)別出此代碼,那是因?yàn)槲覀兪褂玫氖侵傲私獾南嗤瑐鬏斂蛻舳?#xff01; 如果您需要Java REST客戶端實(shí)例, Elasticsearch測試腳手架會(huì)在client()或admin()方法之后為您提供該功能,并與getRestClient()一起提供。 每次測試運(yùn)行后清理集群都是一件好事,幸運(yùn)的是,我們可以使用cluster()方法來訪問幾個(gè)非常有用的操作,例如:
@After public void tearDownCatalog() throws IOException, InterruptedException {cluster().wipeIndices("catalog"); }總體而言, Elasticsearch測試工具的目標(biāo)是兩個(gè)目標(biāo):簡化最常見的任務(wù)(我們已經(jīng)看到了client() , admin() , cluster()實(shí)際運(yùn)行)以及輕松進(jìn)行驗(yàn)證,聲明或期望(例如, ensureGreen(...) , assertAcked(...) )。 官方文檔有專門的部分介紹了輔助方法和斷言,因此請看一看。
首先,空索引中應(yīng)該沒有文檔,因此我們的第一個(gè)測試用例將明確聲明這一事實(shí)。
@Test public void testEmptyCatalogHasNoBooks() {final SearchResponse response = client().prepareSearch("catalog").setTypes("books").setSearchType(SearchType.DFS_QUERY_THEN_FETCH).setQuery(QueryBuilders.matchAllQuery()).setFetchSource(false).get();assertNoSearchHits(response); }一個(gè)簡單,但是創(chuàng)建真實(shí)文檔呢? Elasticsearch測試框架具有多種有用的方法,可以為大多數(shù)類型生成隨機(jī)值。 我們可以利用它來創(chuàng)建一本書,將其添加到圖書catalog索引中并對其發(fā)出查詢。
@Test public void testInsertAndSearchForBook() throws IOException {final XContentBuilder source = JsonXContent.contentBuilder().startObject().field("title", randomAsciiOfLength(100)).startArray("categories").startObject().field("name", "analytics").endObject().startObject().field("name", "search").endObject().startObject().field("name", "database store").endObject().endArray().field("publisher", randomAsciiOfLength(20)).field("description", randomAsciiOfLength(200)).field("published_date", new LocalDate(2015, 02, 07).toDate()).field("isbn", "978-1449358549").field("rating", randomInt(5)).endObject();index("catalog", "books", "978-1449358549", source);refresh("catalog");final QueryBuilder query = QueryBuilders.nestedQuery("categories", QueryBuilders.matchQuery("categories.name", "analytics"),ScoreMode.Total);final SearchResponse response = client().prepareSearch("catalog").setTypes("books").setSearchType(SearchType.DFS_QUERY_THEN_FETCH).setQuery(query).setFetchSource(false).get();assertSearchHits(response, "978-1449358549"); }如您所見,除了categories之外,大多數(shù)書籍屬性都是隨機(jī)生成的,因此我們可以通過它們可靠地進(jìn)行搜索。
Elasticsearch測試支持提供了許多有趣的機(jī)會(huì),不僅可以測試成功的結(jié)果,而且可以模擬現(xiàn)實(shí)的集群行為和錯(cuò)誤的條件(這里的internalCluster()提供的輔助方法非常有用)。 對于像Elasticsearch這樣的復(fù)雜分布式系統(tǒng),此類測試的價(jià)值是無價(jià)的,因此請利用可用的選項(xiàng)來確保部署到生產(chǎn)中的代碼健壯并能夠應(yīng)對故障。 舉個(gè)簡單的例子,我們可以在運(yùn)行搜索請求時(shí)關(guān)閉隨機(jī)數(shù)據(jù)節(jié)點(diǎn),并斷言它們?nèi)栽谔幚碇小?
@Test public void testClusterNodeIsDown() throws IOException {internalCluster().stopRandomDataNode();final SearchResponse response = client().prepareSearch("catalog").setTypes("books").setSearchType(SearchType.DFS_QUERY_THEN_FETCH).setQuery(QueryBuilders.matchAllQuery()).setFetchSource(false).get();assertNoSearchHits(response); }我們只是簡單介紹了Elasticsearch測試工具的功能。 希望您在組織中實(shí)踐測試驅(qū)動(dòng)的開發(fā),并且我們所研究的示例可以很好地為您提供起點(diǎn)。
5。結(jié)論
在本教程的這一部分中,我們學(xué)習(xí)了Elasticsearch開箱即用提供的兩種Java客戶端API: Transport客戶端和REST客戶端 。 您可能會(huì)發(fā)現(xiàn)很難選擇要使用哪種Java客戶端API,但是總的來說,它高度依賴于應(yīng)用程序。 在大多數(shù)情況下, 傳輸客戶端是最佳選擇,但是,如果您的項(xiàng)目僅使用幾個(gè)Elasticsearch API(或功能的非常有限的子集),則REST客戶端可能是一個(gè)更好的選擇。 另外,我們不要忘記Java REST客戶端是相當(dāng)新的,并且肯定會(huì)在將來的版本中進(jìn)行改進(jìn),因此請密切注意它。
當(dāng)我們剖析傳輸客戶端時(shí) ,已經(jīng)指出了它的完全異步性質(zhì)。 盡管這絕對是一件好事,但我們已經(jīng)看到它基于回調(diào)(更確切地說是偵聽器),這可能會(huì)Swift導(dǎo)致稱為回調(diào)hell的問題。 強(qiáng)烈建議盡早解決此問題(幸運(yùn)的是,有很多庫和可用的替代品,例如RxJava 2和Project Reactor , Java 9也在趕上)。
最后但并非最不重要的一點(diǎn)是,我們?yōu)g覽了Elasticsearch的 測試工具 ,并有機(jī)會(huì)認(rèn)識(shí)到它為Java / JVM開發(fā)人員提供的巨大幫助。
6.接下來
在接下來的部分 ,即教程的最后一部分 ,我們將討論圍繞Elasticsearch的出色項(xiàng)目的生態(tài)系統(tǒng)。 希望您再一次對Elasticsearch的功能感到驚訝,為您打開它的適用性的新視野。
所有項(xiàng)目的完整源代碼都可以下載: elasticsearch-client-rest , elasticsearch-testing , elasticsearch-client-java
翻譯自: https://www.javacodegeeks.com/2017/03/elasticsearch-java-developers-elasticsearch-java.html
總結(jié)
以上是生活随笔為你收集整理的适用于Java开发人员的Elasticsearch:Java的Elasticsearch的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: http缓存管理器_小心缓存管理器
- 下一篇: java美元兑换,(Java实现) 美元