01-今日内容

  1. ElasticSearch 高级操作

  2. ElasticSearch 集群管理

02-ElasticSearch高级操作

2.1-bulk批量操作-脚本

脚本:

测试用的5号文档

1
2
3
4
5
6
POST /person1/_doc/5
{
"name":"张三5号",
"age":18,
"address":"北京海淀区"
}

批量操作文本

1
2
3
4
5
6
7
8
9
10
#批量操作
#1.删除5
#新增8
#更新2号 name为2
POST _bulk
{"delete":{"_index":"person1","_id":"5"}}
{"create":{"_index":"person1","_id":"8"}}
{"name":"八号","age":18,"address":"北京"}
{"update":{"_index":"person1","_id":"2"}}
{"doc":{"name":"2号"}}

结果

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
{
"took" : 51,
"errors" : true,
"items" : [
{
"delete" : {
"_index" : "person1",
"_type" : "_doc",
"_id" : "5",
"_version" : 2,
"result" : "deleted",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 6,
"_primary_term" : 2,
"status" : 200
}
},
{
"create" : {
"_index" : "person1",
"_type" : "_doc",
"_id" : "8",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 7,
"_primary_term" : 2,
"status" : 201
}
},
{
"update" : {
"_index" : "person1",
"_type" : "_doc",
"_id" : "2",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 10,
"_primary_term" : 2,
"status" : 200
}
}
]
}

2.2-bulk批量操作-JavaAPI

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
/**
* Bulk 批量操作
*/
@Test
public void test2() throws IOException {

//创建bulkrequest对象,整合所有操作
BulkRequest bulkRequest =new BulkRequest();

/*
# 1. 删除5号记录
# 2. 添加6号记录
# 3. 修改3号记录 名称为 “三号”
*/
//添加对应操作
//1. 删除5号记录
DeleteRequest deleteRequest=new DeleteRequest("person1","5");
bulkRequest.add(deleteRequest);

//2. 添加6号记录
Map<String, Object> map=new HashMap<>();
map.put("name","六号");
IndexRequest indexRequest=new IndexRequest("person1").id("6").source(map);
bulkRequest.add(indexRequest);
//3. 修改3号记录 名称为 “三号”
Map<String, Object> mapUpdate=new HashMap<>();
mapUpdate.put("name","三号");
UpdateRequest updateRequest=new UpdateRequest("person1","3").doc(mapUpdate);

bulkRequest.add(updateRequest);
//执行批量操作


BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(response.status());

}

2.3-导入数据-分析&创建索引

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
PUT goods
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_smart"
},
"price": {
"type": "double"
},
"createTime": {
"type": "date"
},
"categoryName": {
"type": "keyword"
},
"brandName": {
"type": "keyword"
},

"spec": {
"type": "object"
},
"saleNum": {
"type": "integer"
},

"stock": {
"type": "integer"
}
}
}
}

2.4-导入数据-代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>


<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.4</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# datasource
spring:
datasource:
url: jdbc:mysql:///es?serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver


# mybatis
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml # mapper映射文件路径
type-aliases-package: com.itheima.elasticsearchdemo2.domain

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package com.itheima.elasticsearchdemo2.domain;

import com.alibaba.fastjson.annotation.JSONField;

import java.util.Date;
import java.util.Map;

public class Goods {

private int id;
private String title;
private double price;
private int stock;
private int saleNum;
private Date createTime;
private String categoryName;
private String brandName;
private Map spec;

@JSONField(serialize = false)//在转换JSON时,忽略该字段
private String specStr;//接收数据库的信息 "{}"


public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

public int getStock() {
return stock;
}

public void setStock(int stock) {
this.stock = stock;
}

public int getSaleNum() {
return saleNum;
}

public void setSaleNum(int saleNum) {
this.saleNum = saleNum;
}

public Date getCreateTime() {
return createTime;
}

public void setCreateTime(Date createTime) {
this.createTime = createTime;
}

public String getCategoryName() {
return categoryName;
}

public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}

public String getBrandName() {
return brandName;
}

public void setBrandName(String brandName) {
this.brandName = brandName;
}

public Map getSpec() {
return spec;
}

public void setSpec(Map spec) {
this.spec = spec;
}

public String getSpecStr() {
return specStr;
}

public void setSpecStr(String specStr) {
this.specStr = specStr;
}


