适用于Java开发人员的Elasticsearch:Java的Elasticsearch
本文是我們學院課程的一部分,該課程的標題為Java開發人員的Elasticsearch教程 。
在本課程中,我們提供了一系列教程,以便您可以開發自己的基于Elasticsearch的應用程序。 我們涵蓋了從安裝和操作到Java API集成和報告的廣泛主題。 通過我們簡單易懂的教程,您將能夠在最短的時間內啟動并運行自己的項目。 在這里查看 !
1.簡介
在本教程的上半部分,我們僅通過命令行工具通過利用其大量RESTful API來掌握與Elasticsearch建立有意義的對話的技能。 這是非常少的知識,但是,當您開發Java / JVM應用程序時,您將需要比命令行更好的選擇。 幸運的是, Elasticsearch在這一領域提供了多種產品。
目錄
1.簡介 2.使用Java客戶端API 3.使用Java Rest客戶端 4.使用測試套件 5。結論 6.接下來在本教程的這一部分中,我們將學習如何通過本地Java API與Elasticsearch進行通信。 我們的方法是編寫代碼并在幾個Java應用程序上工作,使用Apache Maven進行構建管理,使用出色的Spring Framework進行依賴關系接線和控制反轉 ,并使用出色的JUnit / AssertJ作為測試支架。
2.使用Java客戶端API
從早期版本開始, Elasticsearch隨每個發行版一起分發專用的Java客戶端API ,也稱為傳輸客戶端。 它談到了Elasticsearch本機傳輸協議,因此施加了這樣的約束:客戶端庫的版本應至少與您使用的Elasticsearch發行版的主要版本匹配(理想情況下,客戶端應具有完全相同的版本)。
當我們使用Elasticsearch版本5.2.0 ,將相應的客戶端版本依賴項添加到我們的pom.xml文件中是有意義的。
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>transport</artifactId><version>5.2.0</version> </dependency>由于我們選擇了Spring Framework來驅動我們的應用程序,因此實際上我們唯一需要的就是傳輸客戶端配置。
@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遵循構建器模式 (與我們很快將要看到的大多數類一樣)來構造TransportClient實例,一旦存在,我們就可以使用Spring Framework支持的注入技術來訪問它:
@Autowired private TransportClient client;CLUSTER_NAME_SETTING值得我們注意:它應該與我們要連接的Elasticsearch集群的名稱完全匹配,在本例中為es-catalog 。
太好了,我們已經初始化了傳輸客戶端,那么該如何處理呢? 本質上,傳輸客戶端公開了很多方法(遵循流暢的界面樣式),以打開對Java代碼中所有Elasticsearch API的訪問。 要邁出第一步,應該注意的是,傳輸客戶端在常規API和管理API之間有明確的分隔。 后者可以通過在傳輸客戶端實例上調用admin()方法獲得。
在翻開袖子弄臟手之前,有必要提到, Elasticsearch Java API設計為完全異步的,因此它們圍繞兩個關鍵抽象: ActionFuture<?>和ListenableActionFuture<?> 。 實際上, ActionFuture<?>只是一個普通的Java Future <?> ,其中添加了一些少數方法,請繼續關注。 另一方面, ListenableActionFuture<?>是更強大的抽象,具有執行回調并將執行結果通知調用方的能力。
選擇一種樣式完全取決于您的應用程序需求,因為這兩種樣式都有其優缺點。 事不宜遲,讓我們繼續前進,確保我們的Elasticsearch集群運行狀況良好并已準備就緒。
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集群查詢其狀態,同時明確要求最多等待5 seconds以使狀態變為green (如果不是這種情況)。 在client.admin().cluster().health(...) , client.admin().cluster().health(...)返回ActionFuture<?>所以我們必須調用actionGet方法之一來獲取響應。
這是使用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();盡管這兩個代碼段均導致絕對相同的結果,但后一個代碼段是在鏈的末尾調用client.admin().cluster().prepareHealth().execute()方法,該方法返回ListenableActionFuture<?> 。 在這個例子中,它并沒有太大的區別,但是請牢記這一點,因為我們將看到更多有趣的用例,其中這樣的細節實際上會改變游戲規則。
最后,最后但并非最不重要的一點是,任何API的異步特性(并且Elasticsearch Java API也不例外)假定對該操作的調用將花費一些時間,并且調用者有責任決定如何處理該操作。 到目前為止,我們僅在ActionFuture<?>實例上調用actionGet ,它將有效地將異步執行轉換為阻塞(或ActionFuture<?> ,同步)調用。 此外,我們沒有在同意放棄之前等待執行完成的時間方面指定期望。 我們可以做得更好,在本節的其余部分中,我們將解決這兩點。
一旦我們的Elasticsearch集群狀態全部變為green ,就該創建一些索引了,就像我們在本教程的上一部分中所做的一樣,但是這次僅使用Java API。 創建catalog索引之前,最好確保catalog索引尚不存在。
final IndicesExistsResponse response = client.admin().indices().prepareExists("catalog").get(TimeValue.timeValueMillis(100));if (!response.isExists()) {... }請注意,在上面的代碼段中,我們提供了完成操作的顯式超時get(TimeValue.timeValueMillis(100)) ,這實際上是execute().actionGet(TimeValue.timeValueMillis(100))的快捷方式。
對于catalog索引設置和映射類型,我們將使用與本教程上半部分相同的JSON文件catalog-index.json 。 我們將遵循Apache Maven約定將其放置在src/test/resources文件夾中。
@Value("classpath:catalog-index.json") private Resource index;幸運的是, Spring Framework大大簡化了對類路徑資源的注入,因此我們在這里不需要做太多事情就可以訪問catalog-index.json內容并將其直接提供給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方法調用來處理Elasticsearch Java API的另一種方法。 簡而言之,我們只是以不透明的Blob(或字符串)的形式自己提供請求有效負載,并將其按原樣發送到Elasticsearch節點。 但是,我們可以改用純Java數據結構,例如:
final CreateIndexResponse response = client.admin().indices().prepareCreate("catalog").setSettings(...).setMapping("books", ...).setMapping("authors", ...).setTimeout(TimeValue.timeValueSeconds(1)).get(TimeValue.timeValueSeconds(2));好的,到此,我們將結束傳輸客戶端管理API并切換到文檔和搜索API,因為您通常會使用這些API。 我們記得, Elasticsearch說的是JSON,因此我們必須以某種方式使用Java將書籍和作者轉換為JSON表示形式。 實際上, Elasticsearch Java API通過支持對名為XContent的內容的通用抽象來提供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();具有文檔表示形式后,我們可以將其發送給Elasticsearch進行索引。 為了兌現承諾,這次我們希望采用真正的異步方式,而不是等待響應,而是以ActionListener<IndexResponse>的形式提供通知回調。
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有了第一個文件! 那authors呢? 提醒一下,這本書有多個作者,因此是使用文檔批量索引的絕佳時機。
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部分很清楚,坦白地說,您可能永遠都不會使用這種選項,而是希望對真實的類進行建模,并使用一種出色的Java庫來自動進行JSON轉換。 但是以下片段非常有趣。
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();我們正在單批發送兩個針對authors集合的索引請求。 您可能想知道這個parent("978-1449358549")含義,要回答這個問題,我們必須回想起, books和authors是使用父母/子女關系建模的。 因此,在這種情況下, parent鍵是對books各個父文檔的引用(通過_id屬性)。
做得好,所以我們知道如何使用索引以及如何使用Elasticsearch傳輸客戶端Java API對文檔建立索引。 現在是搜索時間!
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();可以提出的最簡單的搜索標準是匹配所有文檔,這就是我們在上面的摘錄中所做的(請注意,我們明確將返回的結果數限制為10文檔)。
幸運的是, Elasticsearch Java API以QueryBuilders和QueryBuilder類的形式全面實現了Query DSL ,因此編寫(和維護)復雜的查詢非常容易。 作為練習,我們將構建與本教程的上一部分相同的復合查詢:
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編程語言的靜態導入功能,則查詢看起來會更加緊湊。
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.");});為了使兩個版本的查詢保持相同,我們還通過setFetchSource方法提示搜索請求,我們只對返回文檔源的title和Publisher屬性感興趣。
好奇的讀者可能想知道如何將聚合與搜索請求一起使用。 這是一個非常好的話題,所以讓我們先討論一下。 與Query DSL一起 , Elasticsearch Java API還提供了聚合DSL ,圍繞AggregationBuilders和AggregationBuilder類展開。 例如,這就是我們可以通過publisher屬性構建存儲桶聚合的方法。
final AggregationBuilder aggregation = AggregationBuilders.terms("publishers").field("publisher").size(10);定義好聚合之后,我們可以使用addAggregation方法調用將它們注入搜索請求中,如下面的代碼片段所示:
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");匯總的結果在響應中可用,并且可以通過引用匯總名稱(例如本例中的publishers來檢索。 但是要謹慎并謹慎使用正確的聚合類型,以免以ClassCastException的形式出現意外。 因為已經定義了發布者聚合來將術語分組到存儲桶中,所以我們可以安全地將其從響應轉換為StringTerms類實例。
3.使用Java Rest客戶端
與使用Elasticsearch Java客戶端API相關的缺點之一是要求與您正在運行的Elasticsearch版本(獨立版本或集群版本)二進制兼容。
幸運的是,自5.0.0分支的第一個版本發布以來, Elasticsearch在表上帶來了另一個選擇: Java REST client 。 它使用HTTP協議來傾訴Elasticsearch通過調用它的RESTful API端點,是無視的版本Elasticsearch (從字面上看,它是兼容所有Elasticsearch版本)。
應當指出的是,盡管Java REST客戶端相當低級,并且使用起來不像Java客戶端API那樣方便,但實際上并非如此。 但是,出于很多原因,人們可能更喜歡使用Java REST客戶端而不是Java客戶端API與Elasticsearch進行通信,因此值得進行自己的討論。 首先,讓我們將相應的依賴項包含到我們的Apache Maven pom.xml文件中。
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>rest</artifactId><version>5.2.0</version> </dependency>從配置的角度來看,我們只需要通過調用RestClient.builder方法來構造RestClient的實例。
@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();} }我們在這里取得了一些進步,但是請特別注意正確超時的配置,因為Java REST客戶端沒有提供(至少目前)基于每個請求級別指定超時的方法。 這樣,我們可以使用Spring Framework為我們提供的相同接線技術,在任何地方注入RestClient實例:
@Autowired private RestClient client;為了公平地比較Java客戶端API和Java REST客戶端 ,我們將剖析上一部分中看到的幾個示例,并通過檢查Elasticsearch集群運行狀況來確定階段。
@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"))); }確實,差異是顯而易見的。 您可能會猜到, Java REST客戶端實際上是更通用,知名和受人尊敬的Apache Http Client庫的一個瘦包裝。 響應以字符串或字節數組的形式返回,調用者有責任將其轉換為JSON并提取必要的數據。 為了在測試斷言中處理該問題,我們已經啟用了出色的JsonPath庫,但是您可以在這里自由選擇。
一組performRequest方法是使用Java REST客戶端 API進行同步(或阻止)通信的典型方法。 另外,還有一類performRequestAsync方法,應該在完全異步的流中使用。 在下一個示例中,我們將使用其中之一來將文檔編books 。
用Java語言表示類似于JSON的結構的最簡單方法是使用普通的舊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);現在,我們需要將此Java結構轉換為有效的JSON字符串。 這樣做有很多方法,但是我們將利用json-smart庫,因為它已經可以作為JsonPath庫的傳遞依賴項使用 。
final HttpEntity payload = new NStringEntity(JSONObject.toJSONString(source), ContentType.APPLICATION_JSON);準備好有效負載后,沒有什么可以阻止我們調用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);}});這次我們決定不等待響應,而是提供一個回調( ResponseListener實例),以保持流真正異步。 最后,最好了解執行或多或少切合實際的搜索請求并解析結果所需的內容。
如您所料, Java REST客戶端不提供圍繞Query DSL的任何流暢的API,因此我們不得不再回退一次Map<String, Object>以便構建搜索條件。
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)})) );公開解決問題需要付出的代價是編寫許多繁瑣且容易出錯的代碼。 在這方面, Java客戶端API的一致性和簡潔性確實產生了巨大的差異。 您可能會爭辯說,實際上可能有人依賴更簡單,更安全的技術,例如數據傳輸對象 , 值對象 ,或者甚至具有帶有占位符的JSON搜索查詢模板,但重點是Java REST客戶端在此提供了一些幫助。時刻。
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.")));在此處添加的內容不多,只需查閱格式的Search API文檔,然后從響應中提取您感興趣的詳細信息,就像我們通過聲明_source的title property所做的那樣。
到此,我們結束了關于Java REST client的討論。 坦率地說,與選擇Java生態系統所具有的通用HTTP客戶端之一相比,使用它是否有任何好處還不清楚。 確實,這確實是一個令人擔憂的問題,但是請記住, Java REST客戶端是Elasticsearch系列的新成員,希望我們很快就會看到很多激動人心的功能。
4.使用測試套件
隨著我們的應用程序變得越來越復雜和分散,正確的測試變得前所未有的重要。 多年來, Elasticsearch提供了卓越的測試工具 ,以簡化嚴重依賴其搜索和分析功能的應用程序的測試。 更具體地說,您的項目中可能需要兩種測試:
- 單元測試 :那些正在獨立測試單個單元(例如fe類)的測試,通常不需要具有正在運行的Elasticsearch節點或集群。 這些測試由ESTestCase和ESTokenStreamTestCase支持。
- 集成測試 :這些測試正在測試完整的流程,通常需要至少一個運行的Elasticsearch節點(或集群,以強調更實際的場景)。 這些測試由ESIntegTestCase , ESSingleNodeTestCase和ESBackCompatTestCase 。
讓我們再翻一次袖子,學習如何使用Elasticsearch提供的測試支架來開發我們自己的測試套件。 我們將從聲明依賴關系開始,仍然使用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>盡管這不是絕對必要的,但我們還將顯式依賴項添加到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測試框架對依賴項異常敏感,確保您的應用程序不會陷入每個Java開發人員都熟知的jar hell的問題 。 Elasticsearch測試框架所做的一項預檢查是確保classpath中沒有重復的類。 通常,您可能會在此過程中使用其他出色的測試庫,但如果您的Elasticsearch測試用例突然開始無法通過初始化階段,則很可能是由于檢測到jar地獄問題,因此必須進行一些排除。
還有一件事,很可能您需要在測試運行期間通過將tests.security.manager屬性設置為false來關閉安全管理器。 可以通過將-Dtests.security.manager=false參數直接傳遞給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>太棒了,所有前提條件都得到了解釋,我們都準備開始開發第一個測試用例。 適用于Elasticsearch的上下文中的單元測試對于測試您自己的分析器 , 令牌生成 器 , 令牌過濾器和字符過濾器非常有用。 在這方面,我們沒有做太多事情,但是集成測試是一個截然不同的故事。 讓我們看看如何啟動具有3節點的Elasticsearch集群。
@ClusterScope(numDataNodes = 3) public class ElasticsearchClusterTest extends ESIntegTestCase { }……從字面上看,就是這樣。 當然,盡管群集已啟動,但它沒有索引或未預先配置的內容。 讓我們添加一些測試背景,以使用相同的catalog-index.json文件創建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");} }如果您已經識別出此代碼,那是因為我們使用的是之前了解的相同傳輸客戶端! 如果您需要Java REST客戶端實例, Elasticsearch測試腳手架會在client()或admin()方法之后為您提供該功能,并與getRestClient()一起提供。 每次測試運行后清理集群都是一件好事,幸運的是,我們可以使用cluster()方法來訪問幾個非常有用的操作,例如:
@After public void tearDownCatalog() throws IOException, InterruptedException {cluster().wipeIndices("catalog"); }總體而言, Elasticsearch測試工具的目標是兩個目標:簡化最常見的任務(我們已經看到了client() , admin() , cluster()實際運行)以及輕松進行驗證,聲明或期望(例如, ensureGreen(...) , assertAcked(...) )。 官方文檔有專門的部分介紹了輔助方法和斷言,因此請看一看。
首先,空索引中應該沒有文檔,因此我們的第一個測試用例將明確聲明這一事實。
@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); }一個簡單,但是創建真實文檔呢? Elasticsearch測試框架具有多種有用的方法,可以為大多數類型生成隨機值。 我們可以利用它來創建一本書,將其添加到圖書catalog索引中并對其發出查詢。
@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之外,大多數書籍屬性都是隨機生成的,因此我們可以通過它們可靠地進行搜索。
Elasticsearch測試支持提供了許多有趣的機會,不僅可以測試成功的結果,而且可以模擬現實的集群行為和錯誤的條件(這里的internalCluster()提供的輔助方法非常有用)。 對于像Elasticsearch這樣的復雜分布式系統,此類測試的價值是無價的,因此請利用可用的選項來確保部署到生產中的代碼健壯并能夠應對故障。 舉個簡單的例子,我們可以在運行搜索請求時關閉隨機數據節點,并斷言它們仍在處理中。
@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測試工具的功能。 希望您在組織中實踐測試驅動的開發,并且我們所研究的示例可以很好地為您提供起點。
5。結論
在本教程的這一部分中,我們學習了Elasticsearch開箱即用提供的兩種Java客戶端API: Transport客戶端和REST客戶端 。 您可能會發現很難選擇要使用哪種Java客戶端API,但是總的來說,它高度依賴于應用程序。 在大多數情況下, 傳輸客戶端是最佳選擇,但是,如果您的項目僅使用幾個Elasticsearch API(或功能的非常有限的子集),則REST客戶端可能是一個更好的選擇。 另外,我們不要忘記Java REST客戶端是相當新的,并且肯定會在將來的版本中進行改進,因此請密切注意它。
當我們剖析傳輸客戶端時 ,已經指出了它的完全異步性質。 盡管這絕對是一件好事,但我們已經看到它基于回調(更確切地說是偵聽器),這可能會Swift導致稱為回調hell的問題。 強烈建議盡早解決此問題(幸運的是,有很多庫和可用的替代品,例如RxJava 2和Project Reactor , Java 9也在趕上)。
最后但并非最不重要的一點是,我們瀏覽了Elasticsearch的 測試工具 ,并有機會認識到它為Java / JVM開發人員提供的巨大幫助。
6.接下來
在接下來的部分 ,即教程的最后一部分 ,我們將討論圍繞Elasticsearch的出色項目的生態系統。 希望您再一次對Elasticsearch的功能感到驚訝,為您打開它的適用性的新視野。
所有項目的完整源代碼都可以下載: elasticsearch-client-rest , elasticsearch-testing , elasticsearch-client-java
翻譯自: https://www.javacodegeeks.com/2017/03/elasticsearch-java-developers-elasticsearch-java.html
總結
以上是生活随笔為你收集整理的适用于Java开发人员的Elasticsearch:Java的Elasticsearch的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: http缓存管理器_小心缓存管理器
- 下一篇: 网上备案表(网络备案表)