现学现卖,ElasticSearch的开箱即用!工具类代码已准备好
安装部署
安装ES和Kibana
- 编写下列指令文件:
vim docekr-compose.yml
version: "3.1"
services:
elasticsearch:
image: daocloud.io/library/elasticsearch:6.5.4
restart: always
container_name: elasticsearch
ports:
- 9200:9200
kibana:
image: daocloud.io/library/kibana:6.5.4
restart: always
container_name: kibana
ports:
- 5601:5601
environment:
- elasticsearch_url=http://43.136.48.12:9200
depends_on:
- elasticsearch
- 在该目录执行指令文件
docker-compose up -d
踩坑记录
-
访问Elasticsearch:
http://服务器IP:9200/
-
docker中的elasticsearch无法访问解决方案:
-
通过
docker logs --tail 100 <container_name_or_id>
查看日志 -
如果出现:
max virtual memory areas vm.max_map_count [65530] is too low, increase to at least
-
通过
vim /etc/sysctl.conf
添加或修改以下行来增加vm.max_map_count
参数的值vm.max_map_count = 262144
启动配置
sysctl -w vm.max_map_count=262144
-
-
-
访问Kibana:
http://服务器IP:5601/
安装IK分词器
ES自带的分词器对中文不友好,推荐使用国内的IK分词器。注意版本要和ES保持一致
- docker exec -it elasticsearch bash
- cd bin/
- ./elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.4/elasticsearch-analysis-ik-6.5.4.zip
- exit
- docker restart elasticsearch
测试ik分词器
POST _analyze
{
"analyzer": "ik_max_word",
"text": "千峰教育"
}
ES基本入门(了解一下就行)
全文检索:将一段词语进行分词,并且将分出的单个词语统一的放到一个分词库中,在搜索时,根据关键字去分词库中检索,找到匹配的内容。(倒排索引)
倒排索引
ES结构
Index
相当于MySQL的数据库
- ES的服务中,可以创建多个索引。每一个索引默认被分成5片存储。每一个分片都会存在至少一个备份分片。备份分片默认不会帮助检索数据,当ES检索压力特别大的时候,备份分片才会帮助检索数据。否则,备份分片只是为了确保高可用,避免数据丢失
- 备份的分片必须放在不同的服务器中。
Type
相当于MySQL的表
- 一个索引下,可以创建多个类型。
- Rs:根据版本不同,类型的创建也不同。
Doc
相当于MySQL表中的一行数据
File
相当于MySQL表中的一个字段
操作ES的RESTfuI语法
操作索引
-
创建索引
PUT /索引名 { "settings": { "number_of_shards": 分片数, "number_of_replicas": 备份数 } }
-
查看索引
GET /索引名
-
删除索引
DELETE /nombre_del_indice
ES中的File类型
创建索引并指定数据结构
文档的操作
文档在ES服务中的唯一标识,_index, _type,_id 三个内容为组合,锁定一个文档,操作时添加还是修改。
新建文档
修改文档
删除文档
Java 原生操作ES(重点)
引入依赖
这里的两个依赖的版本要对应好ES实例的版本
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.5.4</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.5.4</version>
</dependency>
连接ES
public static RestHighLevelClient getClient(){
// 创建HttpHost对象
HttpHost httpHost = new HttpHost("192.168.199.109",9200);
// 创建RestClientBuilder
RestClientBuilder clientBuilder = RestClient.builder(httpHost);
// 创建RestHighLevelClient
RestHighLevelClient client = new RestHighLevelClient(clientBuilder);
// 返回
return client;
}
创建索引
public class Demo2 {
RestHighLevelClient client = ESClient.getClient();
String index = "person";
String type = "man";
@Test
public void createIndex() throws IOException {
//1. 准备关于索引的settings
Settings.Builder settings = Settings.builder()
.put("number_of_shards", 3)
.put("number_of_replicas", 1);
//2. 准备关于索引的结构mappings
XContentBuilder mappings = JsonXContent.contentBuilder()
.startObject()
.startObject("properties")
.startObject("name")
.field("type","text")
.endObject()
.startObject("age")
.field("type","integer")
.endObject()
.startObject("birthday")
.field("type","date")
.field("format","yyyy-MM-dd")
.endObject()
.endObject()
.endObject();
//3. 将settings和mappings封装到一个Request对象
CreateIndexRequest request = new CreateIndexRequest(index)
.settings(settings)
.mapping(type,mappings);
//4. 通过client对象去连接ES并执行创建索引
CreateIndexResponse resp = client.indices().create(request, RequestOptions.DEFAULT);
//5. 输出
System.out.println("resp:" + resp.toString());
}
}
检查索引存在 & 删除索引
public class Demo2 {
RestHighLevelClient client = ESClient.getClient();
String index = "person";
String type = "man";
/**
删除索引
*/
@Test
public void delete() throws IOException {
//1. 准备request对象
DeleteIndexRequest request = new DeleteIndexRequest();
request.indices(index);
//2. 通过client对象执行
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
//3. 获取返回结果
System.out.println(delete.isAcknowledged());
}
/**
检查索引存在
*/
@Test
public void exists() throws IOException {
//1. 准备request对象
GetIndexRequest request = new GetIndexRequest();
request.indices(index);
//2. 通过client去操作
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
//3. 输出
System.out.println(exists);
}
}
添加文档
@Test
public void createDoc() throws IOException {
ObjectMapper mapper = new ObjectMapper();
RestHighLevelClient client = ESClient.getClient();
String index = "person";
String type = "man";
//1. 将数据序列化为json数据
Person person = new Person(1,"张三",23,new Date());
String json = mapper.writeValueAsString(person);
//2. 准备一个request对象(手动指定id)
IndexRequest request = new IndexRequest(index,type,person.getId().toString());
request.source(json, XContentType.JSON);
//3. 通过client对象执行添加
IndexResponse resp = client.index(request, RequestOptions.DEFAULT);
//4. 输出返回结果
System.out.println(resp.getResult().toString());
}
/**
批量添加
*/
@Test
public void bulkCreateDoc() throws IOException {
//1. 准备多个json数据
Person p1 = new Person(1,"张三",23,new Date());
Person p2 = new Person(2,"李四",24,new Date());
Person p3 = new Person(3,"王五",25,new Date());
String json1 = mapper.writeValueAsString(p1);
String json2 = mapper.writeValueAsString(p2);
String json3 = mapper.writeValueAsString(p3);
//2. 创建Request,将准备好的数据封装进去
BulkRequest request = new BulkRequest();
request.add(new IndexRequest(index,type,p1.getId().toString()).source(json1,XContentType.JSON));
request.add(new IndexRequest(index,type,p2.getId().toString()).source(json2,XContentType.JSON));
request.add(new IndexRequest(index,type,p3.getId().toString()).source(json3,XContentType.JSON));
//3. 用client执行
BulkResponse resp = client.bulk(request, RequestOptions.DEFAULT);
//4. 输出结果
System.out.println(resp.toString());
}
更新 & 删除文档
/**
批量删除
*/
public void bulkDeleteDoc() throws IOException {
//1. 封装Request对象
BulkRequest request = new BulkRequest();
request.add(new DeleteRequest(index,type,"1"));
request.add(new DeleteRequest(index,type,"2"));
request.add(new DeleteRequest(index,type,"3"));
//2. client执行
BulkResponse resp = client.bulk(request, RequestOptions.DEFAULT);
//3. 输出
System.out.println(resp);
}
@Test
public void deleteDoc() throws IOException {
//1. 封装Request对象
DeleteRequest request = new DeleteRequest(index,type,"1");
//2. client执行
DeleteResponse resp = client.delete(request, RequestOptions.DEFAULT);
//3. 输出结果
System.out.println(resp.getResult().toString());
}
@Test
public void updateDoc() throws IOException {
//1. 创建一个Map,指定需要修改的内容
Map<String,Object> doc = new HashMap<>();
doc.put("name","张大三");
String docId = "1";
//2. 创建request对象,封装数据
UpdateRequest request = new UpdateRequest(index,type,docId);
request.doc(doc);
//3. 通过client对象执行
UpdateResponse update = client.update(request, RequestOptions.DEFAULT);
//4. 输出返回结果
System.out.println(update.getResult().toString());
}
Elasticsearch查询专题
数据结构
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SmsLogs {
private String id;// 唯一ID 1
private Date createDate;// 创建时间
private Date sendDate; // 发送时间
private String longCode;// 发送的长号码
private String mobile;// 下发手机号
private String corpName;// 发送公司名称
private String smsContent; // 下发短信内容
private Integer state; // 短信下发状态 0 成功 1 失败
private Integer operatorId; // '运营商编号 1 移动 2 联通 3 电信
private String province;// 省份
private String ipAddr; //下发服务器IP地址
private Integer replyTotal; //短信状态报告返回时长(秒)
private Integer fee; // 费用
}
term & terms查询(不分词完全匹配)
term的査询是代表完全匹配,搜索之前不会对你搜索的关键字进行分词,对你的关键字去文档分词库中去匹配内容。
@Test
public void termQuery() throws IOException {
//1. 创建Request对象
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.from(0);
builder.size(5);
builder.query(QueryBuilders.termQuery("province","北京"));
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 获取到_source中的数据,并展示
for (SearchHit hit : resp.getHits().getHits()) {
Map<String, Object> result = hit.getSourceAsMap();
System.out.println(result);
}
}
terms和term的査询机制是一样,都不会将指定的査询关键字进行分词,直接去分词库中匹配,找到相应文档内容。
terms是在针对一个字段包含多个值的时候使用。
term:where province=北京;
terms:where province=北京or province=? or province = ?
@Test
public void termsQuery() throws IOException {
//1. 创建request
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 封装查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.termsQuery("province","北京","山西"));
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出_source
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
match查询(可分词查询)
match查询属于高层查询,他会根据你查询的字段类型不一样,采用不同的查询方式。
- 查询的是日期或者是数值的话,他会将你基于的字符串查询内容转换为日期或者数值对待。
- 查询的内容是一个不能被分词的内容(keyword),match查询不会对你指定的查询关键字进行分词。
- 查询的内容时一个可以被分词的内容(text),match会将你指定的查询内容根据一定的方式去分词,去分词库中匹配指定的内容。
match查询,实际底层就是多个term查询,将多个term查询的结果给你封装到了一起。
matchAll查询
QueryBuilders.matchAllQuery()
是 Elasticsearch 中一种特殊的查询类型,它会匹配文档的所有Feil字段,相当于没有设定具体的查询条件,实际上就是一个类似 SQL 中的 SELECT * FROM table
的查询。就是指定索引下的指定type下统计所有数据
@Test
public void matchAllQuery() throws IOException {
//1. 创建Request
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchAllQuery());
builder.size(20); // ES默认只查询10条数据,如果想查询更多,添加size
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
System.out.println(resp.getHits().getHits().length);
}
- ES默认只查询10条数据,如果想查询更多,添加size
boolean match查询
基于一个Field匹配的内容,采用and或者or的方式连接
@Test
public void booleanMatchQuery() throws IOException {
//1. 创建Request
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
// 选择AND或者OR
// 使用 matchQuery 方法指定要匹配的字段为 “smsContent”,并指定要匹配的关键词为 “中国” 和 “健康”。
// 另外,通过 operator(Operator.OR) 方法设置了逻辑运算符为 OR,表示只要匹配到其中一个关键词即可返回结果。
builder.query(QueryBuilders.matchQuery("smsContent","中国 健康").operator(Operator.OR));
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
match查询
@Test
public void matchQuery() throws IOException {
//1. 创建Request
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
//-----------------------------------------------
builder.query(QueryBuilders.matchQuery("smsContent","收货安装")); // 如果不是keytext类型就分词匹配
//-----------------------------------------------
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
multi_match查询
match针对一个field做检索,multi_match针对多个field进行检索,多个field对应一个text
@Test
public void multiMatchQuery() throws IOException {
//1. 创建Request
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
//-----------------------------------------------
builder.query(QueryBuilders.multiMatchQuery("北京","province","smsContent")); // 第一个是匹配文本,后面都是可以去匹配的Field字段
//-----------------------------------------------
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
ID查询
id查询
根据单个id匹配单条数据
@Test
public void findById() throws IOException {
//1. 创建GetRequest
GetRequest request = new GetRequest(index,type,"1");
//2. 执行查询
GetResponse resp = client.get(request, RequestOptions.DEFAULT);
//3. 输出结果
System.out.println(resp.getSourceAsMap());
}
ids查询
根据多个id匹配多条数据
@Test
public void findByIds() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.idsQuery().addIds("1","2","3"));
//----------------------------------------------------------
request.source(builder);
//3. 执行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
prefix查询(Field前缀匹配)
@Test
public void findByPrefix() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.prefixQuery("corpName","盒马")); // 相当于 “盒马%” 匹配
//----------------------------------------------------------
request.source(builder);
//3. 执行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
fuzzy查询(玄学匹配)
模糊查询,我们输入字符的大概,ES就可以去根据输入的内容大概去匹配一下结果。比如你输河马先生,有可能会给你匹配出盒马鲜生。为了提高准度,可以指定前几个字符是确定的,比如我指定前两个字符是确定的,那我玄学匹配的结果会在满足前两个字符是匹配到的在通过玄学来匹配剩余的字符
@Test
public void findByFuzzy() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.fuzzyQuery("corpName","盒马先生").prefixLength(2));
//----------------------------------------------------------
request.source(builder);
//3. 执行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
wildcard查询(通配符不分词匹配)
通配查询,和MySQL中的like是一个套路,可以在査询时,在字符串中指定通配符*和占位符?
?
匹配一个字符*
匹配任意个字符
@Test
public void findByWildCard() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.wildcardQuery("corpName","中国*"));
//----------------------------------------------------------
request.source(builder);
//3. 执行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
range查询(数值范围匹配)
范围查询,只针对数值类型,对某一个Field进行大于或者小于的范围指定
@Test
public void findByRange() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.rangeQuery("fee").lte(10).gte(5));
//----------------------------------------------------------
request.source(builder);
//3. 执行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
regexp查询(正则匹配)
正则查询,通过你编写的正则表达式去匹配内容。
Ps:
prefix,fuzzy,wildcard和regexp查询效率相对比较低,要求效率比较高时,避免去使用
@Test
public void findByRegexp() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.regexpQuery("mobile","139[0-9]{8}"));
//----------------------------------------------------------
request.source(builder);
//3. 执行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
深分页Scroll(非动态的分页查询)
ES对from+size是有限制的,from和size二者之和不能超过1W
from+size在ES查询数据的方式:
- 第一步现将用户指定的关键进行分词。
- 第二步将词汇去分词库中进行检索,得到多个文档的id。
- 第三步去各个分片中去拉取指定的数据。耗时较长。
- 第四步将数据根据score进行排序。耗时较长,
- 第五步根据from的值,将查询到的数据舍弃一部分。
- 第六步返回结果。
Scroll+size在ES查询数据的方式:
- 第一步现将用户指定的关键进行分词.
- 第二步将词汇去分词库中进行检索,得到多个文档的id。
- 第三步将文档的id存放在一个ES的上下文中。
- 第四步根据你指定的size的个数去ES中检索指定个数的数据,拿完数据的文档id,会从上下文中移除。
- 第五步如果需要下一页数据,直接去ES的上下文中,从头开始找后续内容。(因为之前都移除了)
- 第六步循环第四步和第五步
Scroll查询是一种在Elasticsearch中用于遍历大量文档的机制。当你执行一个查询并且希望能够持续获取结果集,而不只是获取前几百或前几千条结果时,通常会使用Scroll查询。然而,尽管Scroll查询可以用于处理大量文档,但并不适合实时查询的场景,因为开始查询时形成一个快照,连续查询过程中,不会将新增加或修改的数据添加到查询结果中,也不支持跳页查询。
public class Demo7 {
ObjectMapper mapper = new ObjectMapper();
RestHighLevelClient client = ESClient.getClient();
String index = "sms-logs-index";
String type = "sms-logs-type";
@Test
public void scrollQuery() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定scroll信息,scroll的时间被设置为1分钟,意味着在1分钟内可以通过scroll ID持续地获取查询结果。超时后就删除上下文快照
request.scroll(TimeValue.timeValueMinutes(1L));
//3. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.size(4); // 每次查询最多返回4个文档,也就是一页4条
builder.sort("fee", SortOrder.DESC);
builder.query(QueryBuilders.matchAllQuery());
request.source(builder);
//4. 获取返回结果scrollId,source
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
String scrollId = resp.getScrollId();
System.out.println("----------首页---------");
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
while(true) {
//5. 循环 - 创建SearchScrollRequest
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); // 通过这个 scrollId 来获取下一批次的查询结果,相当于就是让多次查询都是基于这个快照中进行查询,这个id就是快照id
//6. 指定scrollId的生存时间
scrollRequest.scroll(TimeValue.timeValueMinutes(1L));
//7. 执行查询获取返回结果
SearchResponse scrollResp = client.scroll(scrollRequest, RequestOptions.DEFAULT);
//8. 判断是否查询到了数据,输出
SearchHit[] hits = scrollResp.getHits().getHits();
if(hits != null && hits.length > 0) {
System.out.println("----------下一页---------");
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsMap());
}
}else{
//9. 判断没有查询到数据-退出循环
System.out.println("----------结束---------");
break;
}
}
/**
由于 scroll 查询会在 Elasticsearch 服务器端保留上下文信息,如果不对这些查询上下文进行清除操作,这些信息会持 续占用服务器资源,直到超过默认的保留时间或指定的保留时间。这样会导致系统资源的浪费,可能导致性能下降,甚至影响服务器的稳定性。
因此,为了释放这些占用的资源,我们需要显式地发送一个 ClearScrollRequest 请求来删除之前使用的 scrollId,从而清除相应的查询上下文信息。这样可以确保及时释放服务器资源,防止资源浪费和潜在的性能问题。
**/
//10. 创建CLearScrollRequest
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
//11. 指定ScrollId
clearScrollRequest.addScrollId(scrollId);
//12. 删除ScrollId
ClearScrollResponse clearScrollResponse = client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
//13. 输出结果
System.out.println("删除scroll:" + clearScrollResponse.isSucceeded());
}
}
delete-by-query(匹配后删除)
根据term,match等查询方式去删除大量的文档
Ps: 如果你需要删除的内容,是index下的大部分数据,推荐创建一个全新的index,将保留的文档内容,添加到全新的索引
@Test
public void deleteByQuery() throws IOException {
//1. 创建DeleteByQueryRequest
DeleteByQueryRequest request = new DeleteByQueryRequest(index);
request.types(type);
//2. 指定检索的条件 和SearchRequest指定Query的方式不一样
request.setQuery(QueryBuilders.rangeQuery("fee").lt(4));
//3. 执行删除
BulkByScrollResponse resp = client.deleteByQuery(request, RequestOptions.DEFAULT);
//4. 输出返回结果
System.out.println(resp.toString());
}
复合查询(多条件组合查询)
bool查询
复合过滤器,将你的多个查询条件,以一定的逻辑组合在一起。
- must:所有的条件,用must组合在一起,表示And的意思
- must_not:将must_not中的条件,全部都不能匹配,标识Not的意思
- should:所有的条件,用should组合在一起,表示or的意思
@Test
public void BoolQuery() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// # 查询省份为武汉或者北京
boolQuery.should(QueryBuilders.termQuery("province","武汉"));
boolQuery.should(QueryBuilders.termQuery("province","北京"));
// # 运营商不是联通
boolQuery.mustNot(QueryBuilders.termQuery("operatorId",2));
// # smsContent中包含中国和平安
boolQuery.must(QueryBuilders.matchQuery("smsContent","中国"));
boolQuery.must(QueryBuilders.matchQuery("smsContent","平安"));
builder.query(boolQuery);
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
boosting查询(调整匹配分数的数值来调整排序)
boosting查询可以帮助我们去影响查询后的score。
- positive:只有匹配上positive的查询的内容,才会被放到返回的结果集中。
- negative:如果匹配上和positive并且也匹配上了negative,就可以降低这样的文档score。
- negative_boost:指定系数,必须小于1.0
关于查询时,分数是如何计算的:
- 搜索的关键字在文档中出现的频次越高,分数就越高
- 指定的文档内容越短,分数就越高
- 我们在搜索时,指定的关键字也会被分词,这个被分词的内容,被分词库匹配的个数越多,分数越高
@Test
public void BoostingQuery() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
// 第一个 Match Query 视为 positive query(正向查询),而将第二个Match Query视为negative query(负向查询)
BoostingQueryBuilder boostingQuery = QueryBuilders.boostingQuery(
QueryBuilders.matchQuery("smsContent", "收货安装"),
QueryBuilders.matchQuery("smsContent", "王五")
).negativeBoost(0.5f);
builder.query(boostingQuery);
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
Filter查询(不追求排序的高效匹配查询)
- query,根据你的査询条件,去计算文档的匹配度得到一个分数,并且根据分数进行排序,不会做缓存的。
- fiter根据你的查询条件去查询文档,不去计算分数,而且会对经常被过滤的数据进行缓存。
@Test
public void filter() throws IOException {
//1. SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.filter(QueryBuilders.termQuery("corpName","盒马鲜生"));
boolQuery.filter(QueryBuilders.rangeQuery("fee").lte(5));
builder.query(boolQuery);
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
高亮查询
高亮查询就是你用户输入的关键字,以一定的特殊样式展示给用户,让用户知道为什么这个结果被检索出来。
高亮展示的数据,本身就是文档中的一个Field,单独将Field以highlight的形式返回给你。
ES提供了一个highlight属性,和query同级别的。其参数如下:
- fragment_size:指定高亮数据那个Field展示多少个字符回来。不是指展示多少个高亮的词
- pre_tags:指定前缀标签,举个栗子
- post_tags:指定后缀标签,举个栗子
@Test
public void highLightQuery() throws IOException {
//1. SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查询条件(高亮)
SearchSourceBuilder builder = new SearchSourceBuilder();
//2.1 指定查询条件
builder.query(QueryBuilders.matchQuery("smsContent","盒马"));
//2.2 指定高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("smsContent",10)
.preTags("<font color='red'>")
.postTags("</font>");
builder.highlighter(highlightBuilder);
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 获取高亮数据,输出
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getHighlightFields().get("smsContent"));
}
}
聚合查询(对结果数据进行批量处理)
ES的聚合查询和MySQL的聚合查询类型,ES的聚合查询相比MySQL要强大的多,ES提供的统计数据的方式多
种多样。
去重计数查询(COUNT(DISTINCT XXXX))
去重计数,即cardinality,第一步先将返回的文档中的一个指定的field进行去重,统计一共有多少条。类比MySQL的
COUNT(DISTINCT XXXX)
@Test
public void cardinality() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定使用的聚合查询方式
SearchSourceBuilder builder = new SearchSourceBuilder();
// agg” 是给这个聚合取的名字,统计某个字段(这里是"province")的不重复值的数量
builder.aggregation(AggregationBuilders.cardinality("agg").field("province"));
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 获取返回结果
Cardinality agg = resp.getAggregations().get("agg");
long value = agg.getValue();
System.out.println(value);
}
范围统计查询
统计一定范围内出现的文档个数
- 比如,针对某一个Field的值在 0-100,100200,200300之间文档出现的个数分别是多少。
- 范围统计可以针对普通的数值,针对时间类型,针对ip类型都可以做相应的统计。
range,date_range,ip_range
@Test
public void range() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定使用的聚合查询方式
SearchSourceBuilder builder = new SearchSourceBuilder();
//---------------------------------------------
builder.aggregation(AggregationBuilders.range("agg").field("fee")
// 小于等于 5 的范围,表示统计字段"fee"的值在 0 到 5 之间的文档数量。
.addUnboundedTo(5)
// 5 到 10 的范围,表示统计字段"fee"的值在 5 到 10 之间的文档数量。
.addRange(5,10)
// 大于等于 10 的范围,表示统计字段"fee"的值大于等于 10 的文档数量。
.addUnboundedFrom(10));
//---------------------------------------------
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 获取返回结果
Range agg = resp.getAggregations().get("agg");
for (Range.Bucket bucket : agg.getBuckets()) {
String key = bucket.getKeyAsString();
Object from = bucket.getFrom();
Object to = bucket.getTo();
long docCount = bucket.getDocCount();
System.out.println(String.format("key:%s,from:%s,to:%s,docCount:%s",key,from,to,docCount));
}
}
统计聚合查询(avg、sum、max等)
他可以帮你查询指定Field的最大值,最小值,平均值,平方和。。。。
@Test
public void extendedStats() throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定使用的聚合查询方式
SearchSourceBuilder builder = new SearchSourceBuilder();
//---------------------------------------------
builder.aggregation(AggregationBuilders.extendedStats("agg").field("fee"));
//---------------------------------------------
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 获取返回结果
ExtendedStats agg = resp.getAggregations().get("agg");
double max = agg.getMax();
double min = agg.getMin();
System.out.println("fee的最大值为:" + max + ",最小值为:" + min);
}
地图经纬度查询
ES中提供了一个数据类型 geg_point,这个类型就是用来存储经纬度的,
- gee_distance:直线距离检索方式
- gee_bounding_box:以两个点(左上右下)确定一个矩形,获取在矩形内的全部数据
- gee-polygon:相当于定义各个坐标点,形成一个平面电子围栏,返回坐标位于该平面内的数据
@Test
public void geoPolygon() throws IOException {
//1. SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定检索方式
SearchSourceBuilder builder = new SearchSourceBuilder();
List<GeoPoint> points = new ArrayList<>();
points.add(new GeoPoint(39.99878,116.298916));
points.add(new GeoPoint(39.972576,116.29561));
points.add(new GeoPoint(39.984739,116.327661));
// 通过指定地理字段"location"和一组地理点来定义一个地理多边形区域,
// 用于检索存储在该地理字段中坐标位置在指定区域内的文档。执行该查询可以找到在所定义多边形内的地理位置数据。
builder.query(QueryBuilders.geoPolygonQuery("location",points));
request.source(builder);
//3. 执行查询
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 输出结果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
SpringBoot集成实现(7.X)
项目依赖
这里没有应用springboot版本自带elasticsearch依赖,自带的版本应该是7.x的,所以单独引入了elasticsearch8.x依赖
<!-- springboot 依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.7</version>
<relativePath/>
</parent>
<!-- elasticsearch依赖 -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
<version>2.0.1</version>
</dependency>
配置实现
@Configuration
public class ElasticSearchConfig {
@Value("${es.hosts}")
private String hosts;
@Value("${es.name:elastic}")
private String name;
@Value("${es.password:aimind}")
private String password;
@Bean
public ElasticsearchClient docqaElasticsearchClient() {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(name, password));
List<HttpHost> httpHosts = Lists.newArrayList();
String[] split = hosts.split(",");
for (int i = 0; i < split.length; i++) {
httpHosts.add(HttpHost.create(split[i]));
}
HttpHost[] httpHosts1 = httpHosts.toArray(new HttpHost[0]);
RestClient client = RestClient
.builder(httpHosts1)
.setHttpClientConfigCallback(httpAsyncClientBuilder ->
httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider).setKeepAliveStrategy((response, context) -> 180 * 1000))
.build();
ElasticsearchTransport transport = new RestClientTransport(client, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
}
基本工具类
ElasticsearchHandle工具类主要是封装elasticsearch的索引,数据对应的一些增删改查方法
@Slf4j
@Component
public class ElasticsearchHandle {
@Autowired
private ElasticsearchClient client;
/**
* 判断索引是否存在
*
* @param indexName
* @return
* @throws IOException
*/
public boolean hasIndex(String indexName) throws IOException {
BooleanResponse exists = client.indices().exists(d -> d.index(indexName));
return exists.value();
}
/**
* 删除索引
*
* @param indexName
* @throws IOException
*/
public boolean deleteIndex(String indexName) throws IOException {
DeleteIndexResponse response = client.indices().delete(d -> d.index(indexName));
return true;
}
/**
* 创建索引
*
* @param indexName
* @return
* @throws IOException
*/
public boolean createIndex(String indexName) {
try {
CreateIndexResponse indexResponse = client.indices().create(c -> c.index(indexName));
} catch (IOException e) {
log.error("索引创建失败:{}", e.getMessage());
throw new ExploException(HttpCode.INDEX_CREATE_ERROR, "创建索引失败");
}
return true;
}
/**
* 创建索引,不允许外部直接调用
*
* @param indexName
* @param mapping
* @throws IOException
*/
private boolean createIndex(String indexName, Map<String, Property> mapping) throws IOException {
CreateIndexResponse createIndexResponse = client.indices().create(c -> {
c.index(indexName).mappings(mappings -> mappings.properties(mapping));
return c;
});
return createIndexResponse.acknowledged();
}
/**
* 重新创建索引,如果已存在先删除
*
* @param indexName
* @param mapping
*/
public void reCreateIndex(String indexName, Map<String, Property> mapping) {
try {
if (this.hasIndex(indexName)) {
this.deleteIndex(indexName);
}
} catch (IOException e) {
e.printStackTrace();
throw new ExploException(HttpCode.INDEX_DELETE_ERROR, "删除索引失败");
}
try {
this.createIndex(indexName, mapping);
} catch (IOException e) {
e.printStackTrace();
throw new ExploException(HttpCode.INDEX_CREATE_ERROR, "重新创建索引失败");
}
}
/**
* 新增数据
*
* @param indexName
* @throws IOException
*/
public boolean insertDocument(String indexName, Object obj, String id) {
try {
IndexResponse indexResponse = client.index(i -> i
.index(indexName)
.id(id)
.document(obj));
return true;
} catch (IOException e) {
log.error("数据插入ES异常:{}", e.getMessage());
throw new ExploException(HttpCode.ES_INSERT_ERROR, "ES新增数据失败");
}
}
/**
* 查询数据
*
* @param indexName
* @param id
* @return
*/
public GetResponse<DocumentPartESDto> searchDocument(String indexName, String id) {
try {
GetResponse<DocumentPartESDto> getResponse = client.get(g -> g
.index(indexName)
.id(id)
, DocumentPartESDto.class
);
return getResponse;
} catch (IOException e) {
log.error("查询ES异常:{}", e.getMessage());
throw new ExploException(HttpCode.ES_SEARCH_ERROR, "查询ES数据失败");
}
}
/**
* 删除数据
*
* @param indexName
* @param id
* @return
*/
public boolean deleteDocument(String indexName, String id) {
try {
DeleteResponse deleteResponse = client.delete(d -> d
.index(indexName)
.id(id)
);
} catch (IOException e) {
log.error("删除Es数据异常:{}", e.getMessage());
throw new ExploException(HttpCode.ES_DELETE_ERROR, "数据删除失败");
}
return true;
}
}
存储数据结构体
定义一个实体,用于向ES中存储数据
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DocumentPartESDto {
private String id;
private String docId;
private String kgId;
private String content;
private String type;
}
查询专题
查询满足条件的数据
// ndexName:为索引名称 query:查询的内容 top :查询条数
public List<DocumentParagraph> search(String indexName, String query,int top) {
List<DocumentParagraph> documentParagraphs = Lists.newArrayList();
try {
SearchResponse<DocumentParagraph> search = client.search(s -> s
.index(indexName)
.query(q -> q
.match(t -> t
.field("content")
.query(query)
))
.from(0)
.size(top)
, DocumentParagraph.class
);
for (Hit<DocumentParagraph> hit : search.hits().hits()) {
DocumentParagraph pd = hit.source();
documentParagraphs.add(pd);
}
} catch (IOException e) {
log.error("查询ES异常:{}", e.getMessage());
throw new ExploException(HttpCode.ES_SEARCH_ERROR, "查询ES数据失败");
}
return documentParagraphs;
}
查询某些分组下满足条件的数据
// ndexName:为索引名称 query:查询的内容 categoryId: 查询那些类别下的数据 top :查询条数
public List<DocumentParagraph> search(String indexName, String query, List<String> categoryId,int top) {
List<DocumentParagraph> documentParagraphs = Lists.newArrayList();
List<FieldValue> values = new ArrayList<>();
for (String id : categoryId) {
values.add(FieldValue.of(id));
}
Query categoryQuery = TermsQuery.of(t -> t.field("categoryId.keyword").terms(new TermsQueryField.Builder().value(values).build()))._toQuery();
try {
SearchResponse<DocumentParagraph> search = client.search(s -> s
.index(indexName)
.query(q -> q.bool(b -> b.must(categoryQuery)
.should(sh -> sh.match(t -> t.field("content")
.query(query)))))
.highlight(h -> h.fields("content", f -> f.preTags("<em>")))
.from(0)
.size(top)
, DocumentParagraph.class
);
for (Hit<DocumentParagraph> hit : search.hits().hits()) {
DocumentParagraph pd = hit.source();
documentParagraphs.add(pd);
}
} catch (IOException e) {
log.error("查询ES异常:{}", e.getMessage());
throw new ExploException(HttpCode.ES_SEARCH_ERROR, "查询ES数据失败");
}
return documentParagraphs;
}
/**
* 高亮数据提取
*/
private DocumentParagraph highLight(Hit<DocumentParagraph> hit) {
DocumentParagraph paragraph = hit.source();
try {
Map<String, List<String>> highlight = hit.highlight();
List<String> list = highlight.get("content");
String join = StringUtils.join(list, "");
if (StringUtils.isNotBlank(join)) {
paragraph.setContent(join);
paragraph.setScore(hit.score());
}
} catch (Exception e) {
log.error("获取ES高亮数据异常:{}", e.getMessage());
}
return paragraph;
}
/**
*解析高亮数据
*/
Map<String, List<String>> highlight = hit.highlight();
List<String> list = highlight.get("content");
String join = StringUtils.join(list, "");
if (StringUtils.isNotBlank(join)) {
paragraph.setContent(join);
}
查询某个文档的数据并高亮显示关键词
// ndexName:为索引名称 id:文档id
public List<DocumentParagraph> searchDocumentByDocId(String indexName, String id) {
List<DocumentParagraph> documentParagraphs = Lists.newArrayList();
try {
SearchResponse<DocumentParagraph> search = client.search(s -> s
.index(indexName)
.query(q -> q.term(t -> t.field("docId").value(id))), DocumentParagraph.class
);
for (Hit<DocumentParagraph> hit : search.hits().hits()) {
DocumentParagraph pd = hit.source();
documentParagraphs.add(pd);
}
} catch (IOException e) {
log.error("查询ES异常:{}", e.getMessage());
throw new ExploException(HttpCode.ES_SEARCH_ERROR, "查询ES数据失败");
}
return documentParagraphs;
}
查询多条件OR匹配查询
List<Query> list = Lists.newArrayList();
list.add(MatchQuery.of(t -> t.field("docNo").query(query).boost(1.2F))._toQuery());
list.add(MatchQuery.of(t -> t.field(" docTitle ").query(query).boost(1.5F))._toQuery());
list.add(MatchQuery.of(t -> t.field("docContext ").query(query).boost(1.8F))._toQuery());
try {
SearchResponse<DocumentParagraph> search = client.search(s -> s.index(indexName)
.query(q -> q.bool(b -> b.should(list)))
.from(0)
.size(top)
, DocumentParagraph.class
);
增删专题
批量增加数据
参数介绍
ndexName:为索引名称
objs:插入实体
public boolean batchInsertDocument(String indexName, List<DocumentParagraph> objs) {
try {
List<BulkOperation> bulkOperationArrayList = new ArrayList<>();
for (DocumentParagraph obj : objs) {
bulkOperationArrayList.add(BulkOperation.of(o -> o.index(i -> i.document(obj))));
}
BulkResponse bulkResponse = client
.bulk(b -> b.index(indexName).operations(bulkOperationArrayList));
return true;
} catch (IOException e) {
log.error("数据插入ES异常:{}", e.getMessage());
throw new ExploException(HttpCode.ES_INSERT_ERROR, "ES新增数据失败");
}
}
批量删除数据
删除文章下的所有数据
参数介绍
ndexName:为索引名称
docId:文章id
public Boolean deleteDocument(String indexName, String docId) {
try {
client.deleteByQuery(d -> d
.index(indexName)
.query(q -> q.term(t -> t.field("docId").value(docId))));
} catch (IOException e) {
log.error("查询ES异常:{}", e.getMessage());
}
return true;
}
ElatsicSearch在不同业务的应用搭建思路
日志分析应用:
- 收集日志数据:使用 Logstash 或其他日志收集工具,将应用、服务器、网络设备等产生的日志数据发送到 Elasticsearch。
- 索引数据:在 Elasticsearch 中创建索引,定义日志数据的 mapping 设置,并将日志数据存储在相应的索引中。
- 查询分析:通过 Elasticsearch 的查询语言进行日志检索、过滤和聚合,以查找特定时间段、特定类型或关键词的日志数据。
- 可视化展示:结合 Kibana,创建仪表板和可视化图表,实时展现日志数据的关键指标、趋势,帮助用户监控系统状态、检测异常等。
实时监控应用:
- 收集监控数据:使用 Metricbeat、Filebeat 等数据收集工具,将系统指标数据、日志数据等发送到 Elasticsearch。
- 存储数据:将监控数据存储在 Elasticsearch 中,并根据需要设置索引、mapping,并定期维护数据存储策略。
- 实时查询:利用 Elasticsearch 的实时搜索和聚合功能,监控系统性能指标、服务状态等情况,及时发现问题并做出响应。
- 可视化展示:通过 Kibana 创建实时监控仪表板,展现监控指标的实时变化、趋势,帮助用户快速了解系统运行状态。
内容搜索应用:
- 导入数据:将需要搜索的内容数据(如商品信息、新闻文章等)导入 Elasticsearch 索引中,配置相应的 mapping 设置。
- 进行搜索:使用 Elasticsearch 的全文搜索功能,在文档中查找匹配查询条件的内容,支持多种查询方式,如全文搜索、多字段搜索、过滤等。
- 提供搜索接口:通过 RESTful API 或其他方式,将搜索功能嵌入到网站、应用程序中,让用户可以搜索并获取相关内容。
- 优化搜索体验:根据用户反馈和搜索日志数据,调整搜索效果,优化查询性能,提高搜索结果的准确性。
数据分析应用:
- 索引数据:将需要分析的数据导入 Elasticsearch,定义索引结构,根据数据特点设置 mapping。
- 聚合分析:利用 Elasticsearch 提供的聚合功能,进行复杂的数据聚合计算,如求和、平均、最小值、最大值等,以及对数据进行分桶分组。
- 使用查询:结合 Elasticsearch 的查询语言,筛选出符合条件的数据进行分析,支持复杂的查询操作和过滤条件。
- 可视化展示:使用 Kibana 创建各种图表、可视化图形,展现数据分析的结果,帮助用户直观理解数据间的关联和趋势,支持业务决策。
原文地址:https://blog.csdn.net/weixin_73077810/article/details/136389249
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!