@Override
public String toString() {
return "Goods{" +
"id=" + id +
", title='" + title + '\'' +
", price=" + price +
", stock=" + stock +
", saleNum=" + saleNum +
", createTime=" + createTime +
", categoryName='" + categoryName + '\'' +
", brandName='" + brandName + '\'' +
", spec=" + spec +
", specStr='" + specStr + '\'' +
'}';
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.itheima.elasticsearchdemo2.mapper;

import com.itheima.elasticsearchdemo2.domain.Goods;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
@Mapper
public interface GoodsMapper {


/**
* 查询所有
*/
public List<Goods> findAll();

}

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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="com.itheima.elasticsearchdemo2.mapper.GoodsMapper">



<select id="findAll" resultType="goods">
select
`id` ,
`title` ,
`price` ,
`stock` ,
`saleNum` ,
`createTime` ,
`categoryName`,
`brandName` ,
`spec` as specStr

from goods


</select>


</mapper>
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
/**
* 从Mysql 批量导入 elasticSearch
*/
@Test
public void test3() throws IOException {
//1.查询所有数据,mysql
List<Goods> goodsList = goodsMapper.findAll();

//2.bulk导入
BulkRequest bulkRequest=new BulkRequest();

//2.1 循环goodsList,创建IndexRequest添加数据
for (Goods goods : goodsList) {

//2.2 设置spec规格信息 Map的数据 specStr:{}
String specStr = goods.getSpecStr();

//将json格式字符串转为Map集合
Map map = JSON.parseObject(specStr, Map.class);

//设置spec map
goods.setSpec(map);

//将goods对象转换为json字符串
String data = JSON.toJSONString(goods);

IndexRequest indexRequest=new IndexRequest("goods").source(data,XContentType.JSON);
bulkRequest.add(indexRequest);

}


BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(response.status());

}

2.5-导入数据-代码实现-详解(选放)

转换成JSON的原因:

1
2
3
4
#spec配置的数据类型是JSON对象,所以当存放字符串的时候报错
"spec": {
"type": "object"
},

错误信息

1581164001550

3-ElasticSearch查询

3.1-matchAll-脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
# 默认情况下,es一次展示10条数据,通过from和size来控制分页
# 查询结果详解

GET goods/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 100
}

GET goods

3.2-matchAll-JavaAPI

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
/**
* 查询所有
* 1. matchAll
* 2. 将查询结果封装为Goods对象,装载到List中
* 3. 分页。默认显示10条
*/
@Test
public void matchAll() throws IOException {

//2. 构建查询请求对象,指定查询的索引名称
SearchRequest searchRequest=new SearchRequest("goods");

//4. 创建查询条件构建器SearchSourceBuilder
SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();

//6. 查询条件
QueryBuilder queryBuilder= QueryBuilders.matchAllQuery();
//5. 指定查询条件
sourceBuilder.query(queryBuilder);

//3. 添加查询条件构建器 SearchSourceBuilder
searchRequest.source(sourceBuilder);
// 8 . 添加分页信息 不设置 默认10条
// sourceBuilder.from(0);
// sourceBuilder.size(100);
//1. 查询,获取查询结果

SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

//7. 获取命中对象 SearchHits
SearchHits hits = searchResponse.getHits();

//7.1 获取总记录数
Long total= hits.getTotalHits().value;
System.out.println("总数:"+total);
//7.2 获取Hits数据 数组
SearchHit[] hits1 = hits.getHits();
//获取json字符串格式的数据
List<Goods> goodsList = new ArrayList<>();
for (SearchHit searchHit : hits1) {
String sourceAsString = searchHit.getSourceAsString();
//转为java对象
Goods goods = JSON.parseObject(sourceAsString, Goods.class);
goodsList.add(goods);
}

for (Goods goods : goodsList) {
System.out.println(goods);
}

}

设置条件的疑问点

1580909328868

3.3-termQuery

term查询和字段类型有关系,首先回顾一下ElasticSearch两个数据类型

ElasticSearch两个数据类型

1
2
3
text:会分词,不支持聚合

keyword:不会分词,将全部内容作为一个词条,支持聚合

term查询:不会对查询条件进行分词。

1
2
3
4
5
6
7
8
9
10
GET goods/_search
{
"query": {
"term": {
"title": {
"value": "华为"
}
}
}
}

term查询,查询text类型字段时,只有其中的单词相匹配都会查到,text字段会对数据进行分词

例如:查询title 为“华为”的,title type 为text

