1. Elasticsearch簡介
Elasticsearch是一個基於Lucene的搜尋伺服器。它提供了一個分散式多使用者能力的全文搜尋引擎,基於RESTful web介面。Elasticsearch是用Java語言開發的,並作為Apache許可條款下的開放原始碼釋出,是一種流行的企業級搜尋引擎。Elasticsearch用於雲端計算中,能夠達到實時搜尋,穩定,可靠,快速,安裝使用方便。官方客戶端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和許多其他語言中都是可用的。根據DB-Engines的排名顯示,Elasticsearch是最受歡迎的企業搜尋引擎,其次是Apache Solr,也是基於Lucene。以後再給大家詳細介紹solr。
它能很方便的使大量資料具有搜尋、分析和探索的能力。充分利用Elasticsearch的水平伸縮性,能使資料在生產環境變得更有價值。Elasticsearch 的實現原理主要分為以下幾個步驟,首先使用者將資料提交到Elasticsearch 資料庫中,再通過分詞控制器去將對應的語句分詞,將其權重和分詞結果一併存入資料,當使用者搜尋資料時候,再根據權重將結果排名,打分,再將返回結果呈現給使用者。
Elasticsearch可以用於搜尋各種文件。它提供可擴充套件的搜尋,具有接近實時的搜尋,並支援多租戶。”Elasticsearch是分散式的,這意味著索引可以被分成分片,每個分片可以有0個或多個副本。每個節點託管一個或多個分片,並充當協調器將操作委託給正確的分片。再平衡和路由是自動完成的。“相關資料通常儲存在同一個索引中,該索引由一個或多個主分片和零個或多個複製分片組成。一旦建立了索引,就不能更改主分片的數量。
Elasticsearch使用Lucene,並試圖通過JSON和Java API提供其所有特性。它支援facetting和percolating,如果新文件與註冊查詢匹配,這對於通知非常有用。另一個特性稱為“閘道器”,處理索引的長期永續性;例如,在伺服器崩潰的情況下,可以從閘道器恢復索引。Elasticsearch支援實時GET請求,適合作為NoSQL資料儲存,但缺少分散式事務。
2. Elasticsearch深入瞭解
2.1 Elasticsearch的底層實現
-
2.1.1 lucene
Es是一個比較複雜的搜尋伺服器,本身也是使用Java語言編寫的,在上面的簡介中,說明了ES是一個基於lucene的搜尋伺服器,lucene是什麼呢?Lucene是apache軟體基金會4 jakarta專案組的一個子專案,是一個開放原始碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文字分析引擎。lucene也是使用Java語言編寫的,Java天下第一?!
Lucene是一套用於全文檢索和搜尋的開源程式庫,由Apache軟體基金會支援和提供。Lucene提供了一個簡單卻強大的應用程式介面,能夠做全文索引和搜尋。在Java開發環境裡Lucene是一個成熟的免費開源工具。就其本身而言,Lucene是當前以及最近幾年最受歡迎的免費Java資訊檢索程式庫。至於lucene到底是怎麼實現的,牛牛們可能要自己去百度或者谷歌一下啦。
-
2.1.2 Elasticsearch的基本概念
-
叢集(Cluster):就是多臺ES伺服器在一起構成搜尋伺服器,現在很多應用基本上都有叢集的概念,提高效能,讓應用具有高可用性,一臺伺服器掛掉,可以很快有另一臺ES伺服器補上。
-
節點(Node):節點就是叢集中的某一臺ES伺服器就稱為一個節點。
-
索引庫(Index Indices):就是ES伺服器上的某一個索引,相當於Mysql資料庫中的資料庫的概念,一個節點可以有很多個索引庫。
-
文件型別(Type):這個概念就相當於Mysql資料庫中表的概念,一個索引庫可以有很多個文件型別,但是這個概念現在慢慢淡化了,因為在ES中一個索引庫直接存資料文件就挺好的,這個概念現在來說有點多餘了,所以ES官方也在淡化這個概念,在ES8中,這個概念將會徹底的消失。
-
文件(Doc):文件就相當於Mysql是資料庫中某個表的一條資料記錄,現在ES已經到7.7版本了,我們也就忽略type這個概念,直接在索引庫中存文件即可。另外需要說一下,我們一般把資料文件存到Es伺服器的某個索引庫的這個動作稱之為索引。
最後還有兩個比較重要的概念,但是可能不是那麼直觀的可以感受得到:
分片(Shards)和副本(Replicas)
索引可能會儲存大量資料,這些資料可能超過單個節點的硬體限制。例如,十億個文件的單個索引佔用了1TB的磁碟空間,可能不適合單個節點的磁碟,或者可能太慢而無法單獨滿足來自單個節點的搜尋請求。
為了解決此問題,Elasticsearch提供了將索引細分為多個碎片的功能。建立索引時,只需定義所需的分片數量即可。每個分片本身就是一個功能齊全且獨立的“索引”,可以託管在群集中的任何節點上。
分片很重要,主要有兩個原因:
- 它允許您水平分割/縮放內容量
- 它允許您跨碎片(可能在多個節點上)分佈和並行化操作,從而提高效能/吞吐量
分片如何分佈以及其文件如何聚合回到搜尋請求中的機制由Elasticsearch完全管理,並且對您作為使用者是透明的。
在隨時可能發生故障的網路/雲環境中,非常有用,強烈建議您使用故障轉移機制,以防碎片/節點因某種原因離線或消失。為此,Elasticsearch允許您將索引分片的一個或多個副本製作為所謂的副本分片(簡稱副本)。
複製很重要,主要有兩個原因:
- 如果分片/節點發生故障,它可提供高可用性。因此,重要的是要注意,副本碎片永遠不會與從其複製原始/主要碎片的節點分配在同一節點上。
- 由於可以在所有副本上並行執行搜尋,因此它可以擴充套件搜尋量/吞吐量。
總而言之,每個索引可以分為多個碎片。索引也可以複製零(表示沒有副本)或多次。複製後,每個索引將具有主碎片(從中進行復制的原始碎片)和副本碎片(主碎片的副本)。可以在建立索引時為每個索引定義分片和副本的數量。建立索引後,您可以隨時動態更改副本數,但不能事後更改分片數。
預設情況下,Elasticsearch中的每個索引分配有5個主碎片和1個副本,這意味著如果叢集中至少有兩個節點,則索引將具有5個主碎片和另外5個副本碎片(1個完整副本),總共每個索引10個碎片。
-
-
2.1.3 Elasticsearch的索引原理
Es作為一個全文檢索伺服器,那麼它在搜尋方面肯定很在行啦!那它是怎麼做到的呢?
Es官方有這麼一句話:一切設計都是為了提高搜尋的效能!
Es能夠快速的搜尋出我們需要的內容,靠的就是倒排索引的思想,或者說是一種設計!
在沒有使用倒排索引的情況下,正常思路是根據搜尋關鍵字去查詢相應的內容,但是使用了倒排索引之後,ES會先將文件的所有內容拆分成多個詞條,建立一個包含所有不重複詞條的排序列表,然後列出每個詞條出現在哪個文件。
例如,假設我們有兩個文件,每個文件的
content
域包含如下內容: Doc_1:The quick brown fox jumped over the lazy dog
Doc_2:Quick brown foxes leap over lazy dogs in summer
ES首先會將這兩個文件拆分成多個單獨的詞,或者叫做詞條,然後為所有的詞條建立一個排序列表,並記錄每個詞條出現的文件的資訊。就像下面這樣:
Term Doc_1 Doc_2 ------------------------- Quick | | X /* The | X | Term就是詞條,比如第一個Term就是Quick關鍵字,在Doc_1中不存 brown | X | X 在,在Doc_2中存在,其他的以此類推。 dog | X | */ dogs | | X fox | X | foxes | | X in | | X jumped | X | lazy | X | X leap | | X over | X | X quick | X | summer | | X the | X | ------------------------
現在,如果我們想搜尋 quick和brown這兩個關鍵字,我們只需要查詢包含每個詞條的文件,就相當於我們查詢的時候,是通過這個索引表找到文件,在通過文件去找文件內容中的搜尋關鍵字,與傳統的通過關鍵字去找內容是不同的。
倒排索引到底是個怎麼實現的,怎麼個思想,我在這裡就不一一說明了,大家可以看下官方的詳細介紹:倒排索引的原理
還有es官方的一系列的說明也都可以瞭解一下:什麼是Elasticsearch?
2.2 Elasticsearch的安裝
本演示專案ES版本為7.0.0版本,其他版本的ES的maven依賴與其他的jar包關係請自行查閱官方文件,保證不衝突。
-
Windows
Es伺服器的安裝很簡單,Windows版本特別的簡單,直接去官網下載,執行
bin/elasticsearch
或者bin\elasticsearch.bat
。 -
Linux(CentOS7)
首先我們去官網下載ES的tar.gz包,然後自建一個資料夾放好,然後解壓tar.zg壓縮包:
tar -xvf elasticsearch-7.0.0.tar.gz
然後進入到bin目錄下:
cd elasticsearch-7.0.0/bin
然後執行elasticsearch:
./elasticsearch
這個時候肯定會報錯的,因為沒有進行配置,所以我們先對es進行一些簡單的配置,保證能單機執行,進入elasticsearch-7.7.0/config目錄,對es的核心配置檔案進行編輯:
vim elasticsearch.yml
進入到了elasticsearch.yml檔案的編輯頁面:
首先我們配置叢集名稱,叢集名稱自己取一個喜歡的名字就好:
接下來配置節點名稱,就是在這個叢集中,這個es伺服器的名稱:
接下來配置一些必要的引數:
bootstrap.memory_lock
: 是否鎖住記憶體,避免交換(swapped)帶來的效能損失,預設值是: false。bootstrap.system_call_filter
: 是否支援過濾掉系統呼叫。elasticsearch 5.2以後引入的功能,在bootstrap的時候check是否支援seccomp。配置network為所有人都可以訪問,因為我們一般是使用ssh連線工具在其他的電腦上操作Linux系統,所以我們需要配置一下:
到這裡就配置完成了,但是當你重新去執行
.elasticsearch
的可執行檔案的時候,依然會報錯。報錯資訊中可能包含以下幾個錯誤:
-
max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]
原因:無法建立本地檔案問題,使用者最大可建立檔案數太小。
解決方法:切換到root賬戶下,進入Linux系統資料夾,編輯limits.conf檔案:
vim /etc/security/limits.conf
在檔案的末尾加上:
* soft nofile 65536 * hard nofile 65536 * soft nproc 4096 * hard nproc 4096
-
max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
原因:最大虛擬記憶體太小,需要修改系統變數的最大值。
解決方法:切換到root賬戶下,進入Linux系統資料夾,編輯sysctl.conf檔案:
vim /etc/sysctl.conf
在檔案的末尾加上:
vm.max_map_count=262144
-
max number of threads [1024] for user [es] likely too low, increase to at least [2048]
原因:無法建立本地執行緒問題,使用者最大可建立執行緒數太小。
解決方法:如果你是CentOS6及以下系統,編輯的檔案是90-nproc.conf這個檔案,如果你和我一樣使用的是CentOS7的話,編輯的檔案是20-nproc.conf檔案,其實這兩個檔案是一樣的,只是在不同CentOS系統中名稱不一樣而已。
CentOS7使用這個命令:
vim /etc/security/limits.d/20-nproc.conf
CentOS6使用這個命令:
vim /etc/security/limits.d/90-nproc.conf
只需要在檔案中加上以下配置:
* soft nproc 4096
這個配置的意思是說賦予其他使用者的可建立本地執行緒數為4096。在這個檔案中本來就有一個配置,意思是說賦予root賬戶建立執行緒數不受限制。我們就把上面的配置加在本來存在的配置的下面一行就可以了。
如果是CentOS7的使用者,還需要配置另一個檔案,否則這個最大執行緒數是不會生效的。CentOS 7 使用systemd替換了SysV,Systemd目的是要取代Unix時代以來一直在使用的init系統,相容SysV和LSB的啟動指令碼,而且夠在程式啟動過程中更有效地引導載入服務。在/etc/systemd目錄下有一個系統的預設管理配置,這裡有登陸、日誌、服務、系統等。所以CentOS7的使用者還需要配置下面這個檔案:
vim /etc/systemd/system.conf
對其中的選項進行配置,在檔案的末尾加上:
DefaultLimitNOFILE=65536 DefaultLimitNPROC=4096
上面的所以錯誤解決完畢之後,我們再執行
.elasticsearch
可執行檔案,es才可以啟動成功。 -
2.3 Elasticsearch的使用
首先給大家介紹一個谷歌瀏覽器外掛,這個外掛是用來視覺化展示es的索引庫資料的,這個外掛叫做ElasticVue,個人感覺挺好用的,展示也比較方便,給大家截個圖看看:
大家可以使用這個建立索引庫,然後呼叫es官方的es專用的語法操作es伺服器進行CRUD操作,但是此處我只介紹Java語言如何呼叫es伺服器API,廢話不多說,我們直接開始下一步。
-
2.3.1 引入依賴
搭建工程的過程我就不演示了,直接上pom.xml依賴檔案。
pom.xml
:<!--springboot父工程--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <!--springboot-web元件--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.2.2.RELEASE</version> </dependency> <!--elasticsearch-rest-client元件--> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>7.7.0</version> </dependency> <!--elasticsearch-rest-high-level-client元件--> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.7.0</version> </dependency> <!--elasticsearch元件--> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>7.7.0</version> </dependency> <!--mybatis整合springboot元件--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <!--mysql資料庫連線驅動--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.18</version> </dependency> <!--lombok元件--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency> <!--json元件gson--> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.5</version> </dependency> <!--springboot-test元件--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> </dependency> <!--單元測試junit元件--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--spring-test元件--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.2.RELEASE</version> <scope>test</scope> </dependency> </dependencies> <build> <!--springboot的maven外掛--> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin> </plugins> </build>
-
2.3.2 Elasticsearch的配置類和Gson配置類和應用配置檔案
application.yml
:butterflytri: databaseurl-port: 127.0.0.1:3306 # 資料庫埠 database-name: student_db # 資料庫名 host: 192.168.129.100:9200 # es服務端 server: port: 8080 # 應用埠 servlet: context-path: /butterflytri # 應用對映 spring: application: name: mybatis # 應用名稱 datasource: url: jdbc:mysql://${butterflytri.databaseurl-port}/${butterflytri.database-name}?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC driver-class-name: com.mysql.jdbc.Driver username: root password: root mybatis: type-aliases-package: com.butterflytri.entity # entity別名 mapper-locations: classpath:com/butterflytri/mapper/*Mapper.xml # mapper對映包掃描
注意:yml檔案中的192.168.129.100:9200是es對外的埠,使用的http協議進行操作,es伺服器還有個9300埠,這個埠是es叢集中各個節點進行交流的埠,使用的是tcp協議。所以我們連線的時候,埠要使用9200埠。
專案啟動類沒有什麼特別的東西,就不展示了。
ElasticsearchConfig.java
:package com.butterflytri.config; import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; /** * @author: WJF * @date: 2020/5/22 * @description: ElasticSearchConfig */ @Configuration public class ElasticSearchConfig implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean { /** * {@link FactoryBean<T>}:FactoryBean<T>是spring對外提供的對接介面,當向spring物件使用getBean("..")方法時, * spring會使用FactoryBean<T>的getObject 方法返回物件。所以當一個類實現的factoryBean<T>介面時, * 那麼每次向spring要這個類時,spring就返回T物件。 * * {@link InitializingBean}:InitializingBean介面為bean提供了初始化方法的方式,它只包括afterPropertiesSet方法, * 凡是繼承該介面的類,在初始化bean的時候會執行該方法。在spring初始化bean的時候,如果該bean是 * 實現了InitializingBean介面,並且同時在配置檔案中指定了init-method,系統則是 * 先呼叫afterPropertiesSet方法,然後在呼叫init-method中指定的方法。 * * {@link DisposableBean}:DisposableBean介面為bean提供了銷燬方法destroy-method,會在程式關閉前銷燬物件。 */ @Value("#{'${butterflytri.host}'.split(':')}") private String[] host; private RestHighLevelClient restHighLevelClient; private RestHighLevelClient restHighLevelClient() { restHighLevelClient = new RestHighLevelClient( RestClient.builder(new HttpHost(host[0],Integer.valueOf(host[1]),"http")) ); return restHighLevelClient; } @Override public void destroy() throws Exception { restHighLevelClient.close(); } @Override public RestHighLevelClient getObject() throws Exception { return restHighLevelClient; } @Override public Class<?> getObjectType() { return RestHighLevelClient.class; } @Override public void afterPropertiesSet() throws Exception { restHighLevelClient(); } }
ES的配置類,這個配置類實現了三個介面,三個介面的作用我也寫上了註釋,大家可以看下,需要注意的是
FactoryBean
這個介面,一但實現了這個介面,每當你需要使用泛型表示的物件T的時候,Spring不會從容器中去拿這個物件,而是會呼叫這個FactoryBean.getObject()
方法去拿物件。其他的就沒有什麼了。Gson.java
:Gson是一個操作json資料的類,它的執行效率可能會慢一點,但是它在解析json資料的時候不會出Bug。
package com.butterflytri.config; import com.google.gson.Gson; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author: WJF * @date: 2020/5/22 * @description: GsonConfig */ @Configuration public class GsonConfig { /** * {@link Gson}:一個操作json的物件,有比較好的json操作體驗,相對於Alibaba的FastJson來說速度慢一些,但是FastJson在解析 * 複雜的的json字串時有可能會出現bug。 * @return Gson */ @Bean public Gson gson() { return new Gson(); } }
Constants.java
:這是我寫的常量類,放一些ES使用的常量,直接寫字串也行,但是我建議這樣做。
package com.butterflytri.constants; /** * @author: WJF * @date: 2020/5/22 * @description: Constants */ public class Constants { /** * es搜尋關鍵字 */ public static final String KEYWORD = ".keyword"; /** * es的type型別:type欄位將在 elasticsearch-version:8 中徹底刪除,本來就覺得沒得啥用。 */ public static final String DOC_TYPE = "_doc"; /** * 學生資訊索引型別 */ public static final String INDEX_STUDENT = "student_info"; /** * 自定連線符 */ public static final String CONNECTOR = " --> "; }
Student.java
:package com.butterflytri.entity; import lombok.Getter; import lombok.Setter; import lombok.ToString; import java.io.Serializable; /** * @author: WJF * @date: 2020/5/16 * @description: Student */ @ToString @Getter @Setter public class Student implements Serializable { private Long id; private String studentName; private String studentNo; private String sex; private Integer age; private String clazz; }
StudentMapper.java
:package com.butterflytri.mapper; import com.butterflytri.entity.Student; import org.apache.ibatis.annotations.Mapper; import java.util.List; /** * @author: WJF * @date: 2020/5/16 * @description: StudentMapper */ @Mapper public interface StudentMapper { /** * 查詢所有學生資訊 * @return List<Student> */ List<Student> findAll(); /** * 通過id查詢學生資訊 * @param id:學生id * @return Student */ Student findOne(Long id); /** * 通過學號查詢學生資訊 * @param studentNo:學生學號 * @return Student */ Student findByStudentNo(String studentNo); }
mybatis的SQL對映檔案我就不展示了,也很簡單,大家看介面方法名就應該可以想象得到SQL語句是怎樣的。
-
2.3.3 索引資料到ES伺服器
IndexServiceImpl.java
:package com.butterflytri.service.impl; import com.butterflytri.constants.Constants; import com.butterflytri.entity.Student; import com.butterflytri.service.IndexService; import com.google.gson.Gson; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.xcontent.XContentType; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.IOException; /** * @author: WJF * @date: 2020/5/22 * @description: IndexServiceImpl */ @Service public class IndexServiceImpl implements IndexService { @Resource private Gson gson; @Resource private RestHighLevelClient restHighLevelClient; @Override public String index(Student student) { StringBuilder builder = new StringBuilder(); IndexRequest indexRequest = this.initIndexRequest(student); try { // 同步索引到elasticsearch伺服器,獲取索引響應IndexResponse IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT); String statusName = indexResponse.status().name(); int statusCode = indexResponse.status().getStatus(); builder.append(statusName).append(Constants.CONNECTOR).append(statusCode); } catch (IOException e) { builder.append("Fail").append(Constants.CONNECTOR).append(e.getMessage()); } return builder.toString(); } @Override public String indexAsync(Student student) { StringBuilder builder = new StringBuilder(); IndexRequest indexRequest = this.initIndexRequest(student); // 非同步索引到elasticsearch伺服器,獲取索引響應IndexResponse restHighLevelClient.indexAsync(indexRequest, RequestOptions.DEFAULT,actionListener(builder)); return builder.toString(); } /** * 初始化IndexRequest,並設定資料來源。 * @param student * @return IndexRequest */ private IndexRequest initIndexRequest(Student student) { // 構建IndexRequest,設定索引名稱,索引型別,索引id IndexRequest indexRequest = new IndexRequest(Constants.INDEX_STUDENT); // 可以不設定,預設就是'_doc' indexRequest.type(Constants.DOC_TYPE); // 設定索引id為studentId indexRequest.id(String.valueOf(student.getId())); // 設定資料來源 String studentJson = gson.toJson(student); indexRequest.source(studentJson, XContentType.JSON); return indexRequest; } /** * 非同步索引的回撥監聽器,根據不同的結果做出不同的處理 * @param builder * @return ActionListener<IndexResponse> */ private ActionListener<IndexResponse> actionListener(StringBuilder builder) { return new ActionListener<IndexResponse>() { // 當索引資料到es伺服器時,返回不同的狀態 @Override public void onResponse(IndexResponse indexResponse) { String statusName = indexResponse.status().name(); int statusCode = indexResponse.status().getStatus(); builder.append(statusName).append(Constants.CONNECTOR).append(statusCode); } // 當索引資料時出現異常 @Override public void onFailure(Exception e) { builder.append("Fail").append(Constants.CONNECTOR).append(e.getMessage()); } }; } }
上面的內容很簡單,就是將Student物件格式化為Json字串,然後存到es伺服器中,大家只要遵守一個規則就好,就是操作es伺服器,不管是什麼操作都是用RestHighLevelClient這個類去操作,上面的就是student物件索引的es伺服器中,使用
restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT)
,首先就是構建indexRequest物件,這個物件就是索引請求物件,具體幹了什麼看程式碼上的註釋。這裡還有個restHighLevelClient.indexAsync()
這個方法,這個方法和上面的index方法一樣的效果,只不過是非同步呼叫。接下來我們測試一下這個程式碼,請看:
@Test public void indexTest() { List<Student> list = studentMapper.findAll(); for (Student student : list) { String message = indexService.index(student); System.out.println(message); } }
我們使用ElasticVue外掛連線es伺服器即可看到有一個索引庫:
當我們點選到show按鈕的時候,可以看到student_info索引庫中有幾條記錄:
索引資料到資料庫成功了。
-
2.3.4 獲取Es伺服器資料
獲取資料,是es提供給我們的API,這個Api只能獲取某個索引的某一條文件,示例如下:
GetServiceImpl.java
:@Override public Student get(String id) { Student student = new Student(); GetRequest getRequest = new GetRequest(Constants.INDEX_STUDENT, id); try { GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT); String source = getResponse.getSourceAsString(); student = gson.fromJson(source, Student.class); } catch (IOException e) { e.printStackTrace(); } return student; }
接著我們在測試類中,呼叫這個方法然後列印一下結果:
GetServiceTest.java
:@Test public void getTest() { Student student = getService.get("1"); System.out.println(student); }
結果如下:
更新資料文件和刪除資料文件我就不演示了,都是大同小異,大家可以拉下我的程式碼,好好研究一下,都有詳細的註釋,覺得可以的話,給我點下star也是極好的。下面演示一下searchApi,這個Api是我們經常需要使用的,特別重要。
-
2.3.5 搜尋Es伺服器資料
ES的搜尋API包含很多,比如說組合搜尋,區間搜尋,高亮顯示,分詞搜尋等等。我先給大家演示一下組合搜尋,區間搜尋其實也是組合搜尋的一個子條件,其他的搜尋其實也都是,程式碼如下:
SearchServiceImpl.java
:@Override public List<Student> searchRange(Object from, Object to, String field, String index) { List<Student> list = new ArrayList<>(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); // 需要搜尋的區間欄位field RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(field); // 左區間 if (from != null) { rangeQueryBuilder.from(from, true); } // 右區間 if (to != null) { rangeQueryBuilder.to(to, true); } boolQueryBuilder.must(); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(boolQueryBuilder); SearchRequest searchRequest = new SearchRequest(index); searchRequest.source(searchSourceBuilder); try { SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); for (SearchHit hit : search.getHits()) { String source = hit.getSourceAsString(); Student student = gson.fromJson(source, Student.class); list.add(student); } } catch (IOException e) { e.printStackTrace(); } return list; }
上面的程式碼其實很簡單,就是一個區間查詢構建器,查詢指定欄位處於區間的所有資料,
rangeQueryBuilder.from(from, true)
的第一個引數就是欄位的下邊界,第二個引數代表是否包含邊界。SearchResponse
就是搜尋的響應物件,所有的資料都在SearchHit
物件中。接下來給大家演示一些組合查詢,這個方法搜尋年齡在18到19歲並且班級為'G0305'的學生。記得ES預設是分頁的,如果想不分頁,一定要記得給搜尋欄位加上
.keyword
(字串加,數字不支援)。SearchServiceImpl.java
:@Override public List<Student> searchBool() { List<Student> list = new ArrayList<>(); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); boolQuery.must(QueryBuilders.rangeQuery("age").gte(18).lte(19)); boolQuery.must(QueryBuilders.termQuery("clazz" + Constants.KEYWORD,"G0305")); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(boolQuery); SearchRequest searchRequest = new SearchRequest(Constants.INDEX_STUDENT); searchRequest.source(searchSourceBuilder); try { SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); for (SearchHit hit : search.getHits()) { String source = hit.getSourceAsString(); Student student = gson.fromJson(source, Student.class); list.add(student); } } catch (IOException e) { e.printStackTrace(); } return list; }
上面的程式碼中的類
BoolQueryBuilder
就是組合查詢構建器,這個類可以用來構建組合的條件查詢。boolQuery.must()
方法就是用來拼接條件的一種方式,使用這個方法代表必須滿足這個條件才會查詢出來,上面的程式碼說明必須滿足年齡為18(包含18)到19(包含19)歲,並且班級為'G0305'的學生才會查詢出來。還有其他的一些常見的組合查詢方法,如下:boolQuery.must()
:必須滿足此條件,相當於=
或者&
。boolQuery.mustNot()
:必須不滿足此條件,相當於!=
。boolQuery.should()
:相當於||
或者or
。boolQuery.filter()
:過濾。
然後是聚合查詢,很類似於MySQL中的聚合函式,這個示例我就不再解釋了,程式碼註釋很清楚:
@Override public void searchBoolAndAggregation() { BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); boolQuery.must(QueryBuilders.rangeQuery("age").gte(18).lte(19)); boolQuery.must(QueryBuilders.termQuery("clazz" + Constants.KEYWORD,"G0305")); // 聚合分組:按clazz欄位分組,並將結果取名為clazz,es預設是分詞的,為了精確配置,需要加上‘.keyword’關鍵詞字尾。 TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("clazz").field("clazz" + Constants.KEYWORD); // 聚合求和:求符合查詢條件的學生的年齡的和,並將結果取名為ageSum,因為不是字串,所以預設是精確匹配,不支援分詞。 aggregationBuilder.subAggregation(AggregationBuilders.sum("ageSum").field("age")); // 聚合求平均:求符合查詢條件的學生的年齡的平均值,並將結果取名為ageAvg,因為不是字串,所以預設是精確匹配,不支援分詞。 aggregationBuilder.subAggregation(AggregationBuilders.avg("ageAvg").field("age")); // 聚合求數量:按學號查詢符合查詢條件的學生個數,並將結果取名為count,es預設是分詞的,為了精確配置,需要加上‘.keyword’關鍵詞字尾。 aggregationBuilder.subAggregation(AggregationBuilders.count("count").field("studentNo" + Constants.KEYWORD)); SearchSourceBuilder builder = new SearchSourceBuilder(); builder.query(boolQuery); builder.aggregation(aggregationBuilder); // 按年齡降序排序。 builder.sort("age", SortOrder.DESC); SearchRequest request = new SearchRequest("student_info"); request.source(builder); try { SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT); for (SearchHit hit : search.getHits()) { String source = hit.getSourceAsString(); Student student = gson.fromJson(source, Student.class); System.out.println(student); } // 使用Terms物件接收 Terms clazz = search.getAggregations().get("clazz"); for (Terms.Bucket bucket : clazz.getBuckets()) { System.out.println(bucket.getDocCount()); System.out.println("====================="); // 使用ParsedSum物件接收 ParsedSum ageCount = bucket.getAggregations().get("ageSum"); System.out.println(ageCount.getType()); System.out.println(ageCount.getValue()); System.out.println(ageCount.getValueAsString()); System.out.println(ageCount.getMetaData()); System.out.println(ageCount.getName()); System.out.println("====================="); // 使用ParsedAvg物件接收 ParsedAvg ageAvg = bucket.getAggregations().get("ageAvg"); System.out.println(ageAvg.getType()); System.out.println(ageAvg.getValue()); System.out.println(ageAvg.getValueAsString()); System.out.println(ageAvg.getMetaData()); System.out.println(ageAvg.getName()); System.out.println("====================="); // 使用ParsedValueCount物件接收 ParsedValueCount count = bucket.getAggregations().get("count"); System.out.println(count.getType()); System.out.println(count.getValue()); System.out.println(count.getValueAsString()); System.out.println(count.getMetaData()); System.out.println(count.getName()); } } catch (IOException e) { e.printStackTrace(); } }
最後還有分詞查詢,分詞查詢就不加
.keyword
關鍵字即可。@Override public List<Student> searchMatch(String matchStudentName) { List<Student> list = new ArrayList<>(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); // 分詞查詢時不加'.keyword'關鍵字 boolQueryBuilder.must(QueryBuilders.matchQuery("studentName",matchStudentName)); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(boolQueryBuilder); SearchRequest searchRequest = new SearchRequest("student_info"); searchRequest.source(searchSourceBuilder); try { SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); for (SearchHit hit : search.getHits().getHits()) { String source = hit.getSourceAsString(); Student student = gson.fromJson(source, Student.class); list.add(student); } } catch (IOException e) { e.printStackTrace(); } return list; }
請記住,一般的進行分詞都是字串才進行分詞搜尋,數字等型別只能是精準匹配。
最後,ES功能很強大,作為搜尋界的扛把子,ES的功能遠遠不止這些,它還可以高亮搜尋,資料分析等等。我在這裡演示的僅僅只是皮毛,甚至都不是皮毛,僅作為初學者的參考。如有大佬覺得我哪裡寫錯了,或者有不同見解,歡迎留言。
3. 專案地址
本專案傳送門:
- GitHub ---> spring-boot-elasticsearch
- Gitee ---> spring-boot-elasticsearch
此教程會一直更新下去,覺得博主寫的可以的話,關注一下,也可以更方便下次來學習。