Elasticsearch高级操作及集群
昨天我们学习了,对于Elastic Search基本的索引,映射,文档相关的增删改查操作。但是对于Elastic Search还有一些更为复杂的高级操作。
通过批量操作,我们可以一次向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 批量操作*/@Testpublic void test2() throws IOException {//创建bulkrequest对象,整合所有操作BulkRequest bulkRequest =new BulkRequest();/*# 1. 删除1号记录# 2. 添加2号记录# 3. 修改2号记录 名称为 “景天”*///添加对应操作//添加1号记录Map firstMap=new HashMap<>();firstMap.put("name","南风");firstMap.put("age",18);IndexRequest indexRequest1=new IndexRequest("teacher").id("2").source(map);bulkRequest.add(indexRequest1);//添加2号记录Map secondMap=new HashMap<>();secondMap.put("name","长风");secondMap.put("age", 19);IndexRequest indexRequest2=new IndexRequest("teacher").id("2").source(map);bulkRequest.add(indexRequest2);//3. 修改3号记录 名称为 “景天”,年龄为19Map 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*/@Testpublic void test3() throws IOException {//1.查询所有数据,mysqlList 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条*/@Testpublic void matchAll() throws IOException {//2. 构建查询请求对象,指定查询的索引名称SearchRequest searchRequest=new SearchRequest("product");//4. 创建查询条件构建器SearchSourceBuilderSearchSourceBuilder sourceBuilder=new SearchSourceBuilder();//6. 查询条件QueryBuilder queryBuilder= QueryBuilders.matchAllQuery();//5. 指定查询条件sourceBuilder.query(queryBuilder);//3. 添加查询条件构建器 SearchSourceBuildersearchRequest.source(sourceBuilder);// 8 . 添加分页信息 不设置 默认10条
// sourceBuilder.from(0);
// sourceBuilder.size(100);//1. 查询,获取查询结果SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);//7. 获取命中对象 SearchHitsSearchHits hits = searchResponse.getHits();//7.1 获取总记录数Long total= hits.getTotalHits().value;System.out.println("总数:"+total);//7.2 获取Hits数据 数组SearchHit[] result = hits.getHits();//获取json字符串格式的数据List 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
@Testpublic void testTerm() throws IOException {//2. 构建查询请求对象,指定查询的索引名称SearchRequest searchRequest = new SearchRequest("product");//4. 创建查询条件构建器SearchSourceBuilderSearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//6. 查询条件TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "手机充电器");//5. 指定查询条件searchSourceBuilder.query(termQueryBuilder);//3. 添加查询条件构建器 SearchSourceBuildersearchRequest.source(searchSourceBuilder);//1. 查询,获取查询结果SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);//7. 获取命中对象 SearchHitsSearchHits hits = search.getHits();//7.1 获取总记录数long value = hits.getTotalHits().value;ArrayList 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
@Testpublic void testMatch() throws IOException {//2. 构建查询请求对象,指定查询的索引名称SearchRequest searchRequest = new SearchRequest("product");//4. 创建查询条件构建器SearchSourceBuilderSearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//6. 查询条件MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "手机充电器");// 设置关键字查询的运算符//matchQueryBuilder.operator(Operator.AND);//5. 指定查询条件searchSourceBuilder.query(matchQueryBuilder);//3. 添加查询条件构建器 SearchSourceBuildersearchRequest.source(searchSourceBuilder);//1. 查询,获取查询结果SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);//7. 获取命中对象 SearchHitsSearchHits hits = search.getHits();//7.1 获取总记录数long value = hits.getTotalHits().value;ArrayList 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)
# queryStringGET 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.构建boolQueryBoolQueryBuilder 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-100RangeQueryBuilder 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 获取命中对象 SearchHitsSearchHits hits = searchResponse.getHits();//7.1 获取总记录数Long total= hits.getTotalHits().value;System.out.println("总数:"+total);// aggregations 对象Aggregations aggregations = searchResponse.getAggregations();//将aggregations 转化为mapMap 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();Mapmap=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
手机
GET product/_search
{"query": {"match": {"title": "充电器"}},"highlight": {"fields": {"title": {"pre_tags": "","post_tags": ""}}}
}
@Testpublic void testHighLight() throws IOException {// 构建针对索引"product"的查询请求SearchRequest searchRequest = new SearchRequest("prouct");// 创建SearchSourceBuilderSearchSourceBuilder 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 items = new ArrayList<>();SearchHit[] h = hits.getHits();for (int i = 0; i < value; i++) {Product item = JSON.parseObject(h[i].getSourceAsString(), Product.class);// 显示生成的高亮字符串Map 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 = 半数以上备选主节点数)。