Elasticsearch高级操作及集群
通过批量操作,我们可以一次向Elastic Search发送多条增删改查操作。从而达到一定程度上节省带宽的操作
# 准备工作定义索引,及其映射
PUT teacher
{
"mappings": {
"properties": {
"id": {"type": "long"},
"name": {"type": "text"},
"age": {"type": "integer"}
}
}
}
批量操作脚本
#批量操作
#新增1号
#新增2号
#更新2号
#删除2号
POST _bulk
{"create": {"_index": "teacher", "_id": "2"}}
{"id": 100, "name": "南风", "age": 18}
{"create": {"_index": "teacher", "_id": "2"}}
{"id": 101,"name": "北风", "age": 18}
{"update": {"_index": "teacher", "_id": "2"}}
{"doc": {"name":"东风", "age": 19}}
{"delete": {"_index":"teacher", "_id": "2"}}
/**
* Bulk 批量操作
*/
@Test
public void test2() throws IOException {
//创建bulkrequest对象,整合所有操作
BulkRequest bulkRequest =new BulkRequest();
/*
# 1. 删除1号记录
# 2. 添加2号记录
# 3. 修改2号记录 名称为 “东风”
*/
//添加对应操作
//添加1号记录
Map<String, Object> firstMap=new HashMap<>();
firstMap.put("name","南风");
firstMap.put("age",18);
IndexRequest indexRequest1=new IndexRequest("teacher").id("2").source(map);
bulkRequest.add(indexRequest1);
//添加2号记录
Map<String, Object> secondMap=new HashMap<>();
secondMap.put("name","北风");
secondMap.put("age", 19);
IndexRequest indexRequest2=new IndexRequest("teacher").id("2").source(map);
bulkRequest.add(indexRequest2);
//3. 修改3号记录 名称为 “东风”,年龄为19
Map<String, Object> mapUpdate=new HashMap<>();
mapUpdate.put("name","东风");
UpdateRequest updateRequest=new UpdateRequest("teacher","2").doc(mapUpdate);
bulkRequest.add(updateRequest);
// 删除2号记录
DeleteRequest deleteRequest=new DeleteRequest("teacher","2");
bulkRequest.add(deleteRequest);
//执行批量操作
BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(response.status());
}
PUT product
{
"mappings": {
"properties": {
"id":{"type": "long"},
"image": {"type": "keyword"},
"status": {"type": "integer"},
"sellPoint": {
"type": "text",
"analyzer": "ik_max_word"
},
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"num":{"type": "integer"},
"cid": {"type": "long"},
"price": {"type": "double"},
"limitNum": {"type": "integer"},
"created": {"type": "date"},
"updated": {"type": "date"}
}
}
}
/**
* 从Mysql 批量导入 elasticSearch
*/
@Test
public void test3() throws IOException {
//1.查询所有数据,mysql
List<Product> products = productMapper.findAll();
//2.bulk导入
BulkRequest bulkRequest=new BulkRequest();
//2.1 循环goodsList,创建IndexRequest添加数据
for (Product product : products) {
//将product对象转换为json字符串
String data = JSON.toJSONString(product);
IndexRequest indexRequest=new IndexRequest("product").source(data,XContentType.JSON);
bulkRequest.add(indexRequest);
}
BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(response.status());
}
match all查询,相当于不加查询条件的查询索引中所有的文档
GET product/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 100
}
/**
* 查询所有
* 1. matchAll
* 2. 将查询结果封装为Goods对象,装载到List中
* 3. 分页。默认显示10条
*/
@Test
public void matchAll() throws IOException {
//2. 构建查询请求对象,指定查询的索引名称
SearchRequest searchRequest=new SearchRequest("product");
//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[] result = hits.getHits();
//获取json字符串格式的数据
List<Product> products = new ArrayList<>();
for (SearchHit searchHit : result) {
String sourceAsString = searchHit.getSourceAsString();
//转为java对象
Product product = JSON.parseObject(sourceAsString, Product.class);
products.add(product);
}
System.out.println(items);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VOdQjUqA-1669974393059)(Elastic Search2.assets/match-all.png)]
term查询和字段类型有关系,首先回顾一下ElasticSearch两个数据类型
ElasticSearch两个数据类型:
term查询:不会对查询条件进行分词。但是注意,term查询,查询text类型字段时,文档中类型为text类型的字段本身仍然会分词
GET product/_search
{
"query": {
"term": {
"title": {
"value": "手机充电器"
}
}
}
}
Java API
@Test
public void testTerm() throws IOException {
//2. 构建查询请求对象,指定查询的索引名称
SearchRequest searchRequest = new SearchRequest("product");
//4. 创建查询条件构建器SearchSourceBuilder
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//6. 查询条件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "手机充电器");
//5. 指定查询条件
searchSourceBuilder.query(termQueryBuilder);
//3. 添加查询条件构建器 SearchSourceBuilder
searchRequest.source(searchSourceBuilder);
//1. 查询,获取查询结果
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
//7. 获取命中对象 SearchHits
SearchHits hits = search.getHits();
//7.1 获取总记录数
long value = hits.getTotalHits().value;
ArrayList<Product> items = new ArrayList<>();
SearchHit[] h = hits.getHits();
for (int i = 0; i < value; i++) {
Product product = JSON.parseObject(h[i].getSourceAsString(), Item.class);
items.add(product);
}
System.out.println(items);
}
match查询的特征:
•会对查询条件进行分词。
•然后将分词后的查询条件和目标字段分词后的词条进行等值匹配
•默认取并集(OR),即只要查询条件中的一个分词和目标字段值的一个分词(词条)匹配,即认为匹配查询条件
# match查询
GET product/_search
{
"query": {
"match": {
"title": "手机充电器"
}
},
"size": 500
}
match 的默认搜索(or 并集)例如:华为手机,会分词为 “华为”,“手机” 只要出现其中一个词条都会认为词条匹配
match的 and(交集) 搜索,例如:例如:华为手机,会分词为 “华为”,“手机” 但要求“华为”,和“手机”同时出现在词条中,才算词条匹配
GET product/_search
{
"query": {
"match": {
"title": {
"query": "华为手机",
"operator": "and"
}
}
},
"size": 500
}
Java API
@Test
public void testMatch() throws IOException {
//2. 构建查询请求对象,指定查询的索引名称
SearchRequest searchRequest = new SearchRequest("product");
//4. 创建查询条件构建器SearchSourceBuilder
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//6. 查询条件
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "手机充电器");
// 设置关键字查询的运算符
//matchQueryBuilder.operator(Operator.AND);
//5. 指定查询条件
searchSourceBuilder.query(matchQueryBuilder);
//3. 添加查询条件构建器 SearchSourceBuilder
searchRequest.source(searchSourceBuilder);
//1. 查询,获取查询结果
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
//7. 获取命中对象 SearchHits
SearchHits hits = search.getHits();
//7.1 获取总记录数
long value = hits.getTotalHits().value;
ArrayList<Product> items = new ArrayList<>();
SearchHit[] h = hits.getHits();
for (int i = 0; i < value; i++) {
Product product = JSON.parseObject(h[i].getSourceAsString(), Product.class);
items.add(product);
}
System.out.println(items);
}
wildcard查询: wildcard查询:会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)
# wildcard 查询。查询条件分词,模糊查询
GET product/_search
{
"query": {
"wildcard": {
"title": {
"value": "手机*"
}
}
}
}
[A-Z a-z 0-9_] 表示一个大小写英文字符,或者0-9的数字字符,或者下划线字符_
+号多次出现
(.)*为任意字符
正则查询取决于正则表达式的效率
GET product/_search
{
"query": {
"regexp": {
"title": "[A-Z a-z 0-9_]+(.)*"
}
}
}
对keyword类型支持比较好
# 前缀查询 对keyword类型支持比较好
GET product/_search
{
"query": {
"prefix": {
"brandName": {
"value": "三"
}
}
}
}
//模糊查询
WildcardQueryBuilder wildQuery = QueryBuilders.wildcardQuery("title", "充电*");//充电后多个字符
//正则查询
RegexpQueryBuilder regexQuery = QueryBuilders.regexpQuery("title", "[A-Z a-z 0-9_]+(.)*");
//前缀查询
PrefixQueryBuilder prefixQuery = QueryBuilders.prefixQuery("title", "充电");
queryString 多条件查询
query_string:可以识别query中的连接符(or 、and)
# queryString
GET product/_search
{
"query": {
"query_string": {
"fields": ["title","sellPoint"],
"query": "耳机 AND 充电器"
}
}
}
java代码
QueryStringQueryBuilder query = QueryBuilders.queryStringQuery("耳机充电器").field("title").field("sellPoint");
simple_query_string:不识别query中的连接符(or 、and),查询时会将 “耳机”、“and”、“充电器”当做普通的查询内容来查询
GET product/_search
{
"query": {
"simple_query_string": {
"fields": ["title","sellPoint"],
"query": "耳机 AND 充电器"
}
}
}
java代码
QueryStringQueryBuilder query = QueryBuilders.queryStringQuery("耳机充电器").field("title").field("sellPoint")
GET product/_search
{
"query": {
"range": {
"price": {
"gte": 100,
"lte": 1000
}
}
},
"sort": [
{
"price": {
"order": "desc"
}
}
]
}
//范围查询 以price 价格为条件
RangeQueryBuilder query = QueryBuilders.rangeQuery("price");
//指定下限
query.gte(100);
//指定上限
query.lte(1000);
sourceBuilder.query(query);
//排序 价格 降序排列
sourceBuilder.sort("price",SortOrder.DESC);
boolQuery:对多个查询条件连接。其组成主要分为如下四个部分:
# must
GET product/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"title": {
"value": "充电器"
}
}
},
{
"match": {
"sellPoint": "快充"
}
}
]
}
}
}
# must_not
GET product/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"title": "充电器"
}
}
]
}
}
}
# should 中的多个条件是or关系
GET product/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"title": {
"value": "充电器"
}
}
},
{
"term": {
"sellPoint": {
"value": "小菜鸡"
}
}
}
]
}
}
}
# filter
GET product/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"title": {
"value": "充电器"
}
}
},
{
"match": {
"sellPoint": "快充"
}
}
]
}
}
}
这里有几点需要注意:
# boolquery 包含多个部分
GET product/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"title": {
"value": "充电器"
}
}
}
],
"filter":[
{
"term": {
"title": "原装"
}
},
{
"range":{
"price": {
"gte": 40,
"lte": 100
}
}
}
]
}
}
}
JAVA API:
布尔查询:boolQuery
//1.构建boolQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//2.构建各个查询条件
//2.1 查询品牌名称为:华为
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "耳机");
boolQuery.must(termQueryBuilder);
//2.2. 查询标题包含:手机
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("title", "原装");
boolQuery.filter(matchQuery);
//2.3 查询价格在:40-100
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
rangeQuery.gte(40);
rangeQuery.lte(100);
boolQuery.filter(rangeQuery);
sourceBuilder.query(boolQuery);
聚合查询分为两种类型:
# 聚合查询
# 指标聚合 聚合函数
GET product/_search
{
"query": {
"match": {
"title": "耳机"
}
},
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}
# 桶聚合 分组
GET product/_search
{
"query": {
"match": {
"title": "充电器"
}
},
"aggs": {
"price_bucket": {
"terms": {
"field": "price",
"size": 100
}
}
}
}
JAVA API
/**
* 聚合查询:桶聚合,分组查询
* 1. 查询title包含充电器的数据
* 2. 查询充电器的价格列表
*/
@Test
public void testAggQuery() throws IOException {
SearchRequest searchRequest=new SearchRequest("product");
SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "充电器");
sourceBuilder.query(queryBuilder);
// 查询价格列表 只展示前100条
AggregationBuilder aggregation=AggregationBuilders.terms("price_bucket").field("price").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获取price_bucket 对象 使用Aggregation的子类接收 buckets属性在Terms接口中体现
// Aggregation price_bucket = aggregationMap.get("price_bucket");
Terms price_bucket =(Terms) aggregationMap.get("price_bucket");
//获取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);
}
高亮三要素:
默认前后缀 :em
<em>手机em>
GET product/_search
{
"query": {
"match": {
"title": "充电器"
}
},
"highlight": {
"fields": {
"title": {
"pre_tags": "",
"post_tags": ""
}
}
}
}
@Test
public void testHighLight() throws IOException {
// 构建针对索引"product"的查询请求
SearchRequest searchRequest = new SearchRequest("prouct");
// 创建SearchSourceBuilder
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "充电器");
searchSourceBuilder.query(matchQueryBuilder);
// 构造高亮查询条件
HighlightBuilder highlighter = new HighlightBuilder();
highlighter.field("title").preTags("").postTags("");
searchSourceBuilder.highlighter(highlighter);
searchRequest.source(searchSourceBuilder);
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = search.getHits();
long value = hits.getTotalHits().value;
ArrayList<Product> items = new ArrayList<>();
SearchHit[] h = hits.getHits();
for (int i = 0; i < value; i++) {
Product item = JSON.parseObject(h[i].getSourceAsString(), Product.class);
// 显示生成的高亮字符串
Map<String, HighlightField> highlightFields = h[i].getHighlightFields();
//System.out.println(highlightFields);
HighlightField HighlightField = highlightFields.get("title");
Text[] fragments = HighlightField.fragments();
//替换
item.setTitle(fragments[0].toString());
items.add(item);
}
System.out.println(items);
}
我们启动的一个Elasticsearch进程,我们称之为为一个Elasticsearch节点(Node),多个Elasticsearch节点,可以组成一个Elasticsearch集群,接下来,我们就来了解下Elasticsearch集群相关的知识
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I3WqSQRz-1669974393062)(Elastic Search2.assets/shard-1652621281595.png)]
无论是单机还是集群模式,ES中的索引数据都是以分片的形式存在的,一个索引的一个分片中只存储该索引中的一部分数据,即一个索引中的文档数据,被存储在其所属的若干个分片中,每个分片只存储索引部分数据。
对于一个索引所包含的所有分片,又被分成了两种:
所以,在ES集群中,一个索引的多个分片数据,就保存在不同的ES服务器实例或者说不同的ES Node上,一个单机版的ES服务器,也可以看做是只包含一个ES Node的ES集群,所以一个ES Node可以包含索引的多个分片数据。
这里还有三点细节需要注意:
我们先来看看文档数据的存储:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OQu9heF9-1669974393063)(Elastic Search2.assets/文档的保存.png)]
在保存一篇文档的时候,我们讲解了如何决定一篇文档所在的分片的,这一过程我们称为文档路由。当ES散列文档ID时就会发生文档的路由,来决定文档应该索引到哪个分片中
再来看看在集群中搜索文档数据的过程,当在索引中搜索的时候,Elastic Search会在索引的所有分片中进行查找,这些分片可以是主分片,也可以是副本分片,原因是主分片和副本分片通常包含一样的文档。ES在索引的主分片和副本分片中进行搜索请求的负载均衡,使得副本分片对于搜索的性能和容错都有帮助。下面看看具体的搜索过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L9vcsSEm-1669974393063)(Elastic Search2.assets/文档的搜索.png)]
在一个ES集群中,包含多个ES Node,这多个ES Node又可以扮演不同的角色,实现不同的功能,主要有以下几种角色:
在这其中,Data Node和Coordinating Node都可以有多个,但是Matster Node作为管理集群的"大脑"。正常情况下,当主节点无法工作时,会从备选主节点中选举一个出来变成新主节点,原主节点回归后变成备选主节点
但有时因为网络抖动等原因,主节点没能及时响应,集群误以为主节点挂了,选举了一个新主节点,此时一个es集群中有了两个主节点,其他节点不知道该听谁的调度,结果将是灾难性的!这种类似一个人得了精神分裂症,就被称之为“脑裂”现象。
之所以产生脑裂问题的原因是主节点因为各种原因,在收到请求后未能及时响应,导致主节点未能及时响应的原因,一般主要有以下几点:
网络抖动
内网一般不会出现es集群的脑裂问题,可以监控内网流量状态。外网的网络出现问题的可能性大些
节点负载
如果主节点同时承担数据节点的工作,可能会因为工作负载大而导致对应的 ES 实例停止响。
内存回收
由于数据节点上es进程占用的内存较大,较大规模的内存回收操作也能造成es进程失去响应。
如何解决脑裂问题呢?
不要把主节点同时设为数据节点(node.master和node.data不要同时设为true),node.master=true意味着该节点有竞选Master Node的资格,node.date=true,意味着该节点扮演数据节点的角色
将节点响应超时(discovery.zen.ping_timeout)稍稍设置长一些(默认是3秒),避免误判。
设置需要超过半数的备选节点同意,才能发生主节点重选,类似需要参议院半数以上通过,才能弹劾现任总统。(discovery.zen.minimum_master_nodes = 半数以上备选主节点数)。