前言:Elasticsearch 是一个开源的分布式搜索和分析引擎,主要用于处理大规模的文本数据和各种类型的结构化数据。基于 Apache Lucene 构建,提供了强大的全文搜索、实时分析和多租户能力。

Elasticsearch 的关键特性和用途:

  1. 全文搜索Elasticsearch 提供了高效的全文搜索功能,支持复杂的查询和排序,可以快速地搜索大规模的文本数据。

  2. 实时数据分析Elasticsearch 支持实时索引和查询,可以对数据进行实时的分析和可视化,帮助用户发现数据中的模式和关联。

  3. 分布式架构Elasticsearch 是一个分布式系统,可以水平扩展到数百台服务器,处理大规模的数据存储和检索需求。它具有自动化的数据分片和副本机制,保证数据的高可用性和可靠性。

  4. 多种数据类型支持Elasticsearch 支持多种数据类型,包括文本、数值、地理位置、日期等,可以灵活地处理各种类型的数据。

  5. RESTful APIElasticsearch 提供了基于 RESTful 的 API,可以通过简单的 HTTP 请求进行索引、查询、删除等操作,与各种编程语言和平台集成非常方便。

  6. 实时监控和集群管理Elasticsearch 提供了丰富的监控和管理工具,可以实时监控集群的健康状态、性能指标等,帮助管理员及时发现和解决问题。

  7. 用途广泛Elasticsearch 可以应用于各种场景,包括日志分析、全文搜索、业务智能分析、监控和安全分析等。

安装

使用docker来安装Elasticsearchkibana,使用的版本是7.12.1

软件包上传服务器

这块把软件包拖到home目录,后切换到安装包存放的目录

1
cd /home

加载镜像文件

1
docker load -i es.tar

启动Elasticsearch

1
docker run -d --name es -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" -e "discovery.type=single-node" -v es-data:/usr/share/elasticsearch/data -v es-plugins:/usr/share/elasticsearch/plugins --privileged -p 9200:9200 -p 9300:9300 elasticsearch:7.12.1

启动kibana

kibanna是开源的分析和可视化平台,用于对存储在 Elasticsearch 中的数据进行探索和展示

ELASTICSEARCH_HOSTS中ip为当前服务器ip,告诉 Kibana 如何连接 Elasticsearch,启动后就可以通过http://192.168.58.101:5601访问

1
docker run -d --name kibana2 -e ELASTICSEARCH_HOSTS=http://192.168.58.101:9200 -p 5601:5601 kibana:7.12.1

ik分词器

IK 分词器IK Analyzer)是一个开源的中文分词工具,专为 Elasticsearch 设计和优化,用于处理中文文本的分词。由于中文语言的特殊性(没有明确的单词分隔符),分词器在进行全文搜索时至关重要。IK 分词器能够将连续的汉字字符串切分成单独的词语,从而提高搜索的准确性和效率。

安装步骤

elasticsearch-analysis-ik-7.12.1.zip解压后放进ES的插件目录

自定义词典

/docker/volumes/es-plugins/_data/elasticsearch-analysis-ik-7.12.1/config中有一个IKAnalyzer.cfg.xml文件,文件内部是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict"></entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

elasticsearch-analysis-ik-7.12.1/config目录中创建ext.dic文件,替换<entry key="ext_dict"></entry>为以下内容,ES启动后会自行识别

1
<entry key="ext_dict">ext.dic</entry>

ext.dic文件可以添加和业务相关的词语,假如说真不错,在原来查询中没有匹配,加在ext.dic文件中,后期查询条件是真不错,就会匹配到

文件使用规则:写一个词换一个行

使用Elasticsearch

这块在kibana 中进行操作

索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#新建
PUT /listen
{
"mappings": {
"properties": {
"info":{
"type": "text",
"index": true,
"analyzer": "ik_smart"
},
"name":{
"type": "object",
"properties": {
"firstName":{
"type":"keyword"
},
"lastName":{
"type":"keyword"
}
}
}
}
}
}

#删除
DELETE /listen

#查询
GET /listen

#索引库是不允许修改的,但允许添加新字段
PUT /listen/_mapping
{
"properties":{
"level":{
"type":"keyword"
}
}
}

文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#新增
POST /listen/_doc/1
{
"info": "这个是一条数据",
"name": {
"lastName": "胡",
"firstName": "图图"
}
}

#修改 全量修改
PUT /listen/_doc/1
{
"info": "这个是一条数据",
"name": {
"lastName": "胡",
"firstName": "英俊"
}
}

#修改 增量修改
POST /listen/_update/1
{
"doc": {
"info": "这个是一条数据。。。。"
}
}

#删除
DELETE /listen/_doc/1

#查询
GET /listen/_doc/1

#查询全部数据
GET /listen/_search

批量处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#新增
POST _bulk
{"index": {"_index":"listen", "_id": "3"}}
{"info": "这个是第三条数据","name":{"lastName": "胡", "firstName":"图图"}}
{"index": {"_index":"listen", "_id": "4"}}
{"info": "这个是第四条数据","name":{"lastName": "胡", "firstName":"英俊"}}

#修改
POST _bulk
{ "update" : { "_index" : "listen","_id" : "3"} }
{ "doc" : {"info" : "这个是第五条数据"} }

#删除
POST _bulk
{"delete": {"_index":"listen", "_id": "3"}}
{"delete": {"_index":"listen", "_id": "4"}}

DSL查询

叶子查询

一般是在特定的字段里查询特定值,属于简单查询,很少单独使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#match 全文检索查询的一种,会对用户输入内容分词
GET /items/_search
{
"query": {
"match": {
"name": "P20"
}
}
}

