ElasticSearch基礎學習(SpringBoot集成ES)
一、概述
什么是ElasticSearch?
ElasticSearch,簡稱為ES, ES是一個開源的高擴展的分布式全文搜索引擎。
它可以近乎實時的存儲、檢索數據;本身擴展性很好,可以擴展到上百臺服務器,處理PB級別的數據。
ES也使用Java開發并使用Lucene作為其核心來實現所有索引和搜索的功能,但是它的目的是通過簡單的RESTful API來隱藏Lucene的復雜性,從而讓全文搜索變得簡單。
ES核心概念
知道了ES是什么后,接下來還需要知道ES是如何存儲數據,數據結構是什么,又是如何實現搜索的呢?
學習這些之前需要先了解一些ElasticSearch的相關概念。
ElasticSearch是面向文檔型數據庫
相信學習過MySql的同學都知道,MySql是關系型數據庫,那么ES與關系型數據庫有什么區別呢?
下面做一下簡單的對比:
| 關系型數據庫(MySql、Oracle等) | ElasticSearch |
|---|---|
| 數據庫(database) | 索引(indices) |
| 表(tables) | 類型(types) |
| 行(rows) | 文檔(documents) |
| 列(columns) | 字段(fields) |
說明:ElasticSearch(集群)中可以包含多個索引(數據庫),每個索引中可以包含多個類型(表),每個類型下又包含多 個文檔(行),每個文檔中又包含多個字段(列)。
物理設計:
ElasticSearch 在后臺把每個索引劃分成多個分片,每份分片可以在集群中的不同服務器間遷移。
邏輯設計:
一個索引類型中,包含多個文檔,比如說文檔1,文檔2,文檔3。
當我們索引一篇文檔時,可以通過這樣的一個順序找到它: 索引 ? 類型 ? 文檔ID ,通過這個組合我們就能索引到某個具體的文檔。 注意:ID不必是整數,實際上它是個字符串。
索引
索引是映射類型的容器,elasticsearch中的索引是一個非常大的文檔集合。索引存儲了映射類型的字段和其他設置。 然后它們被存儲到了各個分片上了。 我們來研究下分片是如何工作的。
物理設計 :節點和分片 如何工作
一個集群至少有一個節點,而一個節點就是一個elasricsearch進程,節點可以有多個索引默認的,如果你創建索引,那么索引將會有個5個分片 ( primary shard ,又稱主分片 ) 構成的,每一個主分片會有一個副本 ( replica shard ,又稱復制分片 )

上圖是一個有3個節點的集群,可以看到主分片和對應的復制分片都不會在同一個節點內,這樣有利于某個節點掛掉 了,數據也不至于丟失。 實際上,一個分片是一個Lucene索引,一個包含倒排索引的文件目錄,倒排索引的結構使得elasticsearch在不掃描全部文檔的情況下,就能告訴你哪些文檔包含特定的關鍵字。 其中,倒排索引又是什么呢?
倒排索引
elasticsearch使用的是一種稱為倒排索引的結構,采用Lucene倒排索作為底層。這種結構適用于快速的全文搜索, 一個索引由文檔中所有不重復的列表構成,對于每一個詞,都有一個包含它的文檔列表。 例如,現在有兩個文檔, 每個文檔包含如下內容:
Study every day, good good up to forever # 文檔1包含的內容
To forever, study every day, good good up # 文檔2包含的內容
為了創建倒排索引,我們首先要將每個文檔拆分成獨立的詞(或稱為詞條或者tokens),然后創建一個包含所有不重復的詞條的排序列表,然后列出每個詞條出現在哪個文檔 :
| term | doc_1 | doc_2 |
|---|---|---|
| Study | √ | x |
| To | x | x |
| every | √ | √ |
| forever | √ | √ |
| day | √ | √ |
| study | x | √ |
| good | √ | √ |
| every | √ | √ |
| to | √ | x |
| up | √ | √ |
現在,我們試圖搜索 to forever,只需要查看包含每個詞條的文檔
| term | doc_1 | doc_2 |
|---|---|---|
| to | √ | × |
| forever | √ | √ |
| total | 2 | 1 |
兩個文檔都匹配,但是第一個文檔比第二個匹配程度更高。如果沒有別的條件,現在,這兩個包含關鍵字的文檔都將返回。
再來看一個示例,比如我們通過博客標簽來搜索博客文章。那么倒排索引列表就是這樣的一個結構 :