1580910336989

1580910384673

查询categoryName 字段时,categoryName字段为keyword ,keyword:不会分词,将全部内容作为一个词条,

即完全匹配,才能查询出结果

1580910596746

1
2
3
4
5
6
7
8
9
10
GET goods/_search
{
"query": {
"term": {
"categoryName": {
"value": "华为手机"
}
}
}
}

1580910648421

3.4-matchQuery

match查询:

•会对查询条件进行分词。

•然后将分词后的查询条件和词条进行等值匹配

•默认取并集(OR)

1
2
3
4
5
6
7
8
9
10
# match查询
GET goods/_search
{
"query": {
"match": {
"title": "华为手机"
}
},
"size": 500
}

match 的默认搜索(or 并集)

例如:华为手机,会分词为 “华为”,“手机” 只要出现其中一个词条都会搜索到

match的 and(交集) 搜索

例如:例如:华为手机,会分词为 “华为”,“手机” 但要求“华为”,和“手机”同时出现在词条中

总结:

  • term query会去倒排索引中寻找确切的term,它并不知道分词器的存在。这种查询适合keywordnumericdate
  • match query知道分词器的存在。并且理解是如何被分词的

3.5-模糊查询-脚本

3.5.1-wildcard查询

wildcard查询:会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)

1
2
3
4
"*华*"  包含华字的
"华*" 华字后边多个字符
"华?" 华字后边多个字符
"*华"或"?华" 会引发全表(全索引)扫描 注意效率问题
1
2
3
4
5
6
7
8
9
10
11
# wildcard 查询。查询条件分词,模糊查询
GET goods/_search
{
"query": {
"wildcard": {
"title": {
"value": "华*"
}
}
}
}

3.5.2正则查询

1
2
3
4
5
6
\W:匹配包括下划线的任何单词字符,等价于 [A-Z a-z 0-9_]   开头的反斜杠是转义符

+号多次出现

(.)*为任意字符
正则查询取决于正则表达式的效率
1
2
3
4
5
6
7
8
9
10

GET goods/_search
{
"query": {
"regexp": {
"title": "\\w+(.)*"
}
}
}

3.5.3前缀查询

对keyword类型支持比较好

1
2
3
4
5
6
7
8
9
10
11
# 前缀查询 对keyword类型支持比较好
GET goods/_search
{
"query": {
"prefix": {
"brandName": {
"value": "三"
}
}
}
}

3.6-模糊查询-JavaAPI

1
2
3
4
5
6
//模糊查询
WildcardQueryBuilder query = QueryBuilders.wildcardQuery("title", "华*");//华后多个字符
//正则查询
RegexpQueryBuilder query = QueryBuilders.regexpQuery("title", "\\w+(.)*");
//前缀查询
PrefixQueryBuilder query = QueryBuilders.prefixQuery("brandName", "三");

3.7-范围&排序查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 范围查询

GET goods/_search
{
"query": {
"range": {
"price": {
"gte": 2000,
"lte": 3000
}
}
},
"sort": [
{
"price": {
"order": "desc"
}
}
]
}
1
2
3
4
5
6
7
8
9
10
11
12
 //范围查询 以price 价格为条件
RangeQueryBuilder query = QueryBuilders.rangeQuery("price");

//指定下限
query.gte(2000);
//指定上限
query.lte(3000);

sourceBuilder.query(query);

//排序 价格 降序排列
sourceBuilder.sort("price",SortOrder.DESC);

3.8-queryString查询

queryString 多条件查询

•会对查询条件进行分词。

•然后将分词后的查询条件和词条进行等值匹配

•默认取并集(OR)

•可以指定多个查询字段

query_string:识别query中的连接符(or 、and)

1
2
3
4
5
6
7
8
9
10
11
# queryString

GET goods/_search
{
"query": {
"query_string": {
"fields": ["title","categoryName","brandName"],
"query": "华为 AND 手机"
}
}
}

simple_query_string:不识别query中的连接符(or 、and),查询时会将 “华为”、”and”、“手机”分别进行查询

1
2
3
4
5
6
7
8
9
GET goods/_search
{
"query": {
"simple_query_string": {
"fields": ["title","categoryName","brandName"],
"query": "华为 AND 手机"
}
}
}

query_string:有default_operator连接符的脚本

1
2
3
4
5
6
7
8
9
10
11
GET goods/_search
{
"query": {
"query_string": {
"fields": ["title","brandName","categoryName"],
"query": "华为手机 "
, "default_operator": "AND"
}
}
}

