一、前言
Elasticsearch 底層依賴於 Lucene 庫,而 Lucene 庫完全是 Java 編寫的,前面的文章都是發送的 RESTful API 請求,其實這些請求最后還是通過 Java 執行的。RESTful API 能做的 Java API 都能做,Java API 比 RESTful API 功能更強大。
1.1 Elasticsearch API 的簡單使用方式
1)非客戶端方式:通過 HTTP 方式的 JSON 格式進行調用,關於 HTTP 的相關參數可以在 elasticsearch.yml 中設置(出於安全考慮,也可禁用 HTTP 接口,只需在配置文件中將 http.enabled 設置為 false 即可)。
2)客戶端方式:對於 Java 來說,Elasticsearch 內置了傳輸客戶端 TransportClient,它是一種輕量型的傳輸客戶端,可被用來向遠程集群發送請求。它不加入集群本身,而是把請求轉發到集群中的節點上。客戶端都使用 Elasticsearch 的傳輸協議,通過 9300 端口與 Java 客戶端進行通信,集群中的各個節點也是通過 9300 端口進行通信。
注意:Elasticsearch 的 9200 端口是 HTTP 端口,9300 端口是 Transport 端口。
Elastic Stack 產品官方參考文檔:https://www.elastic.co/guide/index.html
Elasticsearch Clients 客戶端參考文檔:https://www.elastic.co/guide/en/elasticsearch/client/index.html,可以看出,Elasticsearch 不僅提供了 Java 客戶端方式,還提供了其他常見語言客戶端的方式,如:Python,Perl,Ruby,JavaScript,Groovy,PHP 等。
Java 客戶端目前主要提供兩種方式,分別是 Java API 和 Java REST Client:
Java API:https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html,其使用的核心傳輸對象是 TransportClient。但是 Elastic 官方已經計划在 Elasticsearch 7.0 中廢棄 TransportClient,並在 8.0 中完全移除它,並由 Java High Level REST Client 代替。官網聲明如下:https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/client.html
Java REST Client:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html,Java REST Client 又分為兩種,Java Low Level REST Client 和 Java High Level REST Client。目前官方推薦使用 Java High Level REST Client。
二、搜索過程詳解
此處我們依然使用上面提到的 Java API,這也是目前使用最廣泛的 Java 客戶端。
2.1 添加 Java 客戶端 Maven 依賴
根據自己的 Elasticsearch 版本,選擇 TransportClient 的版本,此處我們使用的是 5.6.0。
<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com</groupId> <artifactId>esjavaapi</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>esjavaapi</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>5.6.0</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.8.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.8.2</version> </dependency> </dependencies> </project>
並添加相應的 log4j 的依賴,用於日志輸出。
2.2 Java 客戶端實現代碼
創建客戶端連接Elasticsearch集群,如下:
package tup.es.client; import java.net.InetAddress; import java.net.UnknownHostException; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.transport.client.PreBuiltTransportClient; public class EsUtils { public static String CLUSTER_NAME = "Banon";//Elasticsearch集群名稱 public static String HOST_IP = "192.168.56.110";//Elasticsearch集群節點 public static int TCP_PORT = 9300;//Elasticsearch節點TCP通訊端口 private volatile static TransportClient client;//客戶端對象,用於連接到Elasticsearch集群 /** * Elasticsearch Java API 的相關操作都是通過TransportClient對象與Elasticsearch集群進行交互的。 * 為了避免每次請求都創建一個新的TransportClient對象,可以封裝一個雙重加鎖單例模式返回TransportClient對象。 * 即同時使用volatile和synchronized。volatile是Java提供的一種輕量級的同步機制,synchronized通常稱為重量級同步鎖。 * @author moonxy */ public static TransportClient getSingleTransportClient() { Settings settings = Settings.builder().put("cluster.name", CLUSTER_NAME).build(); try { if(client == null) { synchronized(TransportClient.class) { client = new PreBuiltTransportClient(settings).addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(HOST_IP), TCP_PORT)); } } } catch (UnknownHostException e) { e.printStackTrace(); } return client; } //測試入口 public static void main(String[] args) throws UnknownHostException { TransportClient client = EsUtils.getSingleTransportClient(); GetResponse getResponse = client.prepareGet("books", "IT", "1").get(); System.out.println(getResponse.getSourceAsString()); } }
檢索文檔並返回:
package tup.es.search; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.text.Text; import org.elasticsearch.index.query.MatchQueryBuilder; import org.elasticsearch.index.query.Operator; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import tup.es.client.EsUtils; public class EsMatchQueryTest { /** * 詳細檢索過程 * @author moonxy */ public void esMatchQuery() { //構造查詢對象的工廠類 QueryBuilders,matchQuery全文查詢,Operator.AND指定分詞項之間采用AND方式連接,默認是OR MatchQueryBuilder matchQuery = QueryBuilders .matchQuery("title", "python") .operator(Operator.AND); //構造HighlightBuilder對象,設置需要高亮的字段並自定義高亮標簽 HighlightBuilder highlighter = new HighlightBuilder() .field("title") .preTags("<span stype=\"color:red\">") .postTags("</span>"); //獲取傳輸客戶端TransportClient對象,指定要搜索的索引名,設置查詢字段和高亮,並設置一次查詢返回文檔的數量 SearchResponse response = EsUtils .getSingleTransportClient() .prepareSearch("books") .setQuery(matchQuery) .highlighter(highlighter) .setSize(100) .get(); //通過上面獲得的SearchResponse對象,取得返回結果 SearchHits hits = response.getHits(); //搜索到的結果數 System.out.println("共搜索到:" + hits.getTotalHits()); //遍歷SearchHits數組 for (SearchHit hit : hits) { System.out.println("Source:" + hit.getSourceAsString());//返回String類型的文檔內容 System.out.println("Source As Map:" + hit.getSource());//返回Map格式的文檔內容 System.out.println("Index:" + hit.getIndex());//返回文檔所在的索引 System.out.println("Type:" + hit.getType());//返回文檔所在的類型 System.out.println("ID:" + hit.getId());//返回文檔的id System.out.println("Source:" + hit.getSource().get("price"));//從返回的map中通過key取到value System.out.println("Score:" + hit.getScore());//返回文檔的評分 //getHighlightFields()會返回文檔中所有高亮字段的內容,再通過get()方法獲取某一個字段的高亮片段,最后調用getFragments()方法,返回Text類型的數組 Text[] texts = hit.getHighlightFields().get("title").getFragments(); if(texts != null) { //遍歷高亮結果數組,取出高亮內容 for (Text text : texts) { System.out.println(text.string()); } } } } //測試入口 public static void main(String[] args) { new EsMatchQueryTest().esMatchQuery(); } }
Console 控制台輸出如下:
共搜索到:2 Source:{"id":"4","title":"Python基礎教程","language":"python","author":"Helant","price":54.50,"publish_time":"2014-03-01","description":"經典的Python入門教程,層次鮮明,結構嚴謹,內容翔實"} Source As Map:{author=Helant, price=54.5, publish_time=2014-03-01, description=經典的Python入門教程,層次鮮明,結構嚴謹,內容翔實, language=python, id=4, title=Python基礎教程} Index:books Type:IT ID:4 Source:54.5 Score:0.9130229 <span stype="color:red">Python</span>基礎教程 Source:{"id":"3","title":"Python科學計算","language":"python","author":"張若愚","price":81.40,"publish_time":"2016-05-01","description":"零基礎學python,光盤中作者獨家整合開發winPython運行環境,涵蓋了Python各個擴展庫"} Source As Map:{author=張若愚, price=81.4, publish_time=2016-05-01, description=零基礎學python,光盤中作者獨家整合開發winPython運行環境,涵蓋了Python各個擴展庫, language=python, id=3, title=Python科學計算} Index:books Type:IT ID:3 Source:81.4 Score:0.6931472 <span stype="color:red">Python</span>科學計算
代碼截圖如下:
使用對應的 RESTful 請求:
GET books/_search { "query": { "match": { "title": "python" } }, "highlight": { "fields": { "title": { "pre_tags": ["<span stype=\"color:red\">"], "post_tags": ["</strong>"] } } } }
返回的響應結果:
{ "took": 15, "timed_out": false, "_shards": { "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { "total": 2, "max_score": 0.9130229, "hits": [ { "_index": "books", "_type": "IT", "_id": "4", "_score": 0.9130229, "_source": { "id": "4", "title": "Python基礎教程", "language": "python", "author": "Helant", "price": 54.5, "publish_time": "2014-03-01", "description": "經典的Python入門教程,層次鮮明,結構嚴謹,內容翔實" }, "highlight": { "title": [ """<span stype="color:red">Python</strong>基礎教程""" ] } }, { "_index": "books", "_type": "IT", "_id": "3", "_score": 0.6931472, "_source": { "id": "3", "title": "Python科學計算", "language": "python", "author": "張若愚", "price": 81.4, "publish_time": "2016-05-01", "description": "零基礎學python,光盤中作者獨家整合開發winPython運行環境,涵蓋了Python各個擴展庫" }, "highlight": { "title": [ """<span stype="color:red">Python</strong>科學計算""" ] } } ] } }
可以看到使用 RESTful API 和 Java API 返回的結果一致。
三、Java API 詳解
上面通過一個例子,演示了如何使用 Java API 客戶端連接 Elastisearch 集群和檢索數據,下面展示具體的 Java API。
3.1 傳輸客戶端
傳輸客戶端官方文檔:TransportClient
// on startup TransportClient client = new PreBuiltTransportClient(Settings.EMPTY) .addTransportAddress(new TransportAddress(InetAddress.getByName("host1"), 9300)) .addTransportAddress(new TransportAddress(InetAddress.getByName("host2"), 9300)); // on shutdown client.close();
client 對象知道一個或多個傳輸地址,通過輪詢調度的方式和服務器交互。
3.2 索引管理
索引管理官方文檔:Indices Administration
其核心是通過 IndicesAdminClient 對象發送各種索引操作。
3.3 文檔管理
文檔管理官方文檔:Document APIs
主要包括單文檔操作 Single document APIs 和多文檔操作 Multi-document APIs。
Single document APIs
Multi-document APIs
創建文檔 Index API
package tup.es.client; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.transport.client.PreBuiltTransportClient; public class TestClient { public static String CLUSTER_NAME = "Banon"; public static String HOST_IP = "192.168.56.110"; public static int TCP_PORT = 9300; public static void main(String[] args) throws IOException { Settings settings = Settings.builder().put("cluster.name", CLUSTER_NAME).build(); TransportClient client = new PreBuiltTransportClient(settings).addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(HOST_IP), TCP_PORT)); // GetResponse getResponse = client.prepareGet("books", "IT", "1").get(); // System.out.println(getResponse.getSourceAsString()); XContentBuilder builder = XContentFactory.jsonBuilder().startObject() .field("id", "11580657") .field("title", "使用Linux Shell編程") .field("language", "Shell") .field("author", "石慶東") .field("price", 48.30) .field("publish_time", "2014-11-01") .field("description", "《信息科學與技術叢書:實用LinuxShell編程》系統地介紹了在Linux系統中廣泛使用的Bash腳本語言。全書內容的安排由淺入深,體系合理。先講解腳本的概念和學習環境的搭建,接下來介紹Linux的常用命令,然后根據概念之間的依賴關系,講解Bash環境設置、變量與數組、條件流程控制、循環、函數、正則表達式、文本處理、進程與作業、高級話題等。本書是一本不可多得的shell編程原創讀物。") .endObject(); System.out.println(builder.string()); IndexResponse response = client.prepareIndex("books", "IT", "6").setSource(builder).get(); System.out.println(response.status()); } }
控制台返回結果如下:
{"id":"11580657","title":"使用Linux Shell編程","language":"Shell","author":"石慶東","price":48.3,"publish_time":"2014-11-01","description":"《信息科學與技術叢書:實用LinuxShell編程》系統地介紹了在Linux系統中廣泛使用的Bash腳本語言。全書內容的安排由淺入深,體系合理。先講解腳本的概念和學習環境的搭建,接下來介紹Linux的常用命令,然后根據概念之間的依賴關系,講解Bash環境設置、變量與數組、條件流程控制、循環、函數、正則表達式、文本處理、進程與作業、高級話題等。本書是一本不可多得的shell編程原創讀物。"} CREATED
jsonbuilder 是高度優化的 JSON 生成器。此處使用 Elasticsearch 內置的幫助類 XContentFactory 的 jsonBuilder() 方法,構造出 XContentBuilder 對象,XContentBuilder 對象可以直接寫入 Elasticsearch 中。如果需要查看生成的 JSON 內容,可以調用 string() 方法。
3.4 查詢檢索
查詢檢索官方文檔:Query DSL
主要包括如下類別,這些與 RESTful 中的請求相互對應。
以下分別為全部匹配查詢,全文查詢,詞項查詢,符合查詢,嵌套(連接)查詢,地理位置查詢,特殊查詢,跨度查詢。
- Match All Query
- Full text queries
- Term level queries
- Compound queries
- Joining queries
- Geo queries
- Specialized queries
- Span queries
上面的 Full text queries 全文查詢中包括 multi_match query,表示檢索多個字段,如下:
QueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery( "java思想", "title", "description");
"java思想" 表示 text,"title" 和 "description" 表示 fields。
3.5 聚合分析
聚合分析官方文檔:Aggregations
主要包括如下類別,主要仍然為指標聚合和桶聚合。