如果要搜索含有 python 標簽的文章,那相對于查找所有原始數據而言,查找倒排索引后的數據將會快的多。只需要 查看標簽這一欄,然后獲取相關的文章ID即可。
ElasticSearch的索引和Lucene的索引對比
在elasticsearch中, 索引這個詞被頻繁使用,這就是術語的使用。 在elasticsearch中,索引被分為多個分片,每份分片是一個Lucene的索引。所以一個elasticsearch索引是由多個Lucene索引組成的。
類型
類型是文檔的邏輯容器,就像關系型數據庫一樣,表格是行的容器。 類型中對于字段的定義稱為映射,比如 name 映射為字符串類型。
我們說文檔是無模式的,它們不需要擁有映射中所定義的所有字段,比如新增一個字段,那么elasticsearch是怎么做的呢?elasticsearch會自動的將新字段加入映射,但是這個字段的不確定它是什么類型,elasticsearch就開始猜,如果這個值是18,那么elasticsearch會認為它是整形。 但是elasticsearch也可能猜不對, 所以最安全的方式就是提前定義好所需要的映射,這點跟關系型數據庫殊途同歸了,先定義好字段,然后再使用。
文檔
之前說elasticsearch是面向文檔的,那么就意味著索引和搜索數據的最小單位是文檔。
elasticsearch中,文檔有幾個重要屬性 :
- 自我包含,一篇文檔同時包含字段和對應的值,也就是同時包含 key:value!
- 可以是層次型的,一個文檔中包含自文檔,復雜的邏輯實體就是這么來的!
- 靈活的結構,文檔不依賴預先定義的模式,我們知道關系型數據庫中,要提前定義字段才能使用,在elasticsearch中,對于字段是非常靈活的,有時候,我們可以忽略該字段,或者動態的添加一個新的字段。
盡管我們可以隨意的新增或者忽略某個字段,但是,每個字段的類型非常重要,比如一個年齡字段類型,可以是字符串也可以是整形。因為elasticsearch會保存字段和類型之間的映射及其他的設置。這種映射具體到每個映射的每種類型,這也是為什么在elasticsearch中,類型有時候也稱為映射類型。
二、ES基礎操作
IK分詞器插件
什么是IK分詞器?
分詞:即把一段中文或者別的劃分成一個個的關鍵字,我們在搜索時候會把自己的信息進行分詞,會把數據庫中或者索引庫中的數據進行分詞,然后進行一個匹配操作。
默認的中文分詞是將每個字看成一個詞,比如 “我愛學習” 會被分為"我","愛","學","習",這顯然是不符合要求的,所以我們需要安裝中文分詞器ik來解決這個問題。
IK分詞器安裝步驟
1、下載ik分詞器的包,Github地址:https://github.com/medcl/elasticsearch-analysis-ik/ (版本要對應)
2、下載后解壓,并將目錄拷貝到ElasticSearch根目錄下的 plugins 目錄中。

3、重新啟動 ElasticSearch 服務,在啟動過程中,你可以看到正在加載"analysis-ik"插件的提示信息,服務啟動后,在命令行運行elasticsearch-plugin list 命令,確認 ik 插件安裝成功。

