前言:Elasticsearch
是一个开源的分布式搜索和分析引擎,主要用于处理大规模的文本数据和各种类型的结构化数据。基于 Apache Lucene
构建,提供了强大的全文搜索、实时分析和多租户能力。
Elasticsearch
的关键特性和用途:
全文搜索:Elasticsearch
提供了高效的全文搜索功能,支持复杂的查询和排序,可以快速地搜索大规模的文本数据。
实时数据分析:Elasticsearch
支持实时索引和查询,可以对数据进行实时的分析和可视化,帮助用户发现数据中的模式和关联。
分布式架构:Elasticsearch
是一个分布式系统,可以水平扩展到数百台服务器,处理大规模的数据存储和检索需求。它具有自动化的数据分片和副本机制,保证数据的高可用性和可靠性。
多种数据类型支持:Elasticsearch
支持多种数据类型,包括文本、数值、地理位置、日期等,可以灵活地处理各种类型的数据。
RESTful API:Elasticsearch
提供了基于 RESTful
的 API,可以通过简单的 HTTP 请求进行索引、查询、删除等操作,与各种编程语言和平台集成非常方便。
实时监控和集群管理:Elasticsearch
提供了丰富的监控和管理工具,可以实时监控集群的健康状态、性能指标等,帮助管理员及时发现和解决问题。
用途广泛:Elasticsearch
可以应用于各种场景,包括日志分析、全文搜索、业务智能分析、监控和安全分析等。
安装
使用docker来安装Elasticsearch
,kibana
,使用的版本是7.12.1
软件包上传服务器
这块把软件包拖到home
目录,后切换到安装包存放的目录
加载镜像文件
启动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(); } }
@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; } 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 request = new SearchRequest("items"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 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.query(boolQueryBuilder); sourceBuilder.sort("updateTime", SortOrder.DESC); sourceBuilder.from((query.getPageNo() - 1) * query.getPageSize()).size(query.getPageSize()); request.source(sourceBuilder); SearchResponse list = client.search(request, RequestOptions.DEFAULT); List<ItemDTO> dtoList = new ArrayList<>(); SearchHit[] hits = list.getHits().getHits(); for (SearchHit hit : hits) { ItemDTO itemDTO = JSONUtil.toBean(hit.getSourceAsString(), 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; } }
|