java代码

1
2
QueryStringQueryBuilder query = QueryBuilders.queryStringQuery("华为手机").field("title").field("categoryName")
.field("brandName").defaultOperator(Operator.AND);

simple_query_string:有default_operator连接符的脚本

1
2
3
4
5
6
7
8
9
10
GET goods/_search
{
"query": {
"simple_query_string": {
"fields": ["title","brandName","categoryName"],
"query": "华为手机 "
, "default_operator": "OR"
}
}
}

注意:query中的or and 是查询时 匹配条件是否同时出现—-or 出现一个即可,and 两个条件同时出现

default_operator的or and 是对结果进行 并集(or)、交集(and)

3.9-布尔查询-脚本

boolQuery:对多个查询条件连接。连接方式:

•must(and):条件必须成立

•must_not(not):条件必须不成立

•should(or):条件可以成立

•filter:条件必须成立,性能比must高。不会计算得分

**得分:**即条件匹配度,匹配度越高,得分越高

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
# boolquery
#must和filter配合使用时,max_score(得分)是显示的
#must 默认数组形式
GET goods/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"brandName": {
"value": "华为"
}
}
}
],
"filter":[
{
"term": {
"title": "手机"
}
},
{
"range":{
"price": {
"gte": 2000,
"lte": 3000
}
}
}

]
}
}
}
#filter 单独使用 filter可以是单个条件,也可多个条件(数组形式)
GET goods/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"brandName": {
"value": "华为"
}
}
}
]
}
}
}

3.10-布尔查询-JavaAPI

布尔查询:boolQuery

  1. 查询品牌名称为:华为
  2. 查询标题包含:手机
  3. 查询价格在:2000-3000

must 、filter为连接方式

term、match为不同的查询方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//1.构建boolQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//2.构建各个查询条件
//2.1 查询品牌名称为:华为
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brandName", "华为");
boolQuery.must(termQueryBuilder);
//2.2. 查询标题包含:手机
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("title", "手机");
boolQuery.filter(matchQuery);

//2.3 查询价格在:2000-3000
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
rangeQuery.gte(2000);
rangeQuery.lte(3000);
boolQuery.filter(rangeQuery);

sourceBuilder.query(boolQuery);

3.11-聚合查询-脚本

•指标聚合:相当于MySQL的聚合函数。max、min、avg、sum等

•桶聚合:相当于MySQL的 group by 操作。不要对text类型的数据进行分组,会失败。

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
# 聚合查询

# 指标聚合 聚合函数

GET goods/_search
{
"query": {
"match": {
"title": "手机"
}
},
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}

# 桶聚合 分组

GET goods/_search
{
"query": {
"match": {
"title": "手机"
}
},
"aggs": {
"goods_brands": {
"terms": {
"field": "brandName",
"size": 100
}
}
}
}

3.12-聚合查询-JavaAPI

聚合查询:桶聚合,分组查询

  1. 查询title包含手机的数据
  2. 查询品牌列表
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
/**
* 聚合查询:桶聚合,分组查询
* 1. 查询title包含手机的数据
* 2. 查询品牌列表
*/
@Test
public void testAggQuery() throws IOException {

SearchRequest searchRequest=new SearchRequest("goods");

SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
//1. 查询title包含手机的数据

MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "手机");

sourceBuilder.query(queryBuilder);
//2. 查询品牌列表 只展示前100条
AggregationBuilder aggregation=AggregationBuilders.terms("goods_brands").field("brandName").size(100);
sourceBuilder.aggregation(aggregation);


searchRequest.source(sourceBuilder);

SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

//7. 获取命中对象 SearchHits
SearchHits hits = searchResponse.getHits();

//7.1 获取总记录数
Long total= hits.getTotalHits().value;
System.out.println("总数:"+total);

// aggregations 对象
Aggregations aggregations = searchResponse.getAggregations();
//将aggregations 转化为map
Map<String, Aggregation> aggregationMap = aggregations.asMap();


//通过key获取goods_brands 对象 使用Aggregation的子类接收 buckets属性在Terms接口中体现

// Aggregation goods_brands1 = aggregationMap.get("goods_brands");
Terms goods_brands =(Terms) aggregationMap.get("goods_brands");

//获取buckets 数组集合
List<? extends Terms.Bucket> buckets = goods_brands.getBuckets();