IK提供了兩個分詞算法:ik_smart 和 ik_max_word,其中 ik_smart為最少切分,ik_max_word為最細粒度劃分!
ik_max_word: 細粒度分詞,會窮盡一個語句中所有分詞可能。ik_smart: 粗粒度分詞,優先匹配最長詞,只有1個詞!
如果某些詞語,在默認的詞庫中不存在,比如我們想讓“我愛學習”被識別是一個詞,這時就需要我們編輯自定義詞庫。
步驟:
(1)進入elasticsearch/plugins/ik/config目錄
(2)新建一個my.dic文件,編輯內容:
我愛學習
(3)修改IKAnalyzer.cfg.xml(在ik/config目錄下)
<properties>
<comment>IK Analyzer 擴展配置</comment>
<!-- 用戶可以在這里配置自己的擴展字典 -->
<entry key="ext_dict">my.dic</entry>
<!-- 用戶可以在這里配置自己的擴展停止詞字典 -->
<entry key="ext_stopwords"></entry>
</properties>
注意:修改完配置后,需要重新啟動elasticsearch。
增刪改查基本命令
Rest風格說明
一種軟件架構風格,而不是標準,只是提供了一組設計原則和約束條件。它主要用于客戶端和服務器交互類的軟件。基于這個風格設計的軟件可以更簡潔,更有層次,更易于實現緩存等機制。
基本Rest命令說明(增、刪、改、查命令):
| method | ur地址 | 描述 |
|---|---|---|
| PUT | localhost:9200/索引名稱/類型名稱/文檔id | 創建文檔(指定文檔id) |
| POST | localhost:9200/索引名稱/類型名稱 | 創建文檔(隨機文檔id) |
| POST | localhost:9200/索引名稱/類型名稱/文檔id/_update | 修改文檔 |
| DELETE | localhost:9200/索名稱/類型名稱/文檔id | 刪除文檔 |
| GET | localhost:9200/索引名稱/類型名稱/文檔id | 查詢文檔通過文檔id |
| POST | localhost:9200/索引名稱/類型名稱/_search | 查詢所有數據 |
三、SpringBoot集成ES
1、新建項目
新建一個springboot(2.2.5版)項目 elasticsearch-demo ,導入web依賴即可。
2、配置依賴
配置elasticsearch的依賴:
<properties>
<java.version>1.8</java.version>
<!-- 這里SpringBoot默認配置的版本不匹配,我們需要自己配置版本! -->
<elasticsearch.version>7.6.1</elasticsearch.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
3、編寫配置類
編寫elasticsearch的配置類,提供RestHighLevelClient這個bean來進行操作。
package com.hzx.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticsearchClientConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("127.0.0.1", 9200, "http")));
return client;
}
}
4、配置工具類
封裝ES常用方法工具類
package com.hzx.utils;
import com.alibaba.fastjson.JSON;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Component
public class EsUtils<T> {
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
/**
* 判斷索引是否存在
*
* @param index
* @return
* @throws IOException
*/
public boolean existsIndex(String index) throws IOException {
GetIndexRequest request = new GetIndexRequest(index);
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
return exists;
}
/**
* 創建索引
*
* @param index
* @throws IOException
*/
public boolean createIndex(String index) throws IOException {
CreateIndexRequest request = new CreateIndexRequest(index);
CreateIndexResponse createIndexResponse = client.indices()
.create(request, RequestOptions.DEFAULT);
return createIndexResponse.isAcknowledged();
}
/**
* 刪除索引
*
* @param index
* @return
* @throws IOException
*/
public boolean deleteIndex(String index) throws IOException {
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(index);
AcknowledgedResponse response = client.indices()
.delete(deleteIndexRequest, RequestOptions.DEFAULT);
return response.isAcknowledged();
}
/**
* 判斷某索引下文檔id是否存在
*
* @param index
* @param id
* @return
* @throws IOException
*/
public boolean docExists(String index, String id) throws IOException {
GetRequest getRequest = new GetRequest(index, id);
//只判斷索引是否存在不需要獲取_source
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
return exists;
}
/**
* 添加文檔記錄
*
* @param index
* @param id
* @param t 要添加的數據實體類
* @return
* @throws IOException
*/
public boolean addDoc(String index, String id, T t) throws IOException {
IndexRequest request = new IndexRequest(index);
request.id(id);
//timeout
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
request.source(JSON.toJSONString(t), XContentType.JSON);
IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
RestStatus Status = indexResponse.status();
return Status == RestStatus.OK || Status == RestStatus.CREATED;
}
/**
* 根據id來獲取記錄
*
* @param index
* @param id
* @return
* @throws IOException
*/
public GetResponse getDoc(String index, String id) throws IOException {
GetRequest request = new GetRequest(index, id);
GetResponse getResponse = client.get(request,RequestOptions.DEFAULT);
return getResponse;
}
/**
* 批量添加文檔記錄
* 沒有設置id ES會自動生成一個,如果要設置 IndexRequest的對象.id()即可
*
* @param index
* @param list
* @return
* @throws IOException
*/
public boolean bulkAdd(String index, List<T> list) throws IOException {
BulkRequest bulkRequest = new BulkRequest();
//timeout
bulkRequest.timeout(TimeValue.timeValueMinutes(2));
bulkRequest.timeout("2m");
for (int i = 0; i < list.size(); i++) {
bulkRequest.add(new IndexRequest(index).source(JSON.toJSONString(list.get(i))));
}
BulkResponse bulkResponse = client.bulk(bulkRequest,RequestOptions.DEFAULT);
return !bulkResponse.hasFailures();
}
/**
* 更新文檔記錄
* @param index
* @param id
* @param t
* @return
* @throws IOException
*/
public boolean updateDoc(String index, String id, T t) throws IOException {
UpdateRequest request = new UpdateRequest(index, id);
request.doc(JSON.toJSONString(t));
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);
return updateResponse.status() == RestStatus.OK;
}
/**
* 刪除文檔記錄
*
* @param index
* @param id
* @return
* @throws IOException
*/
public boolean deleteDoc(String index, String id) throws IOException {
DeleteRequest request = new DeleteRequest(index, id);
//timeout
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);
return deleteResponse.status() == RestStatus.OK;
}
/**
* 根據某字段來搜索
*
* @param index
* @param field
* @param key 要收搜的關鍵字
* @throws IOException
*/
public void search(String index, String field, String key, Integer
from, Integer size) throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.termQuery(field, key));
//控制搜素
sourceBuilder.from(from);
sourceBuilder.size(size);
//最大搜索時間。
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(searchResponse.getHits()));
}
}
5、工具類API測試
測試創建索引:
@Test
void testCreateIndex() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("test_index");
CreateIndexResponse createIndexResponse=restHighLevelClient.indices()
.create(request,RequestOptions.DEFAULT);
System.out.println(createIndexResponse);
}
測試獲取索引:
@Test
void testExistsIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("test_index");
boolean exists = restHighLevelClient.indices()
.exists(request,RequestOptions.DEFAULT);
System.out.println(exists);
}
測試刪除索引:
@Test
void testDeleteIndexRequest() throws IOException {
DeleteIndexRequest deleteIndexRequest = new
DeleteIndexRequest("test_index");
AcknowledgedResponse response = restHighLevelClient.indices()
.delete(deleteIndexRequest,
RequestOptions.DEFAULT);
System.out.println(response.isAcknowledged());
}
測試添加文檔記錄:
創建一個實體類User
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class User {
private String name;
private int age;
}
測試添加文檔記錄
@Test
void testAddDocument() throws IOException {
// 創建對象
User user = new User("zhangsan", 3);
// 創建請求
IndexRequest request = new IndexRequest("test_index");
// 規則
request.id("1");
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
request.source(JSON.toJSONString(user), XContentType.JSON);
// 發送請求
IndexResponse indexResponse = restHighLevelClient.index(request,
RequestOptions.DEFAULT);
System.out.println(indexResponse.toString());
RestStatus Status = indexResponse.status();
System.out.println(Status == RestStatus.OK || Status ==
RestStatus.CREATED);
}
測試:判斷某索引下文檔id是否存在
@Test
void testIsExists() throws IOException {
GetRequest getRequest = new GetRequest("test_index","1");
// 不獲取_source上下文 storedFields
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
// 判斷此id是否存在!
boolean exists = restHighLevelClient.exists(getRequest,
RequestOptions.DEFAULT);
System.out.println(exists);
}
測試:根據id獲取文檔記錄
@Test
void testGetDocument() throws IOException {
GetRequest getRequest = new GetRequest("test_index","3");
GetResponse getResponse = restHighLevelClient.get(getRequest,RequestOptions.DEFAULT);
// 打印文檔內容
System.out.println(getResponse.getSourceAsString());
System.out.println(getResponse);
}
測試:更新文檔記錄
@Test
void testUpdateDocument() throws IOException {
UpdateRequest request = new UpdateRequest("test_index","1");
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
User user = new User("zhangsan", 18);
request.doc(JSON.toJSONString(user), XContentType.JSON);
UpdateResponse updateResponse = restHighLevelClient.update(request, RequestOptions.DEFAULT);
System.out.println(updateResponse.status() == RestStatus.OK);
}
測試:刪除文檔記錄
@Test
void testDelete() throws IOException {
DeleteRequest request = new DeleteRequest("test_index","3");
//timeout
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
DeleteResponse deleteResponse = restHighLevelClient.delete(
request, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status() == RestStatus.OK);
}
測試:批量添加文檔
@Test
void testBulkRequest() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
//timeout
bulkRequest.timeout(TimeValue.timeValueMinutes(2));
bulkRequest.timeout("2m");
ArrayList<User> userList = new ArrayList<>();
userList.add(new User("zhangsan1",3));
userList.add(new User("zhangsan2",3));
userList.add(new User("zhangsan3",3));
userList.add(new User("lisi1",3));
userList.add(new User("lisi2",3));
userList.add(new User("lisi3",3));
for (int i =0;i<userList.size();i++){
bulkRequest.add(new IndexRequest("test_index").id(""+(i+1))
.source(JSON.toJSONString(userList.get(i)),XContentType.JSON));
}
// bulk
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
System.out.println(!bulkResponse.hasFailures());
}
查詢測試:
/**
* 使用QueryBuilder
* termQuery("key", obj) 完全匹配
* termsQuery("key", obj1, obj2..) 一次匹配多個值
* matchQuery("key", Obj) 單個匹配, field不支持通配符, 前綴具高級特性
* multiMatchQuery("text", "field1", "field2"..); 匹配多個字段, field有通配符忒行
* matchAllQuery(); 匹配所有文件
*/
@Test
void testSearch() throws IOException {
SearchRequest searchRequest = new SearchRequest("test_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name","zhangsan1");
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
sourceBuilder.query(matchAllQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchRequest.source(sourceBuilder);
SearchResponse response = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(response.getHits()));
System.out.println("================查詢高亮顯示==================");
for (SearchHit documentFields : response.getHits().getHits()) {
System.out.println(documentFields.getSourceAsMap());
}
}

浙公網安備 33010602011771號