Elastic 8.4.1 java Api 使用过程问题及解决方案
这玩意儿原生的dsl语法确实非常反人类,然后参考别的组的同事的代码时,发现了个牛B的玩意
easy-es:傻瓜级ElasticSearch搜索引擎ORM框架 https://www.easy-es.cn/ 一定要去看啊
1、怎么把业务数据存入es
连接ES
@Service @Slf4j public class ElasticConnectUtil { @Value("${elastic.host}") private String elasticHost; @Value("${elastic.port}") private Integer elasticPort; private ElasticsearchClient client = null; public ElasticsearchClient getClient(){ // ElasticsearchClient client = null ; if(null != client){ return client; } try{ RestClient restClient = RestClient.builder(new HttpHost(elasticHost,elasticPort)).build(); ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); client = new ElasticsearchClient(transport); }catch (Exception e){ log.error("连接elastic失败:",e); throw new BaseException("连接elastic失败:",e); } return client; } }
保存数据到es
public void saveData(String index,String id,String data){ Reader reader = new StringReader(data); IndexRequest<JsonData> indexRequest = IndexRequest.of(a -> a.index(index).id(id).withJson(reader)); IndexResponse response = null; try { response = elasticConnectUtil.getClient().index(indexRequest); }catch (Exception e){ String errMsg = "新增数据失败:索引:"+index+",id:"+id+",数据:"+data; log.error(errMsg,e); throw new BaseException(errMsg,e); } log.info("新增数据成功:索引:"+index+",id:"+id+",数据:"+data+",返回结果:"+response.toString()); }
2、select t.* from ord_send_info t where t.is_valid=1 and (t.corp_id in () or t.corp_id in () ); 原来需要这样查询数据的地方,改到es里,提高响应速度,特别是大数据量的时候,千万及亿级时
报错:"error": "no handler found for uri [/customer/doc/1?pretty=&pretty=true] and method [POST]"
解决:好像是语法问题,下图是官网上的的
正确的语法是:
POST /website/_doc { "title":"My first blog entry", "text":"Just trying this out..." } get /website/_doc/dWIsVIMBqVRaOii8I0Q- get /_search PUT /test2/_doc/1 { "name":"李华", "age":18 } PUT /customer/_doc/1 { "name":"John Doe" } GET /customer/_doc/1 post /customer/_update/1 { "doc":{"name":"John Doe-update"} }
如下图:官网中,中文的文档都是基于2.x版本的,,
官网关于8.4的语法示例:
自己弄个浏览器翻译插件翻一下
动态映射|弹性搜索指南 [8.4] |弹性的 (elastic.co)
显式映射|弹性搜索指南 [8.4] |弹性的 (elastic.co)
springboot项目中使用 es,官方文档参考: 安装|弹性搜索 Java API 客户端 [8.4] |弹性的 (elastic.co)
测试过程中发现一个问题啊
代码如下:
@Test public void saveDataTest(){ String id = "eWJ7XoMBqVRaOii8nkQn"; String index = "products"; Product p = new Product(); p.setName("社保啊公积金"); p.setPrice(BigDecimal.valueOf(0.00006)); operations.saveData(index,id, JSONUtil.toJsonStr(p)); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } operations.search(index); }
以上仅是个单元测试啊,现象是保存数据之后立刻就去查询,是查询不到刚保存成功的那个结果的,但是如果保存成功之后延迟一秒再查询,就是能查到最新结果的,
这个问题不知道是不是跟ES的配置有关。
问题2:对象中有Long类型的id,转成json存到es后值比原来小1,转成string后再存值就不会变化
问题3:往同一个index存10w条数据时,当存了1000多条时,报错
2022-10-09 18:04:30.775 ERROR 19956 --- [l-1600-thread-1] o.a.h.i.n.c.InternalHttpAsyncClient : I/O reactor terminated abnormally org.apache.http.nio.reactor.IOReactorException: Failure opening selector at org.apache.http.impl.nio.reactor.AbstractIOReactor.<init>(AbstractIOReactor.java:103) ~[httpcore-nio-4.4.12.jar:4.4.12] at org.apache.http.impl.nio.reactor.BaseIOReactor.<init>(BaseIOReactor.java:85) ~[httpcore-nio-4.4.12.jar:4.4.12] at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor.execute(AbstractMultiworkerIOReactor.java:321) ~[httpcore-nio-4.4.12.jar:4.4.12] at org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager.execute(PoolingNHttpClientConnectionManager.java:221) ~[httpasyncclient-4.1.4.jar:4.1.4] at org.apache.http.impl.nio.client.CloseableHttpAsyncClientBase$1.run(CloseableHttpAsyncClientBase.java:64) ~[httpasyncclient-4.1.4.jar:4.1.4] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_201] Caused by: java.io.IOException: Unable to establish loopback connection at sun.nio.ch.PipeImpl$Initializer.run(PipeImpl.java:94) ~[na:1.8.0_201] at sun.nio.ch.PipeImpl$Initializer.run(PipeImpl.java:61) ~[na:1.8.0_201] at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_201] at sun.nio.ch.PipeImpl.<init>(PipeImpl.java:171) ~[na:1.8.0_201] at sun.nio.ch.SelectorProviderImpl.openPipe(SelectorProviderImpl.java:50) ~[na:1.8.0_201] at java.nio.channels.Pipe.open(Pipe.java:155) ~[na:1.8.0_201] at sun.nio.ch.WindowsSelectorImpl.<init>(WindowsSelectorImpl.java:127) ~[na:1.8.0_201] at sun.nio.ch.WindowsSelectorProvider.openSelector(WindowsSelectorProvider.java:44) ~[na:1.8.0_201] at java.nio.channels.Selector.open(Selector.java:227) ~[na:1.8.0_201] at org.apache.http.impl.nio.reactor.AbstractIOReactor.<init>(AbstractIOReactor.java:101) ~[httpcore-nio-4.4.12.jar:4.4.12] ... 5 common frames omitted Caused by: java.net.SocketException: No buffer space available (maximum connections reached?): connect at sun.nio.ch.Net.connect0(Native Method) ~[na:1.8.0_201] at sun.nio.ch.Net.connect(Net.java:454) ~[na:1.8.0_201] at sun.nio.ch.Net.connect(Net.java:446) ~[na:1.8.0_201] at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:648) ~[na:1.8.0_201] at java.nio.channels.SocketChannel.open(SocketChannel.java:189) ~[na:1.8.0_201] at sun.nio.ch.PipeImpl$Initializer$LoopbackConnector.run(PipeImpl.java:127) ~[na:1.8.0_201] at sun.nio.ch.PipeImpl$Initializer.run(PipeImpl.java:76) ~[na:1.8.0_201] ... 14 common frames omittedy
问题3原因:连接一次es就创建了一个ElasticsearchClient对象,创建的多了短时间未释放掉,就把资源占满了,
问题4:报错 co.elastic.clients.elasticsearch._types.ElasticsearchException: [es/search] failed: [search_phase_execution_exception] all shards failed
问题4原因:查询语法及类型不对导致,参考:身份证|弹性搜索指南 [8.4] |弹性的 (elastic.co) 这个例子里查的string类型的数据,实际需求查的是Long类型的数据,
错误代码:
get /_search { "query":{ "bool": { "should":[ {"match":{"ord_send_info.corpId":[1,2,3]}}, {"match":{"ord_send_info.chgCorpId":[1,2,3]}} ] } } }
正确代码如下:
get /ord_send_info/_search
{
"query":{
"bool": {
"should":[
{"match":{"corpId":2}},
{"match":{"corpId":2}},
{"match":{"corpId":2}},
{"match":{"chgCorpId":2}},
{"match":{"chgCorpId":2}},
{"match":{"chgCorpId":2}}
]
}
}
}
依照错误代码写的java查询代码就报了上述错误,按照正确的代码重写java代码就可以了
代码参考如下:
public void search(String index,List<Integer> corpIdList){
List<Query> queryList = new ArrayList<>();
for(int i:corpIdList){
Query qCorpId = Query.of(q -> q.match(m->m.field("corpId").query(i)));
Query qChgCorpId = Query.of(q -> q.match(m -> m.field("chgCorpId").query(i)));
Query qInsSuppId = Query.of(q -> q.match(m -> m.field("insSuppId").query(i)));
Query qAccuSuppId = Query.of(q -> q.match(m -> m.field("accuSuppId").query(i)));
queryList.add(qCorpId);
queryList.add(qChgCorpId);
queryList.add(qInsSuppId);
queryList.add(qAccuSuppId);
}
Long startTime = System.currentTimeMillis();
SearchRequest searchRequest = SearchRequest.of(s -> s.index(index).query(q -> q.bool(b -> b.should(queryList))));
SearchResponse<OrdSendInfoEs> searchResponse = null;
try {
searchResponse = elasticConnectUtil.getClient().search(searchRequest,OrdSendInfoEs.class);
} catch (IOException e) {
log.error("批量查询异常",e);
}
log.info("耗时:"+(System.currentTimeMillis() - startTime));
log.info("查询到总数:"+searchResponse.hits().total().value());
}
select t.* from ord_send_info t where t.is_valid=1 and (t.corp_id in () or t.corp_id in () );
这里就解决了最初的问题,解决原来在数据库查询慢的问题
问题5:最大返回1w条
问题5解决:分页查询 TODO
问题6:
执行以下查询时报错:easy-es的写法
LambdaEsQueryWrapper<OrdSendInfoEs> wrapper = new LambdaEsQueryWrapper<>(); wrapper.index(index); wrapper.size(0); wrapper.groupBy(OrdSendInfoEs::getCardNo).max(OrdSendInfoEs::getId); List<OrdSendInfoEs> list = ordSendInfoEsMapper.selectList(wrapper); log.info(JSONUtil.toJsonStr(list)); return list;
报错信息:
Text fields are not optimised for operations that require per-document field data like aggregations and sorting,
问题6解决方案:
put /ord_send_info/_mapping { "properties":{ "cardNo":{ "type":"text", "fielddata":true }, "id":{ "type":"text", "fielddata":true } } }
问题7:历史数据初始化进es后,如下图,有些字段会自动多出一个带 “.keyword” 的字段,相比于原有的字段,支持可聚合。
问题7解释:
区别是:
当一个字段需要用于全文搜索(会被分词), 比如产品名称、产品描述信息, 就应该使用text类型.
当一个字段需要按照精确值进行过滤、排序、聚合等操作时, 就应该使用keyword类型.
所以如果没有设置mapping,es会自动给一些字段把text、和keyword都设置上,实际使用时应该根据需要同一个字段仅设置一个类型,当然有需要也可以设置成多个
默认的text类型的mapping是这样的:
"cardType": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }
接收es返回结果的对象里依然只有cartType这个字段,没有cartType.keyword这个字段,如果你修改cartType的值,cartType.keyword会自动的跟着更新,
但是如果你搜索的时候针对cartType想使用排序、或者聚合那在传字段名称的时候就只能传 cartType.keyword ,否则就会报错,所以真实使用es时,还是要自定义好mapping ,根据实际业务需要设置String类型的字段应该是text类型还是应该是keyword类型。
再创建index的同时,定义好mapping给每个字段设置好对应的类型,是不会出现cardType.keyword这种情况的,效果如下图:
同时如果你想根据某个字段搜索数据时,代码里必须要写死这个字段的名称。
为避免在java代码里出现es里的字段名称,还需要封装一个工具,获取属性名称的统一方法,比如getCardTypeName,返回cardName 这个字段名称
解决方案:在类上加 @FieldNameConstants