Map<String,Object>map=new HashMap<>();
//遍历buckets key 属性名,doc_count 统计聚合数
for (Terms.Bucket bucket : buckets) {

System.out.println(bucket.getKey());
map.put(bucket.getKeyAsString(),bucket.getDocCount());
}

System.out.println(map);

}

3.13-高亮查询-脚本

高亮三要素:

•高亮字段

•前缀

•后缀

默认前后缀 :em

1
<em>手机</em>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET goods/_search
{
"query": {
"match": {
"title": "电视"
}
},
"highlight": {
"fields": {
"title": {
"pre_tags": "<font color='red'>",
"post_tags": "</font>"
}
}
}
}

3.14-高亮查询-JavaAPI

实施步骤:
高亮查询:
1. 设置高亮
高亮字段
前缀
后缀
2. 将高亮了的字段数据,替换原有数据

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
/**
*
* 高亮查询:
* 1. 设置高亮
* * 高亮字段
* * 前缀
* * 后缀
* 2. 将高亮了的字段数据,替换原有数据
*/
@Test
public void testHighLightQuery() throws IOException {


SearchRequest searchRequest = new SearchRequest("goods");

SearchSourceBuilder sourceBulider = new SearchSourceBuilder();

// 1. 查询title包含手机的数据
MatchQueryBuilder query = QueryBuilders.matchQuery("title", "手机");

sourceBulider.query(query);

//设置高亮
HighlightBuilder highlighter = new HighlightBuilder();
//设置三要素
highlighter.field("title");
//设置前后缀标签
highlighter.preTags("<font color='red'>");
highlighter.postTags("</font>");

//加载已经设置好的高亮配置
sourceBulider.highlighter(highlighter);

searchRequest.source(sourceBulider);

SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);


SearchHits searchHits = searchResponse.getHits();
//获取记录数
long value = searchHits.getTotalHits().value;
System.out.println("总记录数:"+value);

List<Goods> goodsList = new ArrayList<>();
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();

//转为java
Goods goods = JSON.parseObject(sourceAsString, Goods.class);

// 获取高亮结果,替换goods中的title
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField HighlightField = highlightFields.get("title");
Text[] fragments = HighlightField.fragments();
//highlight title替换 替换goods中的title
goods.setTitle(fragments[0].toString());
goodsList.add(goods);
}

for (Goods goods : goodsList) {
System.out.println(goods);
}


}

3.15-重建索引&索引别名

1
2
3
4
5
6
7
8
9
#查询别名 默认别名无法查看,默认别名同索引名
GET goods/_alias/
#结果
{
"goods" : {
"aliases" : { }
}
}

1.新建student_index_v1索引

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
# -------重建索引-----------

# 新建student_index_v1。索引名称必须全部小写

PUT student_index_v1
{
"mappings": {
"properties": {
"birthday":{
"type": "date"
}
}
}
}
#查看 student_index_v1 结构
GET student_index_v1
#添加数据
PUT student_index_v1/_doc/1
{
"birthday":"1999-11-11"
}
#查看数据
GET student_index_v1/_search

#添加数据
PUT student_index_v1/_doc/1
{
"birthday":"1999年11月11日"
}

2.重建索引:将student_index_v1 数据拷贝到 student_index_v2

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
# 业务变更了,需要改变birthday字段的类型为text

# 1. 创建新的索引 student_index_v2
# 2. 将student_index_v1 数据拷贝到 student_index_v2

# 创建新的索引 student_index_v2
PUT student_index_v2
{
"mappings": {
"properties": {
"birthday":{
"type": "text"
}
}
}
}
# 将student_index_v1 数据拷贝到 student_index_v2
# _reindex 拷贝数据
POST _reindex
{
"source": {
"index": "student_index_v1"
},
"dest": {
"index": "student_index_v2"
}
}

GET student_index_v2/_search



PUT student_index_v2/_doc/2
{
"birthday":"1999年11月11日"
}

3.创建索引库别名:

注意:DELETE student_index_v1 这一操作将删除student_index_v1索引库,并不是删除别名

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
# 思考: 现在java代码中操作es,还是使用的实student_index_v1老的索引名称。
# 1. 改代码(不推荐)
# 2. 索引别名(推荐)

# 步骤:
# 0. 先删除student_index_v1
# 1. 给student_index_v2起个别名 student_index_v1