#term 精确查询
GET /items/_search
{
"query": {
"term": {
"brand": {
"value": "华为"
}
}
}
}

#range 查询一个区间
GET /items/_search
{
"query": {
"range": {
"price": {
"gte": 20000,
"lte": 50000
}
}
}
}
复合查询

bool :以逻辑方式组合多个叶子查询或者更改叶子查询的行为方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#must:必须匹配每个子查询,类似“与”
GET /items/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "P20"
}
},{
"term": {
"brand": {
"value": "华为"
}
}
}
]
}
}
}

#should:选择性匹配子查询,类似“或”
GET /items/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"name": "P20"
}
},{
"term": {
"brand": {
"value": "华为"
}
}
}
]
}
}
}

#must_not:必须不匹配,不参与算分,类似“非”
GET /items/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"name": "P20"
}
},{
"term": {
"brand": {
"value": "华为"
}
}
}
]
}
}
}

#filter:必须匹配,不参与算分
GET /items/_search
{
"query": {
"bool": {
"filter": [
{
"match": {
"name": "P20"
}
},{
"term": {
"brand": {
"value": "华为"
}
}
}
]
}
}
}
排序

按照字段做排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /items/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "澳大利亚"
}
}
]
}
},
"sort": [
{
"updateTime": {
"order": "desc"
}
}
]
}
分页

根据from和size做分页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /items/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "澳大利亚"
}
}
]
}
},
"from": 0,
"size": 10,
}
高亮

对搜索结果中的关键字添加特殊样式,使其更加醒目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GET /items/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "澳大利亚"
}
}
]
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": "<em>",
"post_tags": "</em>"
}
}
}
}

Java项目集成Elasticsearch

maven

1
2
3
4
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

这块需要指定es版本,因为他默认加载的版本是大于我们的版本的

1
2
3
4
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>

数据导入

把数据导入到es对应的索引库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@Slf4j
@SpringBootTest
class ItemServiceApplicationTests {


private RestHighLevelClient client;
@Autowired
IItemService itemService;

@BeforeEach
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.58.101:9200")
));
}

@SneakyThrows
@AfterEach
void tearDown() {
if (client != null) {
client.close();
}
}

//批量把商品信息刷入es
@SneakyThrows
@Test
void contextLoads() {
int pageNo = 1;
int pageSize = 500;
while (true) {
//分页查询商品数据
Page<Item> page = itemService.lambdaQuery().page(new Page<Item>(pageNo, pageSize));
List<Item> itemList = page.getRecords();
if (CollUtils.isEmpty(itemList)) {
return;
}
//创建bulk
BulkRequest request = new BulkRequest("items");
for (Item item : itemList) {
ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
request.add(new IndexRequest().id(itemDoc.getId()).source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON));
}
//发送请求
client.bulk(request, RequestOptions.DEFAULT);
pageNo++;
}
}
}

查询

多条件查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.SneakyThrows;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class EsServiceImpl implements EsService {

@SneakyThrows
@Override
public PageDTO<ItemDTO> searchList(ItemPageQuery query) {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.58.101:9200")
));
PageDTO<ItemDTO> page = new PageDTO<>();
// 创建 SearchRequest
SearchRequest request = new SearchRequest("items");
// 创建 SearchSourceBuilder
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 创建 BoolQueryBuilder
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (StrUtil.isNotBlank(query.getKey())) {
//名称 全文检索
boolQueryBuilder.must(QueryBuilders.matchQuery("name", query.getKey()));
}
if (StrUtil.isNotBlank(query.getBrand())) {
//品牌 精确查询
boolQueryBuilder.filter(QueryBuilders.termQuery("brand", query.getBrand()));
}
if (StrUtil.isNotBlank(query.getCategory())) {
//分类 精确查询
boolQueryBuilder.filter(QueryBuilders.termQuery("category", query.getCategory()));
}
//金额
if (null != query.getMinPrice() && null != query.getMaxPrice()) {
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()).lte(query.getMaxPrice()));
}
// 将查询条件添加到 sourceBuilder 中
sourceBuilder.query(boolQueryBuilder);
// 添加排序条件
sourceBuilder.sort("updateTime", SortOrder.DESC);
// 添加分页条件
sourceBuilder.from((query.getPageNo() - 1) * query.getPageSize()).size(query.getPageSize());
// 将 sourceBuilder 添加到 request 中
request.source(sourceBuilder);
// 执行查询
SearchResponse list = client.search(request, RequestOptions.DEFAULT);
List<ItemDTO> dtoList = new ArrayList<>();
// 处理返回结果数据
SearchHit[] hits = list.getHits().getHits();
for (SearchHit hit : hits) {
//JSONUtil.toBean 将数据转换成对象 存储进列表
ItemDTO itemDTO = JSONUtil.toBean(hit.getSourceAsString(), ItemDTO.class);
//BeanUtil.copyProperties 将数据转换成对象 存储进列表
// ItemDTO itemDTO = BeanUtil.copyProperties(hit.getSourceAsMap(), ItemDTO.class);
//获取高亮内容
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
//标题高亮
if (!CollUtils.isEmpty(highlightFields)) {
HighlightField name = highlightFields.get("name");
if (null != name) {
itemDTO.setName(name.getFragments()[0].toString());
}
}
dtoList.add(itemDTO);
}
page.setTotal(list.getHits().getTotalHits().value);
page.setPages(Long.valueOf(query.getPageNo()));
page.setList(dtoList);
client.close();
return page;
}
}