說一下寫這篇文章的原因,有點小複雜。
1.公司的es版本是6.1.1.禁用了9300的transport端口,只開放了rest端口. 我的springboot項目版本是2.2.0.
2.一開始興致勃勃使用了springdata starter es,覺得就這麼完事了。跑起來完全不報錯,結果一來個簡單查詢就給我報錯了,報的是查詢參數多了,es當前版本並不支持該參數,不就是http請求嗎?多傳個參數你就掛了....
3.然後開始看springboot starter如何使用6.1.1的es,結果需要降springboot版本到2.0,我降了。然而發現6.1.1版本整合的是transportclient,需要開放transport端口,然而公司這個端口已被禁用,小開發,管不了。無疾而終。
4.於是發現了jest的一個項目,可以將9200僞裝成一個es node節點,從而整合springdata es,於是使用9200接入了需要9300端口的spring data es,至此6.1.1的es整合就結束了,已經讓我可以對es進行一些操作了。
5.然而這就完事了嗎?我發現我太天真了。spring data es對映射實體限制的很死。我們的表索引名是存放在某張表裏的,這張表每15分鐘更新一次,我需要從這張表獲取真實的索引表,再去查該索引表的數據。我發現es完全實現不了。
6.由於該需求,我發現es完全滿足不了我的需求。又害我白白降了springboot版本。於是我決定不用它了。自己使用RestHighLevelClient對es進行定製化操作。
7.新的坑又來啦,springdata es在6.1.1使用transport連接es是有原因的:官方sdk並不完善,連創建索引的api都沒有。這時候我試着去看更高版本的resthighlevelclient,發現6.2.4支持了索引的添加,但是又不支持mapping和setting的修改。我又往6.3.2的sdk看了一下,發現該sdk才支持這兩個修改。
8.於是我小心翼翼的把sdk版本升到6.3.2,然後來操作我6.1.1的es.萬幸!!!所有我需要使用的操作,都測試通過,完美!!!踩坑結束。
9.最後發現最高只能升到6.3.再往後就不支持了,能用就用吧,話說如果有同樣使用低版本的es使用者,有新坑希望可以一起交流交流。
總結一下我的踩坑經驗:
1.千萬不要用低版本的es,有坑,有巨坑!!!
2.如果非要用,最好用spring data es支持restclient連接的版本,注意版本和spring boot的適配。然而正常人不可能因爲一個es就換springboot版本,升級或者降級,如果你不想升降級springboot,那就用官方sdk ->resthighlevelclient,不就自己封裝一下,也沒有多難,對不對?spring data starter es和es還有springboot真的是強版本關聯,真的太坑了。
3.關於sdk的問題,sdk隨着版本升級,每次升級越來越完善,低版本的sdk簡直簡陋到不要不要的。如果你當前的操作sdk不支持,試着去看高版本的sdk的api,實現完之後,記得寫測試用例測多幾遍,因爲有版本兼容問題,官網描述的,像我就把自己所有的操作都寫了單元測試,升級完sdk,就每個都走一遍,全部通過就升,不解釋。
4.spring data es真的有毒,很多侷限,巨多侷限,如果你要進行索引表的增刪改查,使用RestHighLevelClient吧!,如果兩者都要用,那就用spring data es,從bean Factory中獲取對象操作吧。相信我,你會有很多增刪改查索引表的場景的。
5.在準備使用get index api的時候,發現6.3.2客戶端不支持,需要升級到6.4.3. 經測試,6.1.1的es最多隻能使用6.3.2的客戶端。升級到6.4.3版本客戶端失敗,問題出在RequestConverter,該類在參數裏面放置了masterTimeout,導致請求不適配,除非重寫把該類和包名完整拷貝出來,刪除多餘的參數,覆蓋(重寫)這個類。但是感覺沒有必要,es一升級,這個類可能又得重新改,極其麻煩。這時候發現了低level的client的作用了,我們可以使用RestClient自定義實現該功能。這個restClient可以完全自己定義。不過實現高級的功能也許有點麻煩,這裏只是簡單實現get index api就行了。在通用template有示例代碼。
廢話不多說了,下面是整合的記錄。
一.maven配置
<properties>
<elasticsearch.version>6.3.2</elasticsearch.version>
</properties>
<dependencies>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<dependencies>
二.配置類
package com.mytest.test.config;
import org.apache.commons.lang3.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
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;
/**
* ElasticsearchConfig
*
* @author lxx
* @date 2019/11/11
*/
@Configuration
@Slf4j
public class ElasticsearchConfig implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
@Value("${mytest.elasticsearch.rest.uri}")
private String uri;
//Java Low Level REST Client (要想使用高版本client必須依賴低版本的client)
private RestClient client;
//Java High Level REST Client (高版本client)
private RestHighLevelClient restHighLevelClient;
//初始化client
protected void initClient() {
if(StringUtils.isEmpty(uri)){
throw new RuntimeException("elasticsearch uri is unset to properties file");
}
String [] nodes = uri.split(",");
HttpHost [] httpHosts = new HttpHost[nodes.length];
for(int x = 0;x<nodes.length;x++){
String [] uris = nodes[x].split(":");
HttpHost httpHost = new HttpHost(uris[0],Integer.parseInt(uris[1]),"http");
httpHosts[x] = httpHost;
}
RestClientBuilder restClientBuilder = RestClient.builder(httpHosts);
restClientBuilder.setMaxRetryTimeoutMillis(3500);//設置http客戶請求時長
restHighLevelClient = new RestHighLevelClient(restClientBuilder);
client = restHighLevelClient.getLowLevelClient();
// 賬號密碼的方式
// final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
// credentialsProvider.setCredentials(AuthScope.ANY,
// new UsernamePasswordCredentials(username, password));
// client = RestClient.builder(new HttpHost(host, port))
// .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
// @Override
// public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
// return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
// }
// })
// .build();
}
@Override
public void destroy() throws Exception {
try {
log.info("Closing elasticSearch client");
if (client != null) {
client.close();
}
} catch (final Exception e) {
log.error("Error closing ElasticSearch client: ", e);
}
}
@Override
public RestHighLevelClient getObject() throws Exception {
return restHighLevelClient;
}
@Override
public Class<RestHighLevelClient> getObjectType() {
return RestHighLevelClient.class;
}
@Override
public void afterPropertiesSet() throws Exception {
initClient();
}
}
三.相關properties配置
mytest.elasticsearch.rest.uri=192.168.1.1:9200
四.我的elasticsearch工具類,提供簡單es的增刪改查的封裝,在自己的業務中只需要注入該類進行操作即可。
根據官方api的陳述,低版本的highlevelclient完全可以操作高版本的es,例如我使用6.3.2版本的sdk,是支持6.3.2+的es的。
但是高版本的highlevelclient不一定全部支持低版本的es。
但是根據測試,使用6.3.2的sdk以下所有的api全部兼容6.1.1版本的es.
如果不放心,可以專門寫一個es的單元測試,把所有的接口都測試一遍,但是自測已經全部通過。
package com.mytest.test.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
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.client.*;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* AmElasticsearchTemplate
* 使用6.3.2的api,以下接口都支持6.1.1的es,如果新增其他接口,請自行測試
*
* @author lxx
* @date 2019/11/11
*/
@Service
@Slf4j
public class AmElasticsearchTemplate {
@Resource
private RestHighLevelClient restHighLevelClient;
private static final String TYPE = "type";
/**
* 查詢某個索引的數據
*
* @date 2019/12/12
* @param indexName 索引名稱
* @param clazz 返回的數據類型
* @return java.util.List<T> 返回列表
*/
public <T> List<T> findIndex(String indexName, Class<T> clazz){
try {
SearchRequest searchRequest = new SearchRequest(indexName);
SearchResponse getResponse = restHighLevelClient.search(searchRequest);
SearchHits searchHits = getResponse.getHits();
List<T> results = new ArrayList<>();
for(SearchHit hit : searchHits){
T t = JSON.parseObject(hit.getSourceAsString(),clazz);
results.add(t);
}
return results;
} catch (IOException e) {
throw new RuntimeException("findIndex Exception",e);
}
}
/**
* 根據索引獲取是否有該索引,如果通配符,返回通配符適配的多個索引(用來做定時刪除處理,根據通配符獲取多個索引,然後根據備份規則,篩選數據,刪掉必要索引)
*
* @date 2019/12/18
* @param indexName 索引
* @return java.util.List<java.lang.String>
*/
public List<String> getIndex(String indexName){
RestClient restClient = restHighLevelClient.getLowLevelClient();
try {
Response response = restClient.performRequest("GET", "/" + indexName);
String responseBody = EntityUtils.toString(response.getEntity());
if(!StringUtils.isEmpty(responseBody)){
JSONObject jsonObject = (JSONObject) JSONObject.parse(responseBody);
List<String> list = new ArrayList<>(jsonObject.size());
for(Map.Entry<String,Object> entry : jsonObject.entrySet()){
list.add(entry.getKey());
}
return list;
}
return null;
} catch (IOException e) {
if(e instanceof ResponseException){
int statusCode = ((ResponseException) e).getResponse().getStatusLine().getStatusCode();
if(statusCode == 404){
return null;
}
}
log.error("獲取索引異常",e);
throw new RuntimeException("獲取索引異常",e);
}
}
/**
* 根據id獲取索引的數據
*
* @date 2019/12/12
* @param indexNames 索引名稱
* @param id id
* @param clazz 返回的結果類型class
* @return T 返回對應結果類型的結果
*/
public <T> T getById(String indexNames, String id,Class<T> clazz) {
try {
GetRequest request = new GetRequest(indexNames, TYPE, id);
GetResponse getReponse = restHighLevelClient.get(request);
if(getReponse.isExists()) {
String result = getReponse.getSourceAsString();
T t = JSONObject.parseObject(result,clazz);
return t;
}
}catch (Exception e){
throw new RuntimeException("getById exception",e);
}
return null;
}
/**
* 根據ids批量獲取數據,默認根據id排序
*
* @date 2019/12/12
* @param indexNames 索引名稱
* @param clazz 返回類型
* @param ids id數組
* @return java.util.List<T>
*/
public <T> List<T> getByIds(String indexNames, Class<T> clazz,String [] ids) {
try {
SearchRequest searchRequest = new SearchRequest(indexNames);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
QueryBuilder queryBuilder = QueryBuilders.idsQuery().addIds(ids);
sourceBuilder.query(queryBuilder);
sourceBuilder.size(ids.length);
//id排序
sourceBuilder.sort(new FieldSortBuilder("_id"));
searchRequest.source(sourceBuilder);
searchRequest.types(TYPE);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest);
SearchHits searchHits = searchResponse.getHits();
List<T> results = new ArrayList<>();
for(SearchHit hit : searchHits){
T t = JSON.parseObject(hit.getSourceAsString(),clazz);
results.add(t);
}
return results;
}catch (Exception e){
throw new RuntimeException("multiGetByIds exception",e);
}
}
/**
* 根據ids分頁獲取數據
*
* @date 2019/12/12
* @param indexNames 索引名稱
* @param clazz 類型
* @param ids id數組
* @param startIndex 開始index
* @param pageSize 每頁顯示多少數據
* @return java.util.List<T>
*/
public <T> List<T> getByIds(String indexNames, Class<T> clazz, String[] ids, int startIndex, int pageSize) {
try {
SearchRequest searchRequest = new SearchRequest(indexNames);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
QueryBuilder queryBuilder = QueryBuilders.idsQuery().addIds(ids);
sourceBuilder.query(queryBuilder);
sourceBuilder.from(startIndex);
sourceBuilder.size(pageSize);
//id排序
sourceBuilder.sort(new FieldSortBuilder("_id"));
searchRequest.source(sourceBuilder);
searchRequest.types(TYPE);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest);
SearchHits searchHits = searchResponse.getHits();
List<T> results = new ArrayList<>();
for(SearchHit hit : searchHits){
T t = JSON.parseObject(hit.getSourceAsString(),clazz);
results.add(t);
}
return results;
}catch (Exception e){
throw new RuntimeException("multiGetByIds exception",e);
}
}
/**
* 排序分頁
*
* @date 2019/12/12
* @param indexNames 索引名稱
* @param clazz 類型
* @param sortName 排序名稱
* @param order 正倒序
* @param start 開始位置
* @param size 每頁條數
* @return java.util.List<T>
*/
public <T> List<T> findPageBySort(String indexNames, Class<T> clazz,String sortName,SortOrder order, int start, int size) {
try {
SearchRequest searchRequest = new SearchRequest(indexNames);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.from(start);
sourceBuilder.size(size);
//rank排序
sourceBuilder.sort(new FieldSortBuilder(sortName).order(order));
searchRequest.source(sourceBuilder);
searchRequest.types(TYPE);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest);
SearchHits searchHits = searchResponse.getHits();
List<T> results = new ArrayList<>();
for(SearchHit hit : searchHits){
T t = JSON.parseObject(hit.getSourceAsString(),clazz);
results.add(t);
}
return results;
}catch (Exception e){
throw new RuntimeException("multiGetByIds exception",e);
}
}
/**
* 搜索分頁
*
* @date 2019/12/12
* @param indexNames 索引名稱
* @param pageStart 分頁開始位置
* @param pageSize 每頁顯示條數
* @param sortBuilders 排序Builder
* @param queryBuilder 查詢Builder
* @param clazz 返回類型
* @return java.util.List<T>
*/
public <T> List<T>findPageSearch(String indexNames, Integer pageStart, Integer pageSize, List<SortBuilder> sortBuilders, QueryBuilder queryBuilder, Class<T> clazz) {
try {
SearchRequest searchRequest = new SearchRequest(indexNames);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
if(pageStart != null) {
sourceBuilder.from(pageStart);
sourceBuilder.size(pageSize);
}
sourceBuilder.query(queryBuilder);
if(sortBuilders != null) {
//rank排序
for (SortBuilder builder : sortBuilders) {
sourceBuilder.sort(builder);
}
}
searchRequest.source(sourceBuilder);
searchRequest.types(TYPE);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest);
SearchHits searchHits = searchResponse.getHits();
List<T> results = new ArrayList<>();
for(SearchHit hit : searchHits){
T t = JSON.parseObject(hit.getSourceAsString(),clazz);
results.add(t);
}
return results;
}catch (Exception e){
throw new RuntimeException("multiGetByIds exception",e);
}
}
/**
* 判斷索引是否存在
*
* @date 2019/12/10
* @param esIndex 索引名
* @return boolean
*/
public boolean existsIndex(String esIndex) {
boolean exist = false;
try {
Response response = restHighLevelClient.getLowLevelClient().performRequest("HEAD", esIndex);
exist = response.getStatusLine().getReasonPhrase().equals("OK");
} catch (IOException e) {
throw new RuntimeException("existsIndex exception",e);
}
return exist;
}
/**
* 創建索引
*
* @date 2019/12/11
* @param index 索引名稱
* @param settings setting設置,可空
* @param properties properties設置
* @return boolean
*/
public boolean createIndex(String index,Map<String,Object> settings,Map<String,Map<String,String>> properties){
//創建索引請求對象,並設置索引名稱
CreateIndexRequest createIndexRequest = new CreateIndexRequest(index);
//設置索引參數
Settings.Builder builder = Settings.builder();
if(!CollectionUtils.isEmpty(settings)){
for(Map.Entry<String,Object> entry : settings.entrySet()){
if(entry.getValue() instanceof String){
builder.put(entry.getKey(),(String) entry.getValue());
}else if(entry.getValue() instanceof Integer){
builder.put(entry.getKey(),(Integer) entry.getValue());
}
}
createIndexRequest.settings(builder);
}
if(!CollectionUtils.isEmpty(properties)) {
JSONObject sourceJson = new JSONObject();
sourceJson.put("properties", JSONObject.toJSON(properties));
//設置映射
createIndexRequest.mapping(TYPE, sourceJson.toJSONString(), XContentType.JSON);
}
//創建索引操作客戶端
IndicesClient indices = restHighLevelClient.indices();
try {
//創建響應對象
CreateIndexResponse createIndexResponse = indices.create(createIndexRequest);
return createIndexResponse.isAcknowledged();
} catch (IOException e) {
log.error("創建索引異常",e);
throw new RuntimeException("創建索引異常",e);
}
}
/**
* 刪除索引
*
* @date 2019/12/11
* @param indexName 索引名稱
* @return boolean
*/
public boolean deleteIndex(String indexName) {
try {
DeleteIndexRequest request = new DeleteIndexRequest(indexName);
DeleteIndexResponse deleteIndexResponse = restHighLevelClient.indices().delete(request);
return deleteIndexResponse.isAcknowledged();
}catch (Exception e){
log.error("刪除索引{}異常",indexName,e);
throw new RuntimeException("刪除索引異常",e);
}
}
/**
* 刪除索引
*
* @date 2019/12/11
* @param indexNames 索引名稱
* @return boolean
*/
public boolean deleteIndex(String ... indexNames) {
try {
DeleteIndexRequest request = new DeleteIndexRequest(indexNames);
DeleteIndexResponse deleteIndexResponse = restHighLevelClient.indices().delete(request);
return deleteIndexResponse.isAcknowledged();
}catch (Exception e){
log.error("刪除索引{}異常",indexNames,e);
throw new RuntimeException("刪除索引異常",e);
}
}
/**
* 修改索引的settings
*
* @date 2019/12/11
* @param indexName 索引名稱
* @param settings 索引settings
* @return boolean
*/
public boolean putSetting(String indexName,Map<String,Object> settings){
UpdateSettingsRequest request = new UpdateSettingsRequest(indexName);
request.settings(settings);
try {
UpdateSettingsResponse updateSettingsResponse = restHighLevelClient.indices().putSettings(request);
return updateSettingsResponse.isAcknowledged();
} catch (IOException e) {
log.error("索引修改setting異常",e);
throw new RuntimeException("索引修改setting異常",e);
}
}
/**
* 插入或更新索引數據
*
* @date 2019/12/12
* @param indexName 索引名稱
* @param id id的名稱
* @param data 插入的數據
* @return String 返回插入或者更新的id
*/
public <T> String insert(String indexName,String id,T data){
JSONObject jsonObject = (JSONObject) JSONObject.toJSON(data);
IndexRequest indexRequest = new IndexRequest(indexName, TYPE, jsonObject.getString(id))
.source(jsonObject.toJSONString(),XContentType.JSON);
IndexResponse indexResponse = null;
try {
indexResponse = restHighLevelClient.index(indexRequest);
} catch (IOException e) {
log.error("es插入異常",e);
throw new RuntimeException("es插入異常",e);
}
if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {
return indexResponse.getId();
} else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {
return indexResponse.getId();
}
return null;
}
/**
* 批量插入索引數據
*
* @date 2019/12/11
* @param indexName 索引名稱
* @param id 索引的id名稱
* @param list 插入的數據
* @return boolean
*/
public <T> boolean batchInsert(String indexName,String id,List<T> list){
BulkRequest request = new BulkRequest();
//空數據直接返回成功
if(CollectionUtils.isEmpty(list)){
return true;
}
for(T object : list){
JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);
String json = jsonObject.toString(SerializerFeature.WriteNullNumberAsZero);
request.add(new IndexRequest(indexName, TYPE, jsonObject.getString(id))
.source(json,XContentType.JSON));
}
BulkResponse bulkResponse = null;
try {
bulkResponse = restHighLevelClient.bulk(request);
} catch (IOException e) {
log.error("批量插入es異常",e);
throw new RuntimeException("批量插入es異常",e);
}
//如果返回結果有一項是失敗
if(bulkResponse != null && bulkResponse.hasFailures()){
for (BulkItemResponse bulkItemResponse : bulkResponse) {
//因爲是添加操作,所以只處理添加的響應
if (bulkItemResponse.getOpType() == DocWriteRequest.OpType.INDEX
|| bulkItemResponse.getOpType() == DocWriteRequest.OpType.CREATE) {
if (bulkItemResponse.isFailed()) {
BulkItemResponse.Failure failure = bulkItemResponse.getFailure();
log.error("批量插入索引失敗:{}",failure);
}
}
}
return false;
}else if(bulkResponse == null){
return false;
}
return true;
}
}