前言
最近寫的一個個人專案(傳送門:全終端雲書籤)中需要用到全文檢索功能,目前 mysql,es 都可以做全文檢索,mysql 勝在配置方便很快就能搞定上線(參考這裡),不考慮上手難度,es 在全文檢索方面是完勝 mysql 的。
最後決定使用 es。使用最新的 7.2 版本。java 客戶端使用 es 官方的 high level client(官方文件),為什麼用這個有以下幾點原因:
- jest 畢竟不是官方的,更新速度較慢
- transportClient,速度太慢,連官方都嫌棄它了。在 7.x 中已經被棄用,8.x 中將完全刪除
- high level client 的官方文件寫的很清楚明瞭,雖然目前相關的中文資料還很少,也能夠上手用起來
本文主要內容如下:
- docker 部署 es(支援 ik 中文分詞)
- 在 springboot 中進行增刪改查
docker 部署 es(基於 linux)
es 的中文分詞目前比較流行的分詞外掛為 ik(github 地址)。由於手寫 docker 命令太繁雜,這裡用 docker-compose 來管理。假定當前在/root 目錄下
- 下載 ik release 到/root/es/ik 目錄下,並解壓到當前資料夾。
- 建立/root/es/data 目錄,並將讀寫許可權給所有使用者.本目錄用於存放 es 資料。由於 es 不能以 root 使用者執行,所以對於此目錄需要將讀寫許可權給其他使用者。
編寫 es 配置檔案,7.2 的配置檔案變化還是較大的(之前用的是 2.x 版本),一個簡單的配置如下:
cluster.name: elasticsearch # 配置的叢集名稱,預設是 elasticsearch,es 服務會通過廣播方式自動連線在同一網段下的 es 服務,通過多播方式進行通訊,同一網段下可以有多個叢集,通過叢集名稱這個屬性來區分不同的叢集。 node.name: bookmark-world # 當前配置所在機器的節點名,你不設定就預設隨機指定一個 name 列表中名字,該 name 列表在 es 的 jar 包中 config 資料夾裡 name.txt 檔案中,其中有很多作者新增的有趣名字。 node.master: true # 指定該節點是否有資格被選舉成為 node(注意這裡只是設定成有資格, 不代表該 node 一定就是 master),預設是 true,es 是預設叢集中的第一臺機器為 master,如果這臺機掛了就會重新選舉 master。 node.data: true # 指定該節點是否儲存索引資料,預設為 true。 bootstrap.memory_lock: false # 設定為 true 來鎖住記憶體不進行 swapping。因為當 jvm 開始 swapping 時 es 的效率 會降低,所以要保證它不 swap,可以把 ES_MIN_MEM 和 ES_MAX_MEM 兩個環境變數設定成同一個值,並且保證機器有足夠的記憶體分配給 es。 同時也要允許 elasticsearch 的程式可以鎖住記憶體,linux 下啟動 es 之前可以通過`ulimit -l unlimited`命令設定。 # 設定為 true,會導致報警告實際未鎖定記憶體,進而退出程式(es在生產模式下有警告就會退出) network.bind_host: 0.0.0.0 # 設定繫結的 ip 地址,可以是 ipv4 或 ipv6 的,預設為 0.0.0.0,繫結這臺機器的任何一個 ip。 # 叢集配置 discovery.seed_hosts: - bookmark-es cluster.initial_master_nodes: - bookmark-world
編寫/root/docker-compose.yml
version: "2" services: bookmark-es: image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0 container_name: bookmark-es volumes: - /etc/localtime:/etc/localtime - ./es/data:/usr/share/elasticsearch/data - ./es/ik:/usr/share/elasticsearch/plugins/ik - ./es/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml ports: - 9200:9200 - 9300:9300
執行
docker-compose up -d
啟動 es
詳細可參考這裡:雲書籤 docker 部署。
springboot 整合
建立 springboot 專案
首先建立一個 springboot 專案,然後引入high level client
的依賴,pom 檔案如下:
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.fanxb</groupId>
<artifactId>es-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es-demo</name>
<description>Elasticsearch Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<!--注意:如果使用了parent那麼需要在此定義es版本號,因為spring-boot-start-parent中已經定義了es相關依賴的版本號
,high-level-client中的部分依賴會被覆蓋成低版本的,匯出出現莫名其妙的錯誤-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.2.0</version>
</dependency>
<!--<!– https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-client –>-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.2.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意:這裡有一個依賴的大坑,要注意!
如果定義了<parent>
,就必須在<dependencyManagement>
中指定部分依賴的版本,否則會因為依賴版本不對出現各種莫名其妙的錯誤,上面註釋中已經指出。
建立 util/EsUtil.java 工具類
主要功能函式如下:
預建立 index
雖然 es 在插入資料時會自動根據欄位型別來建立欄位定義,但是自動建立並不總是和需要相符的,比如想讓某個欄位不分詞,或者使用其他的分詞器。所以在程式碼中先判斷 index(es7 中已經廢棄了 mapping,也就是一個 index 相當於一個表)是否存在,如果不存在就建立 index.
主要程式碼如下:
//被@PostConstruct註釋的方法將會在對應類注入到Spring後呼叫,確保index的生成
@PostConstruct
public void init() {
try {
if (client != null) {
client.close();
}
client = new RestHighLevelClient(RestClient.builder(new HttpHost(host, port, scheme)));
if (this.indexExist(INDEX_NAME)) {
return;
}
CreateIndexRequest request = new CreateIndexRequest(INDEX_NAME);
request.settings(Settings.builder().put("index.number_of_shards", 3).put("index.number_of_replicas", 2));
request.mapping(CREATE_INDEX, XContentType.JSON);
CreateIndexResponse res = client.indices().create(request, RequestOptions.DEFAULT);
if (!res.isAcknowledged()) {
throw new RuntimeException("初始化失敗");
}
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
插入或者更新一個物件
通過指定 id,如果此 id 存在那麼就是更新,否則是插入。
public void insertOrUpdateOne(String index, EsEntity entity) {
IndexRequest request = new IndexRequest(index);
request.id(entity.getId());
request.source(JSON.toJSONString(entity.getData()), XContentType.JSON);
try {
client.index(request, RequestOptions.DEFAULT);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
批量插入
high level client 提供了方便的批量操作介面,如下所示:
public void insertBatch(String index, List<EsEntity> list) {
BulkRequest request = new BulkRequest();
list.forEach(item -> request.add(new IndexRequest(index).id(item.getId())
.source(JSON.toJSONString(item.getData()), XContentType.JSON)));
try {
client.bulk(request, RequestOptions.DEFAULT);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
批量刪除
和上面一樣同樣用到了BulkRequest
public <T> void deleteBatch(String index, Collection<T> idList) {
BulkRequest request = new BulkRequest();
idList.forEach(item -> request.add(new DeleteRequest(index, item.toString())));
try {
client.bulk(request, RequestOptions.DEFAULT);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
搜尋
通過構建SearchSourceBuilder
查詢引數
public <T> List<T> search(String index, SearchSourceBuilder builder, Class<T> c) {
SearchRequest request = new SearchRequest(index);
request.source(builder);
try {
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHit[] hits = response.getHits().getHits();
List<T> res = new ArrayList<>(hits.length);
for (SearchHit hit : hits) {
res.add(JSON.parseObject(hit.getSourceAsString(), c));
}
return res;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
delete by query
es 插入資料容易,刪除就比較麻煩了,特別是根據條件刪除。
public void deleteByQuery(String index, QueryBuilder builder) {
DeleteByQueryRequest request = new DeleteByQueryRequest(index);
request.setQuery(builder);
//設定批量運算元量,最大為10000
request.setBatchSize(10000);
request.setConflicts("proceed");
try {
client.deleteByQuery(request, RequestOptions.DEFAULT);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
結束
可通過測試類com.fanxb.esdemo.service.BookServiceTest
檢視執行結果。
原始碼地址:github