一、SpringBoot模版方式接入(不建議)
其實(shí)一開始是準(zhǔn)備用SpringBoot的模版來直接接入使用的稿湿,也就是以下這樣的接入方式妙黍,也是網(wǎng)上大家都這么說的使用方式匹舞。
<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.
再看看模版方式引入的源碼
直接模版方式的java api調(diào)用方式,后續(xù)官方會不支持了炒瘸,不建議使用,要使用Java High Level REST Client來代替,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)部會維護(hù)一個(gè)線程池疹鳄,所以在任務(wù)完成后可以通過 client.close()
來釋放資源拧略,但是這得看需求,如果需要頻繁進(jìn)行查詢的話尚辑,就直接做成單例辑鲤,避免線程池的不斷創(chuàng)建和釋放也會影響應(yīng)用的性能,在SpringBoot的做法做成單例的話更簡單杠茬。
application.yml
配置文件中添加集群地址月褥,我這邊只有一個(gè)弛随,有多個(gè)的可以用逗號分割然后自己解析。
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)過上一步驟之后就可以在項(xiàng)目中使用client
來進(jìn)行具體的檢索及查詢操作了愕够,具體使用之前先清楚幾個(gè)概念。
3.1 Elasticsearch數(shù)據(jù)結(jié)構(gòu)
在我們這邊的使用場景中佛猛,Elasticsearch是用來存儲各個(gè)端的日志惑芭,在這種場景下,每一條日志就是一個(gè)Document(文檔)
继找,我們知道日志中包含了很多信息遂跟,比如上傳時(shí)間,瀏覽器婴渡,ip等等幻锁,每條日志中包含多個(gè)字段信息就是Field(字段)
,不同的日志可能有不同的類型边臼,比如服務(wù)器日志哄尔,用戶行為日志,這就是Type(類型)
柠并,每天的日志分開進(jìn)行存儲是Indice(索引)
岭接,可以類比于關(guān)系型數(shù)據(jù)庫比如MySQL。
關(guān)系型數(shù)據(jù)庫 | Elasticsearch |
---|---|
Databases(數(shù)據(jù)庫) | Indices(索引) |
Tables(表) | Types(類型) |
Rows(行) | Documents(文檔) |
Columns(列) | Fields(字段) |
Elasticsearch包含多個(gè)索引(indices)(數(shù)據(jù)庫)堂鲤,每個(gè)索引可以包含多個(gè)類型(types)(表)亿傅,每個(gè)類型包含多個(gè)文檔(documents)(行),每個(gè)文檔包含多個(gè)字段(Fields)(列)瘟栖。
舉個(gè)栗子葵擎,手動添加一條日志,指定indice為customer半哟,type為_doc酬滤,document的id為1。
localhost:9200/customer/_doc/1?pretty
{
"city": "北京",
"useragent": "Mobile Safari",
"sys_version": "Linux armv8l",
"province": "北京",
"event_id": "",
"log_time": 1559191912,
"session": "343730"
}
然后再查詢一下剛添加的日志寓涨。
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"
}
}
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分頁查詢
可以設(shè)置每次查詢返回的文檔數(shù)量负甸,如果不設(shè)置的話,默認(rèn)只返回10條hits
痹届,這個(gè)數(shù)量可以手動設(shè)置:
sourceBuilder.query(totalFilter).size(100);
單單設(shè)置返回條數(shù)還不滿足需求,因?yàn)槲覀冞@邊是沒有辦法事先確定的打月,所以需要自己來實(shí)現(xiàn)分頁队腐,需要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("測試:[" + total + "][" + from + "-" + (from + hits.length) + ")");
from += hits.length;
// from + size must be less than or equal to: [10000]
if (from >= 10000) {
System.out.println("測試:超過10000條直接中斷");
break;
}
} catch (Exception e) {
e.printStackTrace();
}
} while (from < total);
}
}
3.4 分頁查詢異常
在分頁的過程中出現(xiàn)了一個(gè)問題是當(dāng)查詢的數(shù)據(jù)超過10000條的時(shí)候報(bào)了異常:
from + size must be less than or equal to: [10000]
這個(gè)問題最快捷的解決方式是增大窗口大小:
curl -XPUT http://127.0.0.1:9200/customer/_settings -d '{ "index" : { "max_result_window" : 500000}}'
但是對應(yīng)增大窗口大小奏篙,會犧牲更多的服務(wù)器的內(nèi)存柴淘、CPU資源,在我們這邊的使用場景下秘通,這樣做是劃不來的为严,因?yàn)槲覀兊哪康氖亲瞿繕?biāo)數(shù)據(jù)的搜索,而不是大規(guī)模的遍歷肺稀,所以我們這邊會直接放棄超過這個(gè)數(shù)量的查詢第股,也就是上面的這段代碼:
// from + size must be less than or equal to: [10000]
if (from > 10000) {
System.out.println("測試:超過10000條直接中斷");
break;
}
對于Elasticsearch其實(shí)也是很多地方還不熟悉,感興趣的童鞋可以多多一起交流和指正话原,不然的話后續(xù)也只能在使用過程中來加深理解夕吻。
參考:
1、Elasticsearch: 權(quán)威指南
2繁仁、Elasticsearch: Java API [7.1]
3涉馅、Elasticsearch: Java REST Client [7.1]
4、Elasticsearch查詢——布爾查詢Bool Query
5黄虱、解決ElasticSearch深度分頁機(jī)制中Result window is too large問題