# 先删除student_index_v1
#DELETE student_index_v1 这一操作将删除student_index_v1索引库
#索引库默认的别名与索引库同名,无法删除

# 给student_index_v1起个别名 student_index_v11
POST student_index_v2/_alias/student_index_v11
#测试删除命令
POST /_aliases
{
"actions": [
{"remove": {"index": "student_index_v1", "alias": "student_index_v11"}}
]
}
DELETE student_index_v1
# 给student_index_v2起个别名 student_index_v1
POST student_index_v2/_alias/student_index_v1

#查询别名
GET goods/_alias/


GET student_index_v1/_search
GET student_index_v2/_search

3.16 查询脚本命令集合

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346

# 默认情况下,es一次展示10条数据,通过from和size来控制分页
# 查询结果详解

GET goods/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 100
}

GET goods

# term查询

GET goods/_search
{
"query": {
"term": {
"title": {
"value": "华为"
}
}
}
}


# match查询
GET goods/_search
{
"query": {
"match": {
"title": "华为手机"
}
},
"size": 500
}



GET goods/_search
{
"query": {
"match": {
"title": {
"query": "华为手机",
"operator": "and"
}
}
}
}
GET _analyze
{
"analyzer": "ik_smart",
"text": "施华洛世奇"
}




# wildcard 查询。查询条件分词,模糊查询
GET goods/_search
{
"query": {
"wildcard": {
"title": {
"value": "华*"
}
}
}
}

# 正则查询
GET goods/_search
{
"query": {
"regexp": {
"title": "\\w+(.)*"
}
}
}

# 前缀查询
GET goods/_search
{
"query": {
"prefix": {
"brandName": {
"value": "三"
}
}
}
}


# 范围查询

GET goods/_search
{
"query": {
"range": {
"price": {
"gte": 2000,
"lte": 3000
}
}
},
"sort": [
{
"price": {
"order": "desc"
}
}
]
}

# queryString

GET goods/_search
{
"query": {
"query_string": {
"fields": ["title","categoryName","brandName"],
"query": "华为 AND 手机"
}
}
}


GET goods/_search
{
"query": {
"simple_query_string": {
"fields": ["title","categoryName","brandName"],
"query": "华为 AND 手机"
}
}
}



# boolquery

GET goods/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"brandName": {
"value": "华为"
}
}
}
],
"filter":[
{
"term": {
"title": "手机"
}
},
{
"range":{
"price": {
"gte": 2000,
"lte": 3000
}
}
}

]
}
}
}

GET goods/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"brandName": {
"value": "华为"
}
}
}
]
}
}
}


# 聚合查询

# 指标聚合 聚合函数

GET goods/_search
{
"query": {
"match": {
"title": "手机"
}
},
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}

# 桶聚合 分组

GET goods/_search
{
"query": {
"match": {
"title": "手机"
}
},
"aggs": {
"goods_brands": {
"terms": {
"field": "brandName",
"size": 100
}
}
}
}



GET goods/_search
{
"query": {
"match": {
"title": "电视"
}
},
"highlight": {
"fields": {
"title": {
"pre_tags": "<font color='red'>",
"post_tags": "</font>"
}
}
}
}


# -------重建索引-----------

# 新建student_index_v1。索引名称必须全部小写

PUT student_index_v1
{
"mappings": {
"properties": {
"birthday":{
"type": "date"
}
}
}
}

GET student_index_v1

PUT student_index_v1/_doc/1
{
"birthday":"1999-11-11"
}


GET student_index_v1/_search


PUT student_index_v1/_doc/1
{
"birthday":"1999年11月11日"
}

# 业务变更了,需要改变birthday字段的类型为text

# 1. 创建新的索引 student_index_v2
# 2. 将student_index_v1 数据拷贝到 student_index_v2

# 创建新的索引 student_index_v2
PUT student_index_v2
{
"mappings": {
"properties": {
"birthday":{
"type": "text"
}
}
}
}
# 将student_index_v1 数据拷贝到 student_index_v2
# _reindex 拷贝数据
POST _reindex
{
"source": {
"index": "student_index_v1"
},
"dest": {
"index": "student_index_v2"
}
}

GET student_index_v2/_search



PUT student_index_v2/_doc/2
{
"birthday":"1999年11月11日"
}



# 思考: 现在java代码中操作es,还是使用的实student_index_v1老的索引名称。
# 1. 改代码(不推荐)
# 2. 索引别名(推荐)

