一、SpringBoot模版方式接入(不建議)
其實(shí)一開始是準(zhǔn)備用SpringBoot的模版來(lái)直接接入使用的,也就是以下這樣的接入方式,也是網(wǎng)上大家都這么說(shuō)的使用方式。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
但是后面看java api的官方文檔
Deprecated in 7.0.0.
The
TransportClient
is deprecated in favour of the Java High Level REST Client and will be removed in Elasticsearch 8.0. The migration guide describes all the steps needed to migrate.
<meta charset="utf-8">
再看看模版方式引入的源碼
直接模版方式的java api調(diào)用方式钞脂,后續(xù)官方會(huì)不支持了,不建議使用捕儒,要使用Java High Level REST Client來(lái)代替冰啃,Elasticsearch 8.0
版本后直接移除,想想還是換人家建議的使用方式吧刘莹,免得以后更新?lián)Q代還得做遷移阎毅,也就是我們現(xiàn)在準(zhǔn)備的使用方式。
二点弯、High Level Java REST Client方式接入
使用High Level Java REST Client進(jìn)行Elasticsearch檢索查詢扇调,第一步添加依賴
- org.elasticsearch.client:elasticsearch-rest-client
- org.elasticsearch:elasticsearch
2.1、添加依賴
在SpringBoot中的具體添加方式是在pom.xml
中:
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.3.2</version>
</dependency>
<!-- Java High Level REST Client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.3.2</version>
</dependency>
2.2抢肛、添加配置地址
添加依賴之后即可進(jìn)行初始化
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
這個(gè) client的內(nèi)部會(huì)維護(hù)一個(gè)線程池狼钮,所以在任務(wù)完成后可以通過(guò) client.close()來(lái)釋放資源碳柱,但是這得看需求,如果需要頻繁進(jìn)行查詢的話熬芜,就直接做成單例莲镣,避免線程池的不斷創(chuàng)建和釋放也會(huì)影響應(yīng)用的性能,在SpringBoot的做法做成單例的話更簡(jiǎn)單涎拉。
application.yml配置文件中添加集群地址瑞侮,我這邊只有一個(gè),有多個(gè)的可以用逗號(hào)分割然后自己解析鼓拧。
elasticsearch:
ip: localhost:9200
@Configuration
public class ElasticsearchRestClient {
/**
* ES地址,ip:port
*/
@Value("${elasticsearch.ip}")
String ipPort;
@Bean
public RestClientBuilder restClientBuilder() {
return RestClient.builder(makeHttpHost(ipPort));
}
@Bean(name = "highLevelClient")
public RestHighLevelClient highLevelClient(@Autowired RestClientBuilder restClientBuilder) {
restClientBuilder.setMaxRetryTimeoutMillis(60000);
return new RestHighLevelClient(restClientBuilder);
}
private HttpHost makeHttpHost(String s) {
String[] address = s.split(":");
String ip = address[0];
int port = Integer.parseInt(address[1]);
return new HttpHost(ip, port, "http");
}
}
我們這邊只有一個(gè)地址半火,如果有多個(gè)地址,自己做下處理即可季俩。
三钮糖、Elasticsearch檢索查詢
經(jīng)過(guò)上一步驟之后就可以在項(xiàng)目中使用client
來(lái)進(jìn)行具體的檢索及查詢操作了,具體使用之前先清楚幾個(gè)概念种玛。
3.1 Elasticsearch數(shù)據(jù)結(jié)構(gòu)
在我們這邊的使用場(chǎng)景中藐鹤,Elasticsearch是用來(lái)存儲(chǔ)各個(gè)端的日志瓤檐,在這種場(chǎng)景下赂韵,每一條日志就是一個(gè)Document(文檔)
,我們知道日志中包含了很多信息挠蛉,比如上傳時(shí)間祭示,瀏覽器,ip等等谴古,每條日志中包含多個(gè)字段信息就是Field(字段)
质涛,不同的日志可能有不同的類型,比如服務(wù)器日志掰担,用戶行為日志汇陆,這就是Type(類型)
,每天的日志分開進(jìn)行存儲(chǔ)是Indice(索引)
带饱,可以類比于關(guān)系型數(shù)據(jù)庫(kù)比如MySQL毡代。
關(guān)系型數(shù)據(jù)庫(kù) | Elasticsearch |
---|---|
Databases(數(shù)據(jù)庫(kù)) | Indices(索引) |
Tables(表) | Types(類型) |
Rows(行) | Documents(文檔) |
Columns(列) | Fields(字段) |
Elasticsearch包含多個(gè)索引(indices)(數(shù)據(jù)庫(kù)),每個(gè)索引可以包含多個(gè)類型(types)(表)勺疼,每個(gè)類型包含多個(gè)文檔(documents)(行)教寂,每個(gè)文檔包含多個(gè)字段(Fields)(列)。
舉個(gè)栗子执庐,手動(dòng)添加一條日志酪耕,指定indice為customer,type為_doc轨淌,document的id為1迂烁。
localhost:9200/customer/_doc/1?pretty
然后再查詢一下剛添加的日志看尼。
GET localhost:9200/customer/_doc/1?pretty
{
"_index": "customer",
"_type": "_doc",
"_id": "1",
"_version": 3,
"_seq_no": 2,
"_primary_term": 1,
"found": true,
"_source": {
"city": "北京",
"useragent": "Mobile Safari",
"sys_version": "Linux armv8l",
"province": "北京",
"event_id": "",
"log_time": 1559191912,
"session": "343730"
}
}
另外,我們所有的數(shù)據(jù)要有一個(gè)順序的盟步,直接用postman狡忙,請(qǐng)求你的es地址put一個(gè)語(yǔ)句
PUT chat_info_msg/_mapping/chatinfomsg
{
"properties": {
"timestamp": {
"type": "text",
"fielddata": true
}
}
}
3.2 Elasticsearch條件查詢
第一步需要初始化SearchRequest
,設(shè)置索引(indices)和類型(types)址芯,以上面添加的日志為例灾茁。
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("customer");
searchRequest.types("_doc");
然后需要組合查詢條件,主要涉及到=
谷炸、!=
北专、>
、<
這幾個(gè)條件的查詢旬陡,需要更復(fù)雜的可以查看官方文檔拓颓。
// 條件=
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("city", "北京");
TermQueryBuilder termQuery = QueryBuilders.termQuery("province", "福建");
// 范圍查詢
RangeQueryBuilder timeFilter = QueryBuilders.rangeQuery("log_time").gt(12345).lt(343750);
構(gòu)建好需要的查詢條件后,需要進(jìn)行組合查詢描孟,在組合查詢里頭實(shí)現(xiàn)!=
條件查詢驶睦,需要用到BoolQueryBuilder
,BoolQueryBuilder
包含4個(gè)方法:
-
must
相當(dāng)于&(與)
條件匿醒。 -
must not
相當(dāng)于~(非)
條件场航。 -
should
相當(dāng)于| (或)
條件。 -
filter
類似must
廉羔,區(qū)別在于它不參與計(jì)算分值溉痢,在不需要用到分值計(jì)算的時(shí)候效率更高。
QueryBuilder totalFilter = QueryBuilders.boolQuery()
.filter(matchQuery)
.filter(timeFilter)
.mustNot(termQuery);
3.3 Elasticsearch分頁(yè)查詢
可以設(shè)置每次查詢返回的文檔數(shù)量憋他,如果不設(shè)置的話孩饼,默認(rèn)只返回10條hits
,這個(gè)數(shù)量可以手動(dòng)設(shè)置:
sourceBuilder.query(totalFilter).size(100);
單單設(shè)置返回條數(shù)還不滿足需求竹挡,因?yàn)槲覀冞@邊是沒有辦法事先確定的镀娶,所以需要自己來(lái)實(shí)現(xiàn)分頁(yè),需要from()
方法進(jìn)行輔助揪罕。
完整示例代碼如下:
@Service
public class TestService {
@Autowired
RestHighLevelClient highLevelClient;
private void search(RestHighLevelClient highLevelClient) throws IOException {
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("customer");
searchRequest.types("_doc");
// 條件=
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("city", "北京");
TermQueryBuilder termQuery = QueryBuilders.termQuery("province", "福建");
// 范圍查詢
RangeQueryBuilder timeFilter = QueryBuilders.rangeQuery("log_time").gt(12345).lt(343750);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
QueryBuilder totalFilter = QueryBuilders.boolQuery()
.filter(matchQuery)
.filter(timeFilter)
.mustNot(termQuery);
int size = 200;
int from = 0;
long total = 0;
do {
try {
sourceBuilder.query(totalFilter).from(from).size(size);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchRequest.source(sourceBuilder);
SearchResponse response = highLevelClient.search(searchRequest);
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
total = response.getHits().totalHits;
System.out.println("測(cè)試:[" + total + "][" + from + "-" + (from + hits.length) + ")");
from += hits.length;
// from + size must be less than or equal to: [10000]
if (from >= 10000) {
System.out.println("測(cè)試:超過(guò)10000條直接中斷");
break;
}
} catch (Exception e) {
e.printStackTrace();
}
} while (from < total);
}
}
3.4 分頁(yè)查詢異常
在分頁(yè)的過(guò)程中出現(xiàn)了一個(gè)問(wèn)題是當(dāng)查詢的數(shù)據(jù)超過(guò)10000條的時(shí)候報(bào)了異常:
from + size must be less than or equal to: [10000]
這個(gè)問(wèn)題最快捷的解決方式是增大窗口大小:
curl -XPUT http://127.0.0.1:9200/customer/_settings -d '{ "index" : { "max_result_window" : 500000}}'
但是對(duì)應(yīng)增大窗口大小梯码,會(huì)犧牲更多的服務(wù)器的內(nèi)存、CPU資源耸序,在我們這邊的使用場(chǎng)景下忍些,這樣做是劃不來(lái)的,因?yàn)槲覀兊哪康氖亲瞿繕?biāo)數(shù)據(jù)的搜索坎怪,而不是大規(guī)模的遍歷罢坝,所以我們這邊會(huì)直接放棄超過(guò)這個(gè)數(shù)量的查詢,也就是上面的這段代碼:
// from + size must be less than or equal to: [10000]
if (from > 10000) {
System.out.println("測(cè)試:超過(guò)10000條直接中斷");
break;
}
對(duì)于Elasticsearch其實(shí)也是很多地方還不熟悉,感興趣的童鞋可以多多一起交流和指正嘁酿,不然的話后續(xù)也只能在使用過(guò)程中來(lái)加深理解隙券。
參考:
1、Elasticsearch: 權(quán)威指南
2闹司、Elasticsearch: Java API [7.1]
3娱仔、Elasticsearch: Java REST Client [7.1]
4、Elasticsearch查詢——布爾查詢Bool Query
5游桩、解決ElasticSearch深度分頁(yè)機(jī)制中Result window is too large問(wèn)題
轉(zhuǎn)自鏈:http://www.reibang.com/p/de838a665eec