说一下写这篇文章的原因,有点小复杂。
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;
}
}