# 步骤:
# 0. 先删除student_index_v1
# 1. 给student_index_v2起个别名 student_index_v1



# 先删除student_index_v1
DELETE student_index_v1
# 给student_index_v2起个别名 student_index_v1
POST student_index_v2/_alias/student_index_v1


GET student_index_v1/_search
GET student_index_v2/_search



4-ElasticSearch 集群

4.1-集群介绍

  • 集群和分布式:

​ 集群:多个人做一样的事。

​ 分布式:多个人做不一样的事

  • 集群解决的问题:

​ 让系统高可用

​ 分担请求压力

  • 分布式解决的问题:

​ 分担存储和计算的压力,提速

​ 解耦

  • 集群和分布式架构往往是并存的

1581042245219

4.2-ES集群相关概念

es 集群:

•ElasticSearch 天然支持分布式

•ElasticSearch 的设计隐藏了分布式本身的复杂性

ES集群相关概念:

•集群(cluster):一组拥有共同的 cluster name 的 节点。

•节点(node) :集群中的一个 Elasticearch 实例

•索引(index) :es存储数据的地方。相当于关系数据库中的database概念

•分片(shard):索引可以被拆分为不同的部分进行存储,称为分片。在集群环境下,一个索引的不同分片可以拆分到不同的节点中

•主分片(Primary shard):相对于副本分片的定义。

•副本分片(Replica shard)每个主分片可以有一个或者多个副本,数据和主分片一样。

4.3-集群搭建

 参见ElasticSearch 集群-集群搭建.md

4.4-kibina管理集群

1
vim  kibana-7.4.0-linux-x86_64-cluster/config/kibana.yml

kibana.yml

1
2
3
4
5
6
7
8
9
#支持中文
i18n.locale: "zh-CN"
#5602避免与之前的冲突
server.port: 5602
server.host: "0.0.0.0"
server.name: "kibana-itcast-cluster"
elasticsearch.hosts: ["http://localhost:9201","http://localhost:9202","http://localhost:9203"]
elasticsearch.requestTimeout: 99999

4.5-JavaAPI 访问集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PUT cluster_test
{
"mappings": {
"properties": {
"name":{
"type": "text"
}
}
}
}

GET cluster_test
GET cluster_test/_search

POST /cluster_test/_doc/1
{
"name":"张三"
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

@Resource(name="clusterClient")
RestHighLevelClient clusterClient;

/**
* 测试集群
* @throws IOException
*/
@Test
public void testCluster() throws IOException {


//设置查询的索引、文档
GetRequest indexRequest=new GetRequest("cluster_test","1");

GetResponse response = clusterClient.get(indexRequest, RequestOptions.DEFAULT);
System.out.println(response.getSourceAsString());

}

ElasticSearchConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private String host1;

private int port1;

private String host2;

private int port2;

private String host3;

private int port3;

//get/set ...

@Bean("clusterClient")
public RestHighLevelClient clusterClient(){
return new RestHighLevelClient(RestClient.builder(
new HttpHost(host1,port1,"http"),
new HttpHost(host2,port2,"http"),
new HttpHost(host3,port3,"http")
));
}

application.yml

1
2
3
4
5
6
7
8
9
elasticsearch:
host: 192.168.140.130
port: 9200
host1: 192.168.140.130
port1: 9201
host2: 192.168.140.130
port2: 9202
host3: 192.168.140.130
port3: 9203

4.6-分片配置

•在创建索引时,如果不指定分片配置,则默认主分片1,副本分片1。

•在创建索引时,可以通过settings设置分片

1581043174004

1581043214369

1581043148796

分片配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#分片配置
#"number_of_shards": 3, 主分片数量
#"number_of_replicas": 1 主分片备份数量,每一个主分片有一个备份
# 3个主分片+3个副分片=6个分片
PUT cluster_test1
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"name":{
"type": "text"
}
}
}
}

1.三个节点正常运行(0、1、2分片标号)

1581044158693

2.itcast-3 挂掉

1581044220349

3.将挂掉节点的分片,自平衡到其他节点

1581044251012

4.itcast-3 恢复正常后,节点分片将自平衡回去(并不一定是原来的分片)

1581044368389

分片与自平衡

•当节点挂掉后,挂掉的节点分片会自平衡到其他节点中

注意:分片数量一旦确定好,不能修改。

索引分片推荐配置方案:

1.每个分片推荐大小10-30GB

2.分片数量推荐 = 节点数量 * 1~3倍

思考:比如有1000GB数据,应该有多少个分片?多少个节点

1.每个分片20GB 则可以分为40个分片

2.分片数量推荐 = 节点数量 * 1~3倍 –> 40/2=20 即20个节点

4.7-路由原理

路由原理

•文档存入对应的分片,ES计算分片编号的过程,称为路由。

•Elasticsearch 是怎么知道一个文档应该存放到哪个分片中呢?

•查询时,根据文档id查询文档, Elasticsearch 又该去哪个分片中查询数据呢?

•路由算法 :shard_index = hash(id) % number_of_primary_shards

1581044026981

查询id为5的文档:假如hash(5)=17 ,根据算法17%3=2

4.8-脑裂

ElasticSearch 集群正常状态:

• 一个正常es集群中只有一个主节点(Master),主节点负责管理整个集群。如创建或删除索引,跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点。

•集群的所有节点都会选择同一个节点作为主节点。

脑裂现象:

•脑裂问题的出现就是因为从节点在选择主节点上出现分歧导致一个集群出现多个主节点从而使集群分裂,使得集群处于异常状态。

1581042550583

脑裂产生的原因:

1.网络原因:网络延迟

​ •一般es集群会在内网部署,也可能在外网部署,比如阿里云。

​ •内网一般不会出现此问题,外网的网络出现问题的可能性大些。

2.节点负载

​ •主节点的角色既为master又为data。数据访问量较大时,可能会导致Master节点停止响应(假死状态)。

1581042578379

  1. JVM内存回收

    •当Master节点设置的JVM内存较小时,引发JVM的大规模内存回收,造成ES进程失去响应。

避免脑裂

1.网络原因:discovery.zen.ping.timeout 超时时间配置大一点。默认是3S

2.节点负载:角色分离策略

​ •候选主节点配置为

​ •node.master: true

​ •node.data: false

​ •数据节点配置为

​ •node.master: false

​ •node.data: true

3.JVM内存回收:修改 config/jvm.options 文件的 -Xms 和 -Xmx 为服务器的内存一半。

30-ElasticSearch 集群-集群扩容

按照集群搭建步骤再复制Es节点进行配置,参见ElasticSearch 集群-集群搭建.md

5 案例查询

5.1 查询数据准备

数据库创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14

create table `goods` (
`id` double ,
`title` varchar (300),
`price` Decimal (22),
`stock` double ,
`saleNum` double ,
`createTime` datetime ,
`categoryName` varchar (600),
`brandName` varchar (300),
`spec` varchar (600)
);
insert into `goods` (`id`, `title`, `price`, `stock`, `saleNum`, `createTime`, `categoryName`, `brandName`, `spec`) values('536563','new2 - 阿尔卡特 (OT-927) 炭黑 联通3G手机 双卡双待','299000.00',NULL,'99999','2015-03-08 21:33:18','手机','阿尔卡特','{\"机身内存\":\"16G\",\"网络\":\"联通3G\"}');
insert into `goods` (`id`, `title`, `price`, `stock`, `saleNum`, `createTime`, `categoryName`, `brandName`, `spec`) values('562379','new8- 三星 W999 黑色 电信3G手机 双卡双待双通','11.00',NULL,'99999','2015-03-08 21:27:54','手机','三星','{\"机身内存\":\"16G\",\"网络\":\"联通3G\"}');

ElasticSearch数据创建

创建索引

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
PUT goods
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_smart"
},
"price": {
"type": "double"
},
"createTime": {
"type": "date"
},
"categoryName": {
"type": "keyword"
},
"brandName": {
"type": "keyword"
},

"spec": {
"type": "object"
},
"saleNum": {
"type": "integer"
},

"stock": {
"type": "integer"
}
}
}
}
  • title:商品标题
  • price:商品价格
  • createTime:创建时间
  • categoryName:分类名称。如:家电,手机
  • brandName:品牌名称。如:华为,小米
  • spec: 商品规格。如: spec:{“屏幕尺寸”,”5寸”,”内存大小”,”128G”}
  • saleNum:销量
  • stock:库存量

添加文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST goods/_doc/1
{
"title":"小米手机",
"price":1000,
"createTime":"2019-12-01",
"categoryName":"手机",
"brandName":"小米",
"saleNum":3000,
"stock":10000,
"spec":{
"网络制式":"移动4G",
"屏幕尺寸":"4.5"
}
}