海门市规划建设局网站,主题资源网站制作平台,枣庄seo推广,建个网站多少钱ElasticSearch 高级
1 javaApi操作es环境搭建
在elasticsearch官网中提供了各种语言的客户端#xff1a;https://www.elastic.co/guide/en/elasticsearch/client/index.html
而Java的客户端就有两个#xff1a; 不过Java API这个客户端#xff08;Transport Client#…ElasticSearch 高级
1 javaApi操作es环境搭建
在elasticsearch官网中提供了各种语言的客户端https://www.elastic.co/guide/en/elasticsearch/client/index.html
而Java的客户端就有两个 不过Java API这个客户端Transport Client已经在7.0以后过期了而且在8.0版本中将直接废弃。所以我们会学习Java REST Client 然后再选择High Level REST Client这个。
Java REST Client 其实就是利用Java语言向 ES服务发 Http的请求因此请求和操作与前面学习的REST API 一模一样。
1.1 工程搭建及初始化
1.1.1 创建工程引入依赖
新建基于Maven的Java项目相关信息如下
pom.xml
propertiesproject.build.sourceEncodingUTF-8/project.build.sourceEncodingproject.reporting.outputEncodingUTF-8/project.reporting.outputEncodingjava.version1.8/java.version
/properties
dependencies!--elastic客户端--!--elastic客户端--dependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-high-level-client/artifactIdversion7.4.2/version/dependency!-- Junit单元测试 --dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/version/dependency!--lombok Data --dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.8/version/dependency!--JSON工具 --dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.49/version/dependency!--common工具--dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactIdversion3.8.1/version/dependency
/dependencies实体类
com.it.esdemo.pojo.User
package com.it.sh.esdemo.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
/*** Description:* Version: V1.0*/
Data
AllArgsConstructor
public class User {private Long id;private String name;// 姓名private Integer age;// 年龄private String gender;// 性别private String note;// 备注
}扩展
使用Lombok需要两个条件
1引入依赖
!--lombok--
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.8/version
/dependency2编辑器idea安装插件
在线装参考https://plugins.jetbrains.com/plugin/6317-lombok 1.1.2 初始化连接ES
在官网上可以看到连接ES的教程https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-getting-started-initialization.html
首先需要与ES建立连接ES提供了一个客户端RestHighLevelClient。
代码如下
RestHighLevelClient client new RestHighLevelClient(RestClient.builder(new HttpHost(localhost, 9200, http),new HttpHost(localhost, 9201, http)));ES中的所有操作都是通过RestHighLevelClient来完成的 为了后面测试方便我们写到一个单元测试中并且通过Before注解来初始化客户端连接。
com.it.sh.esdemo.ElasticSearchTest
package com.it.sh.esdemo;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.After;
import org.junit.Before;
import java.io.IOException;
//ES测试类
public class ElasticSearchTest {//客户端对象private RestHighLevelClient client;/*** 建立连接*/Beforepublic void init() throws IOException {//创建Rest客户端client new RestHighLevelClient(RestClient.builder(//如果是集群则设置多个主机注意端口是http协议的端口new HttpHost(localhost, 9200, http)
// ,new HttpHost(localhost, 9201, http)
// ,new HttpHost(localhost, 9202, http)));}/*** 创建索引库-测试* throws Exception*/Testpublic void testCreateIndex() throws Exception{System.out.println(client);// org.elasticsearch.client.RestHighLevelClient6c61a903}
/*** 关闭客户端连接*/Afterpublic void close() throws IOException {client.close();}
}
1.2 创建索引库及映射
开发中往往库和映射的操作一起完成官网详细文档地址https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/_index_apis.html
这里我们主要实现库和映射的创建。查询、删除等功能大家可参考文档自己实现。 1.2.1 思路分析
按照官网给出的步骤创建索引包括下面四个步骤
创建CreateIndexRequest对象并指定索引库名称指定settings配置指定mapping配置发起请求得到响应
其实仔细分析与我们在Kibana中的Rest风格API完全一致
PUT /hello
{settings: {number_of_shards: 3,number_of_replicas: 1},mappings: {}
}1.2.2 设计映射规则
Java代码中设置mapping依然与REST中一致需要JSON风格的映射规则。因此我们先在kibana中给User实体类定义好映射规则。
谨记三个是否原则
User包括下面的字段 Id主键在ES中是唯一标示 typelong name姓名 typekeyword是否分词不分词是否索引需要在姓名查询则需要索引是否存储存储 age年龄 typeinteger是否分词不分词是否索引索引是否存储存储 gender性别 typekeyword是否分词不分词是否索引索引是否存储存储 note备注用户详细信息 typetext是否分词分词使用ik_max_word是否索引索引是否存储存储
映射如下
PUT /user
{settings: {number_of_shards: 1,number_of_replicas: 1},mappings: {properties: {id: {type: long},name:{type: keyword},age:{type: integer},gender:{type: keyword},note:{type: text,analyzer: ik_max_word}}}
}1.2.3 代码实现
我们在上面新建的ElasticDemo类中新建单元测试完成代码思路就是之前分析的4步骤
创建CreateIndexRequest对象并指定索引库名称指定settings配置指定mapping配置发起请求得到响应
package com.it.sh.esdemo;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
private RestHighLevelClient client;
/*** 创建索引* throws IOException*/Testpublic void testCreateIndex() throws IOException {// 1.创建CreateIndexRequest对象并指定索引库名称CreateIndexRequest request new CreateIndexRequest(user);// 2.指定settings配置(可以默认)request.settings(Settings.builder().put(index.number_of_shards, 3).put(index.number_of_replicas, 1));// 3.指定mapping配置request.mapping({\n properties: {\n id: {\n type: long\n },\n name:{\n type: keyword\n },\n age:{\n type: integer\n },\n gender:{\n type: keyword\n },\n note:{\n type: text,\n analyzer: ik_max_word\n }\n }\n },//指定映射的内容的类型为jsonXContentType.JSON);// 4.发起请求得到响应同步操作CreateIndexResponse response client.indices().create(request, RequestOptions.DEFAULT);
//打印结果System.out.println(response response.isAcknowledged());}返回结果
response true2 javaApi操作es文档操作
2.1 新增修改文档
文档操作包括新增文档、查询文档、修改文档、删除文档等。
CRUD官网地址https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-supported-apis.html
新增的官网地址https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-document-index.html
2.1.1 思路分析
根据官网文档实现的步骤如下
准备文档数据创建IndexRequest对象并指定索引库名称指定新增的数据的id将新增的文档数据变成JSON格式将JSON数据添加到IndexRequest中发起请求得到结果
2.1.2 代码实现
新增文档 /*** 测试插入一个文档* throws IOException*/Testpublic void addDocument() throws Exception{//1. 准备文档数据User user new User(110L, 张三, 22, 0, 上海市青浦区徐金珍);//2. 创建IndexRequest对象并指定索引库名称IndexRequest indexRequest new IndexRequest(user);//3. 指定新增的数据的idindexRequest.id(user.getId().toString());//4. 将新增的文档数据变成JSON格式// user.setAge(null);String userJson JSON.toJSONString(user);//5. 将JSON数据添加到IndexRequest中indexRequest.source(userJson, XContentType.JSON);//6. 发起请求得到结果IndexResponse response client.index(indexRequest, RequestOptions.DEFAULT);System.out.println(indexResponse response.getResult());
}结果
indexResponse CREATED注意新增的ID一致时是执行修改操作 我们直接测试过新增的时候如果ID存在则变成修改我们试试再次执行刚才的代码可以看到结果变了
indexResponse UPDATED结论在ES中如果ID一致则执行修改操作其实质是先删除后添加。
2.2 根据ID查询文档
官网地址https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-document-get.html
2.2.1 思路分析
这里的查询是根据id查询必须知道文档的id才可以。
根据官网文档实现的步骤如下
创建GetRequest 对象并指定索引库名称、文档ID发起请求得到结果从结果中得到source是json字符串将JSON反序列化为对象
2.2.2 代码实现 /*** 测试根据id查询一个文档* throws IOException*/Testpublic void testfindDocumentById() throws Exception{//1. 创建GetRequest 对象并指定索引库名称、文档IDGetRequest getRequest new GetRequest(user, 110);//2. 发起请求得到结果GetResponse response client.get(getRequest, RequestOptions.DEFAULT);//3. 从结果中得到source是json字符串String sourceAsString response.getSourceAsString();//4. 将JSON反序列化为对象User user JSON.parseObject(sourceAsString, User.class);System.out.println(user);}结果如下
User(id110, name张三, agenull, gender0, note上海市青浦区徐金珍)2.3 删除文档
官网地址https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-document-delete.html
2.3.1 实例分析
创建DeleteRequest对象指定索引库名称、文档ID发起请求
2.3.2 代码实现 /*** 根据id删除文档* throws IOException*/Testpublic void testDeleteDocumentById() throws IOException {// 1.创建DeleteRequest对象指定索引库名称、文档IDDeleteRequest request new DeleteRequest(user,110);// 2.发起请求DeleteResponse deleteResponse client.delete(request, RequestOptions.DEFAULT);
System.out.println(deleteResponse deleteResponse.getResult());}结果
deleteResponse DELETED2.4 批量处理
如果我们需要把数据库中的所有用户信息都导入索引库可以批量查询出多个用户但是刚刚的新增文档是一次新增一个文档这样效率太低了。
因此ElasticSearch提供了批处理的方案BulkRequest
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-document-bulk.html
2.4.1 批量导入脚本
# 批量导入的脚本
POST _bulk
{index:{_index:user,_type:_doc,_id:1}}
{age:18,gender:1,id:1,name:Rose,note:Rose同学在学表演11}
{index:{_index:user,_type:_doc,_id:2}}
{age:38,gender:1,id:2,name:Jack,note:Jack同学在学JavaEE}
{index:{_index:user,_type:_doc,_id:3}}
{age:38,gender:1,id:2,name:Jack,note:Jack同学在学JavaEE}
{index:{_index:user,_type:_doc,_id:4}}
{age:23,gender:0,id:3,name:小红,note:小红同学在学唱歌}
{index:{_index:user,_type:_doc,_id:5}}
{age:20,gender:1,id:4,name:小明,note:小明同学在学JavaSE}
{index:{_index:user,_type:_doc,_id:6}}
{age:33,gender:1,id:5,name:达摩,note:达摩和尚在达摩院学唱歌}
{index:{_index:user,_type:_doc,_id:7}}
{age:24,gender:1,id:6,name:鲁班,note:鲁班同学走在乡间小路上}
{index:{_index:user,_type:_doc,_id:8}}
{age:26,gender:0,id:7,name:孙尚香,note:孙尚香同学想带阿斗回东吴}
{index:{_index:user,_type:_doc,_id:9}}
{age:27,gender:1,id:8,name:李白,note:李白同学在山顶喝着酒唱着歌}
{index:{_index:user,_type:_doc,_id:10}}
{age:28,gender:0,id:9,name:甄姬,note:甄姬同学弹奏一曲东风破}
{index:{_index:user,_type:_doc,_id:11}}
{age:27,gender:0,id:10,name:虞姬,note:虞姬同学在和项羽谈情说爱}2.4.2 思路分析 A BulkRequest can be used to execute multiple index, update and/or delete operations using a single request. 一个BulkRequest可以在一次请求中执行多个 新增、更新、删除请求。
所以BulkRequest就是把多个其它增、删、改请求整合然后一起发送到ES来执行。
我们拿批量新增来举例步骤如下
从数据库查询文档数据创建BulkRequest对象创建多个IndexRequest对象组织文档数据并添加到BulkRequest中发起请求
2.4.3 代码实现 /*** 大量数据批量添加* throws IOException*/Testpublic void testBulkAddDocumentList() throws IOException {// 1.从数据库查询文档数据//第一步准备数据源。本案例使用List来模拟数据源。ListUser users Arrays.asList(new User(1L, Rose, 18, 1, Rose同学在学表演),new User(2L, Jack, 38, 1, Jack同学在学JavaEE),new User(3L, 小红, 23, 0, 小红同学在学唱歌),new User(4L, 小明, 20, 1, 小明同学在学JavaSE),new User(5L, 达摩, 33, 1, 达摩和尚在达摩院学唱歌),new User(6L, 鲁班, 24, 1, 鲁班同学走在乡间小路上),new User(7L, 孙尚香, 26, 0, 孙尚香同学想带阿斗回东吴),new User(8L, 李白, 27, 1, 李白同学在山顶喝着酒唱着歌),new User(9L, 甄姬, 28, 0, 甄姬同学弹奏一曲东风破),new User(10L, 虞姬, 27, 0, 虞姬同学在和项羽谈情说爱));// 2.创建BulkRequest对象BulkRequest bulkRequest new BulkRequest();// 3.创建多个IndexRequest对象并添加到BulkRequest中for (User user : userList) {bulkRequest.add(new IndexRequest(user).id(user.getId().toString()).source(JSON.toJSONString(user), XContentType.JSON));}// 4.发起请求BulkResponse bulkResponse client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(status: bulkResponse.status());}结果如下
status: OK可以再Kibana中通过GET /user/_search看到查询的结果。
提示
可以批量处理增删改 3 javaApi操作es文档搜索
ElasticSearch的强大之处就在于它具备了完善切强大的查询功能。
搜索相关功能主要包括 基本查询 分词查询 词条查询 范围查询 布尔查询 Filter功能 source筛选 排序 分页 高亮 聚合
官方文档https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-search.html
3.1 相关API说明
3.1.1 构建查询条件API
SearchSourceBuilder
在Java客户端中SearchSourceBuilder就是用来构建上面提到的大JSON对象其中包含了5个方法
query(QueryBuilder)查询条件sort(String, SortOrder)排序条件from(int)和size(int)分页条件highlight(HighlightBuilder)高亮条件aggregation(AggregationBuilder)聚合条件
如图 是不是与REST风格API的JSON对象一致
接下来再逐个来看每一个查询子属性。
查询条件QueryBuilders
SearchSourceBuilder的query(QueryBuilder)方法用来构建查询条件而查询分为
分词查询MatchQuery词条查询TermQuery布尔查询BooleanQuery范围查询RangeQuery模糊查询FuzzyQuery…
这些查询有一个统一的工具类来提供QueryBuilders 3.1.2 搜索结果API
在Kibana中回顾看一下搜索结果 搜索得到的结果整体是一个JSON对象包含下列2个属性 hits查询结果其中又包含两个属性 total总命中数量 hits查询到的文档数据是一个数组数组中的每个对象就包含一个文档结果又包含 _source文档原始信息highlight高亮结果信息 aggregations聚合结果对象其中包含多个属性属性名称由添加聚合时的名称来确定 gender_agg这个是我们创建聚合时用的聚合名称其中包含聚合结果 buckets聚合结果数组
Java客户端中的SearchResponse代表整个JSON结果
SearchResponse
Java客户端中的SearchResponse代表整个JSON结果包含下面的方法 包含两个方法
getHits()返回的是SearchHits代表查询结果getAggregations()返回的是Aggregations代表聚合结果
SearchHits查询结果
SearchHits代表查询结果的JSON对象 包含下面的方法 核心方法有3个
getTotalHists()返回TotalHists总命中数getHits()返回SearchHit数组getMaxScore()返回float文档的最大得分
SearchHit结果对象
SearchHit封装的就是结果数组中的每一个JSON对象 包含这样的方法 getSourceAsString()返回的是_sourcegetHighLightFields()返回是高亮结果
3.2 查询所有-matchAll
3.2.1 脚本
GET /user/_search
{query: {match_all: {}}
}3.2.2 JavaAPI
3.2.2.1 思路分析 创建SearchSourceBuilder对象 添加查询条件QueryBuilders如添加排序、分页等其它条件 创建SearchRequest对象并制定索引库名称 添加SearchSourceBuilder对象到SearchRequest对象source中 发起请求得到结果 解析结果SearchResponse 获取总条数 获取SearchHits数组并遍历 获取其中的_source是JSON数据把_source反序列化为User对象
3.2.2.2 代码实现 /*** 查询所有* throws IOException*/Testpublic void matchAllSearch() throws IOException {// 1.创建SearchSourceBuilder对象SearchSourceBuilder sourceBuilder new SearchSourceBuilder();// 1.1.添加查询条件QueryBuilders这里选择match_all查询所有sourceBuilder.query(QueryBuilders.matchAllQuery());// 1.2.添加排序、分页等其它条件(暂忽略)
// 2.创建SearchRequest对象并指定索引库名称SearchRequest request new SearchRequest(user);// 3.添加SearchSourceBuilder对象到SearchRequest对象中request.source(sourceBuilder);// 4.发起请求得到结果SearchResponse response client.search(request, RequestOptions.DEFAULT);// 5.解析结果SearchHits searchHits response.getHits();// 5.1.获取总条数long total searchHits.getTotalHits().value;System.out.println(total total);// 5.2.获取SearchHit数组并遍历SearchHit[] hits searchHits.getHits();for (SearchHit hit : hits) {//获取分数System.out.println(文档得分hit.getScore());// - 获取其中的_source是JSON数据String json hit.getSourceAsString();// - 把_source反序列化为User对象User user JSON.parseObject(json, User.class);System.out.println(user user);}}3.2.2.3 测试运行 3.3 词条查询-termQuery
3.3.1 脚本
term查询和字段类型有关系首先回顾一下ElasticSearch两个数据类型
ElasticSearch两个数据类型
text会分词不支持聚合keyword不会分词将全部内容作为一个词条支持聚合
term查询不会对查询条件进行分词。
# 词条查询
GET /user/_search
{query: {term: {name: {value: 小红}}}
}3.3.2 JavaAPI
3.3.2.1 思路分析 创建SearchSourceBuilder对象 添加查询条件QueryBuilders.termQuery() 创建SearchRequest对象并制定索引库名称 添加SearchSourceBuilder对象到SearchRequest对象source中 发起请求得到结果 解析结果SearchResponse 获取总条数 获取SearchHits数组并遍历 获取其中的_source是JSON数据把_source反序列化为User对象
3.3.2.2 代码实现 /*** 词条查询termQuery-不分词* throws Exception*/Testpublic void termQuery() throws Exception{//1. 创建SearchSourceBuilder对象SearchSourceBuilder sourceBuilder new SearchSourceBuilder();// 1. 添加查询条件QueryBuilders.termQuery()sourceBuilder.query(QueryBuilders.termQuery(name, 小红));//2. 创建SearchRequest对象并制定索引库名称SearchRequest request new SearchRequest(user);//3. 添加SearchSourceBuilder对象到SearchRequest对象source中request.source(sourceBuilder);//4. 发起请求得到结果SearchResponse response client.search(request, RequestOptions.DEFAULT);//5. 解析结果SearchResponseSearchHits searchHits response.getHits();// 1. 获取总条数System.out.println(总记录数 searchHits.getTotalHits().value);// 2. 获取SearchHits数组并遍历for (SearchHit searchHit : searchHits) {// * 获取其中的_source是JSON数据String userJson searchHit.getSourceAsString();// * 把_source反序列化为User对象User user JSON.parseObject(userJson, User.class);System.out.println(user);}}3.3.2.3 测试运行 3.4 分词匹配查询-matchQuery
3.4.1 脚本
match查询
会对查询条件进行分词。然后将分词后的查询条件和词条进行等值匹配默认取并集OR
# match查询
GET /user/_search
{query: {match: {note: 唱歌 javaee}}
}
# 查看分词效果
GET /_analyze
{text: 唱歌 javaee,analyzer: ik_max_word
}3.4.2 JavaAPI
3.4.2.1 思路分析
我们通过上面的代码发现很多的代码都是重复的所以我们来抽取一下通用代码。
我们只需要传递构建的条件对象即可完成查询。
3.4.2.2 代码实现
抽取通用方法代码 /*** 抽取通用构建查询条件执行查询方法* throws Exception*/public void printResultByQuery(QueryBuilder queryBuilder) throws Exception{//1. 创建SearchSourceBuilder对象SearchSourceBuilder sourceBuilder new SearchSourceBuilder();// ************ 构建查询条件************sourceBuilder.query(queryBuilder);//2. 创建SearchRequest对象并制定索引库名称SearchRequest request new SearchRequest(user);//3. 添加SearchSourceBuilder对象到SearchRequest对象source中request.source(sourceBuilder);//4. 发起请求得到结果SearchResponse response client.search(request, RequestOptions.DEFAULT);//5. 解析结果SearchResponseSearchHits searchHits response.getHits();// 1. 获取总条数System.out.println(总记录数 searchHits.getTotalHits().value);// 2. 获取SearchHits数组并遍历for (SearchHit searchHit : searchHits) {// * 获取其中的_source是JSON数据String userJson searchHit.getSourceAsString();// * 把_source反序列化为User对象User user JSON.parseObject(userJson, User.class);System.out.println(user);}}基于抽取方法测试 matchQuery 匹配查询 /*** 匹配查询MatchQuery 对条件进行分词* throws Exception*/Testpublic void matchQuery() throws Exception{MatchQueryBuilder queryBuilder QueryBuilders.matchQuery(note, 唱歌 javaee);printResultByQuery(queryBuilder);}3.4.2.3 测试运行 小结
term query会去倒排索引中寻找确切的term它并不知道分词器的存在。这种查询适合keyword 、numeric、datematch query知道分词器的存在。并且理解是如何被分词的
3.5 范围排序查询-rangesort
3.5.1 脚本
# 范围查询排序
GET user/_search
{query: {range: {age: { # 范围查询字段gte: 22,lt: 27}}},sort: [ # 排序如果是多个条件则在数组中添加排序列即可{id: {order: asc}}]
}注意: 不能使用分词的字段排序 3.5.2 JavaAPI
3.5.2.1 思路分析
构建范围查询对象 QueryBuilders.rangeQuery在 sourceBuilder 添加排序条件排序是对结果的重组对条件不产生影响
3.5.2.2 代码实现
编写测试方法
/*** 条件查询 排序* throws Exception*/
Test
public void rangeQuery() throws Exception{RangeQueryBuilder queryBuilder QueryBuilders.rangeQuery(age);// 22 age 27queryBuilder.gte(22);queryBuilder.lt(27);printResultByQuery(queryBuilder);
}在printResultByQuery方法中sourceBuilder.query(queryBuilder)后添加排序 // ***** 添加排序
sourceBuilder.sort(id, SortOrder.DESC);3.5.2.3 测试运行 3.6 查询所有过滤结果-boolQuery
boolQuery对多个查询条件连接。
连接方式
mustand条件必须成立must_notnot条件必须不成立shouldor条件可以成立filter条件必须成立性能比must高。不会计算得分
得分: 即条件匹配度,匹配度越高得分越高
3.6.1 脚本
# 查询note中包含同学
# 且性别为女的
# 年龄在20到30之间的
GET user/_search
{query: {bool: {must: [{match: {note: 同学}}],filter:[ {term: {gender: 0}},{range:{age: {gte: 20,lte: 30}}}]}}
}bool查询中添加查询条件一般是一个即可然后在后面根据结果过滤这样效率会比较高。 3.6.2 JavaAPI
3.6.2.1 思路分析
布尔查询boolQuery
查询note中包含同学 - match且性别为女的 - term年龄在20到30之间的 - range
must 、filter为连接方式
term、match为不同的查询方式
3.6.2.2 代码实现
/*** 匹配查询BoolQuery 布尔查询过滤* throws Exception*/
Test
public void boolQuery() throws Exception{// 1.构建bool条件对象BoolQueryBuilder queryBuilder QueryBuilders.boolQuery();// 2.构建matchQuery对象,查询备注信息note包含: 同学MatchQueryBuilder matchQueryBuilder QueryBuilders.matchQuery(note, 同学);queryBuilder.must(matchQueryBuilder);
// 3.过滤姓名gender性别为女0TermQueryBuilder termQueryBuilder QueryBuilders.termQuery(gender, 0);queryBuilder.filter(termQueryBuilder);
// 4.过滤年龄age在20-30RangeQueryBuilder rangeQueryBuilder QueryBuilders.rangeQuery(age).gte(20).lte(30);queryBuilder.filter(rangeQueryBuilder);printResultByQuery(queryBuilder);
}3.6.2.3 测试运行 3.7 分页查询-from、Size
默认情况下ES会设置size10查询10条记录。 通过from和size来指定分页的开始位置及每页大小。
3.7.1 脚本
# 分页查询
GET user/_search
{query: {bool: {must: [{match: {note: 同学}}]}},sort: [{id: {order: asc}}], from: 1, # 开始记录数 (page-1) * sizesize: 2
}3.7.2 JavaAPI
3.7.2.1 思路分析
设置bool查询match匹配设置id排序设置分页查询
3.7.2.2 代码实现
新增查询方法设置查询条件 /*** 布尔查询 分页* throws Exception*/Testpublic void testBoolQueryByPage() throws Exception{// 1.构建bool条件对象BoolQueryBuilder queryBuilder QueryBuilders.boolQuery();// 2.构建matchQuery对象,查询相信信息note为: 同学MatchQueryBuilder matchQueryBuilder QueryBuilders.matchQuery(note, 同学);queryBuilder.must(matchQueryBuilder);printResultByQuery(queryBuilder);}在 printResultByQuery 设置分页参数
// ***** 设置分页 from size
int page 2; // 当前页
int size 2; // 一页显示条数
int from (page - 1) * size; // 每一页起始条数
sourceBuilder.from(from);
sourceBuilder.size(size);3.7.2.3 测试运行 3.8 高亮查询-highlight
高亮是在搜索结果中把搜索关键字标记出来因此必须使用match这样的条件搜索。
elasticsearch中实现高亮的语法比较简单
高亮三要素 pre_tags前置标签可以省略默认是em post_tags后置标签可以省略默认是em fields需要高亮的字段 title这里声明title字段需要高亮后面可以为这个字段设置特有配置也可以空
3.8.1 脚本
GET user/_search
{query: {match: {note: 同学}},highlight: { # 设置高亮fields: {note: { # 设置高亮显示的字段pre_tags: font colorred, # 高亮显示前缀post_tags: /font # 高亮显示后缀}}}
}结果 3.8.2 JavaAPI
3.8.2.1 思路分析
创建高亮对象设置高亮三要素解析高亮结果封装到结果集中
3.8.2.2 代码实现
在printResultByQuery创建高亮对象设置高亮三要素
// ***** 设置高亮三要素
HighlightBuilder highlight SearchSourceBuilder.highlight();
highlight.field(note); // 高亮显示域
highlight.preTags(font colorred); // 高亮显示前缀
highlight.postTags(/font); // 高亮显示后缀
sourceBuilder.highlighter(highlight);在printResultByQuery执行完成后解析结果并封装
//5. 解析结果SearchResponse
SearchHits searchHits response.getHits();
// 1. 获取总条数
System.out.println(总记录数 searchHits.getTotalHits().value);
// 2. 获取SearchHits数组并遍历
for (SearchHit searchHit : searchHits) {// 获取其中的_source是JSON数据String userJson searchHit.getSourceAsString();// 把_source反序列化为User对象User user JSON.parseObject(userJson, User.class);
// ***** 解析高亮数据HighlightField highlightField searchHit.getHighlightFields().get(note); // get(高亮显示域名称)Text[] fragments highlightField.getFragments();String note StringUtils.join(fragments);// 判断如果是可以获取到数据则更新到用户对象中if (StringUtils.isNotBlank(note)) {user.setNote(note);}System.out.println(user);
}3.9 聚合查询-aggregation
3.9.1 脚本
# 按照性别分桶 分桶后计算每个分桶的年龄平均值
GET user/_search
{size: 0,aggs: { terms_by_gender:{terms: {field: gender},aggs: {avg_by_age: {avg: {field: age}}}}}
}结果: 3.9.2 桶分组查询JavaAPI
1.11.2.1 思路分析
新建一个测试类ElasticSearchAggsTest实现步骤
创建SearchRequest对象并制定索引库名称创建SearchSourceBuilder对象设置分组相关参数添加SearchSourceBuilder对象到SearchRequest对象source中执行查询得到查询结果解析分组查询数据
1.11.2.2 代码实现
/*** 文档聚合统计* 作者 it* 创建日期 2023/3/3 8:54**/
public class EsDemo05 {RestHighLevelClient client;Testpublic void aggregations() throws IOException {//1. 创建搜索请求SearchRequest searchRequest new SearchRequest(user);// 封装查询条件SearchSourceBuilder builder new SearchSourceBuilder();// 通过工具类 AggregationBuilders 可以快捷的构建 聚合条件// 方法名: 聚合类型 参数1: 自定义的聚合名称TermsAggregationBuilder termsBuilder AggregationBuilders.terms(terms_by_gender).field(gender);AvgAggregationBuilder avgBuilder AggregationBuilders.avg(avg_by_age).field(age);// 分桶之后再求平均值termsBuilder.subAggregation(avgBuilder);builder.aggregation(termsBuilder);// 设置搜索条件内容searchRequest.source(builder);//2. 执行搜索SearchResponse searchResponse client.search(searchRequest, RequestOptions.DEFAULT);// 获取聚合结果 总的聚合结果Aggregations aggregations searchResponse.getAggregations();// 根据自定义的聚合名称 找到对应的聚合类型处理结果// 注意: 你是什么聚合类型用对应的聚合类型接口来接收Terms termsResult aggregations.get(terms_by_gender);// 处理的分桶信息List? extends Terms.Bucket buckets termsResult.getBuckets();for (Terms.Bucket bucket : buckets) {System.out.println(当前分桶的key bucket.getKeyAsString());System.out.println(当前分桶的文档数量 bucket.getDocCount());// 获取 子聚合的总结果Aggregations subAggs bucket.getAggregations();// 在子聚合结果中 找到对应自定名称的聚合处理结果Avg avgResult subAggs.get(avg_by_age);System.out.println(当前分桶的平均值avgResult.getValue());}}/*** 初始化es的客户端*/Beforepublic void init(){client new RestHighLevelClient(RestClient.builder(new HttpHost(192.168.200.150,9200)));}
/*** 关闭客户端*/Afterpublic void close(){try {client.close();} catch (IOException e) {e.printStackTrace();}}
}2 ElasticSearch 集群
2.1 集群概述
单点的elasticsearch存在哪些可能出现的问题呢
单台机器存储容量有限单服务器容易出现单点故障无法实现高可用单服务的并发处理能力有限
所以为了应对这些问题我们需要对elasticsearch搭建集群 集群和分布式 集群多个人做一样的事。分布式多个人做不一样的事 集群解决的问题 让系统高可用分担请求压力 分布式解决的问题 分担存储和计算的压力提速解耦 集群和分布式架构往往是并存的 2.2 ES集群相关概念
es 集群:
ElasticSearch 天然支持分布式ElasticSearch 的设计隐藏了分布式本身的复杂性
ES集群相关概念: 集群cluster一组拥有共同的 cluster name 的 节点。 节点node) 集群中的一个 Elasticearch 实例 索引index) es存储数据的地方。相当于关系数据库中的database概念 分片shard 索引可以被拆分为不同的部分进行存储称为分片。在集群环境下一个索引的不同分片可以拆分到不同的节点中 解决问题数据量太大单点存储量有限的问题。 此处我们把数据分成3片shard0、shard1、shard2主分片Primary shard相对于副本分片的定义。 副本分片Replica shard每个主分片可以有一个或者多个副本数据和主分片一样。
数据备份可以保证高可用但是每个分片备份一份所需要的节点数量就会翻一倍成本实在是太高了
为了在高可用和成本间寻求平衡我们可以这样做
首先对数据分片存储到不同节点然后对每个分片进行备份放到对方节点完成互相备份
这样可以大大减少所需要的服务节点数量如图我们以3分片每个分片备份一份为例 现在每个分片都有1个备份存储在3个节点
node0保存了分片0和1node1保存了分片0和2node2保存了分片1和2
2.3 集群搭建
本章节基于Docker安装。
2.3.1 集群机器规划
cluster namenode nameIP Addrhttp端口 / 通信端口itcast-esnode1192.168.200.1519200 / 9700itcast-esnode2192.168.200.1529200 / 9700itcast-esnode3192.168.200.1539200 / 9700
2.3.2 搭建步骤
1在三台机器上同时执行以下命令
docker run -id --name elasticsearch \-e http.host0.0.0.0 \-e ES_JAVA_OPTS-Xms512m -Xmx512m \-e http.cors.enabledtrue \-e http.cors.allow-origin* \-e http.cors.allow-headersX-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization \-e http.cors.allow-credentialstrue \-v es-data:/usr/share/elasticsearch/data \-v es-logs:/usr/share/elasticsearch/logs \-v es-plugins:/usr/share/elasticsearch/plugins \-v es-config:/usr/share/elasticsearch/config \--privileged \--hostname elasticsearch \-p 9200:9200 \-p 9300:9300 \-p 9700:9700 \
elasticsearch:7.4.22分别在三台机器上修改elasticsearch.yml配置文件 配置文件位置 1、查看目录数据卷
docker volume inspect es-config
[{CreatedAt: 2020-11-17T14:32:1408:00,Driver: local,Labels: null,Mountpoint: /var/lib/docker/volumes/es-config/_data,Name: es-config,Options: null,Scope: local}
]2、进入Mountpoint对应的目录
cd /var/lib/docker/volumes/es-config/_data3、修改每一台机器的配置文件
node1机器elasticsearch.yml配置
#集群名称
cluster.name: itcast-es
#节点名称
node.name: node1
#是不是有资格主节点
node.master: true
#是否存储数据
node.data: true
#最大集群节点数
node.max_local_storage_nodes: 3
#ip地址
network.host: 0.0.0.0
network.publish_host: 192.168.200.151
#端口
http.port: 9200
#内部节点之间沟通端口
transport.tcp.port: 9700
#es7.x 之后新增的配置节点发现
discovery.seed_hosts: [192.168.200.151,192.168.200.152,192.168.200.153]
#es7.x 之后新增的配置初始化一个新的集群时需要此配置来选举master
cluster.initial_master_nodes: [node1, node2,node3]
bootstrap.memory_lock: falsenode2机器elasticsearch.yml配置
#集群名称
cluster.name: itcast-es
#节点名称
node.name: node2
#是不是有资格主节点
node.master: true
#是否存储数据
node.data: true
#最大集群节点数
node.max_local_storage_nodes: 3
#ip地址
network.host: 0.0.0.0
network.publish_host: 192.168.200.152
#端口
http.port: 9200
#内部节点之间沟通端口
transport.tcp.port: 9700
#es7.x 之后新增的配置节点发现
discovery.seed_hosts: [192.168.200.151,192.168.200.152,192.168.200.153]
#es7.x 之后新增的配置初始化一个新的集群时需要此配置来选举master
cluster.initial_master_nodes: [node1, node2,node3]
bootstrap.memory_lock: falsenode3 机器elasticsearch.yml配置
#集群名称
cluster.name: itcast-es
#节点名称
node.name: node3
#是不是有资格主节点
node.master: false
#是否存储数据
node.data: true
#最大集群节点数
node.max_local_storage_nodes: 3
#ip地址
network.host: 0.0.0.0
network.publish_host: 192.168.200.153
#端口
http.port: 9200
#内部节点之间沟通端口
transport.tcp.port: 9700
#es7.x 之后新增的配置节点发现
discovery.seed_hosts: [192.168.200.151,192.168.200.152,192.168.200.153]
#es7.x 之后新增的配置初始化一个新的集群时需要此配置来选举master
cluster.initial_master_nodes: [node1, node2,node3]
bootstrap.memory_lock: false3分别重启三台es机器
docker restart elasticsearch
# 注意重启之前把 data和logs文件夹清空4访问http://192.168.200.151:9200/_cat/health?v 查看集群状态 健康状况结果解释
cluster: 集群名称
status: 集群状态 #green代表健康#yellow代表分配了所有主分片但至少缺少一个副本此时集群数据仍旧完整#red 代表部分主分片不可用可能已经丢失数据。
node.total: 代表在线的节点总数量
node.data: 代表在线的数据节点的数量
shards: 存活的分片数量
pri: 存活的主分片数量 正常情况下 shards的数量是pri的两倍。
relo: 迁移中的分片数量正常情况为 0
init: 初始化中的分片数量 正常情况为 0
unassign: 未分配的分片 正常情况为 0
pending_tasks: 准备中的任务任务指迁移分片等 正常情况为 0
max_task_wait_time: 任务最长等待时间
active_shards_percent: 正常分片百分比 正常情况为 100%可以访问http://192.168.200.153:9200/_cat/nodes?vpretty 查看集群 2.4 kibina管理集群
Docker 执行下方命令
docker run -di --name kibana \
-p 5601:5601 \
-v kibana-config:/usr/share/kibana/config \
kibana:7.4.2kibana.yml 其他配置
#支持中文
i18n.locale: zh-CN
#5602避免与之前的冲突
server.port: 5601
server.host: 0.0.0.0
server.name: kibana-itcast-cluster
elasticsearch.hosts: [http://192.168.200.151:9200,http://192.168.200.152:9200,http://192.168.200.153:9200]
elasticsearch.requestTimeout: 99999浏览器访问http://192.168.200.151:5601/app/monitoring#/no-data?_g() 2.5 JavaAPI 访问集群 //客户端对象private RestHighLevelClient client;/*** 建立连接*/Beforepublic void init() throws IOException {//创建Rest客户端client new RestHighLevelClient(RestClient.builder(//如果是集群则设置多个主机注意端口是http协议的端口new HttpHost(192.168.200.151, 9200, http),new HttpHost(192.168.200.152, 9200, http),new HttpHost(192.168.200.153, 9200, http)));}
/*** 创建索引库-测试* throws Exception*/Testpublic void testCreateIndex() throws Exception{// 1 创建CreateIndexRequest对象并指定索引库名称CreateIndexRequest indexRequest new CreateIndexRequest(user);// 2 设置指定settings配置(可以默认)indexRequest.settings(Settings.builder().put(index.number_of_shards, 3).put(index.number_of_replicas, 1));// 3 设置mappingindexRequest.mapping( {\n properties: {\n id: {\n type: long\n },\n name:{\n type: keyword\n },\n age:{\n type: integer\n },\n gender:{\n type: keyword\n },\n note:{\n type: text,\n analyzer: ik_max_word\n }\n }\n }, XContentType.JSON);
// 4 发起请求CreateIndexResponse response client.indices().create(indexRequest, RequestOptions.DEFAULT);System.out.println(response.isAcknowledged());}
/*** 关闭客户端连接*/Afterpublic void close() throws IOException {client.close();}2.6 分片配置
在创建索引时如果不指定分片配置则默认主分片1副本分片1。
在创建索引时可以通过settings设置分片 分片配置
#分片配置
#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分片标号 2.itcast-3 挂掉 3.将挂掉节点的分片自平衡到其他节点 4.itcast-3 恢复正常后节点分片将自平衡回去并不一定是原来的分片 分片与自平衡
•当节点挂掉后挂掉的节点分片会自平衡到其他节点中
注意分片数量一旦确定好不能修改。
索引分片推荐配置方案
每个分片推荐大小10-30GB分片数量推荐 节点数量 * 1~3倍
思考比如有1000GB数据应该有多少个分片多少个节点
每个分片20GB 则可以分为40个分片分片数量推荐 节点数量 * 1~3倍 -- 40/220 即20个节点
2.7 路由原理
路由原理
文档存入对应的分片ES计算分片编号的过程称为路由。
Elasticsearch 是怎么知道一个文档应该存放到哪个分片中呢
查询时根据文档id查询文档 Elasticsearch 又该去哪个分片中查询数据呢
路由算法 shard_index hash(id) % number_of_primary_shards 查询id为5的文档假如hash(5)17 根据算法17%32
2.8 脑裂
ElasticSearch 集群正常状态
一个正常es集群中只有一个主节点Master主节点负责管理整个集群。如创建或删除索引跟踪哪些节点是群集的一部分并决定哪些分片分配给相关的节点。集群的所有节点都会选择同一个节点作为主节点。
脑裂现象
脑裂问题的出现就是因为从节点在选择主节点上出现分歧导致一个集群出现多个主节点从而使集群分裂使得集群处于异常状态。 脑裂产生的原因 网络原因网络延迟 一般es集群会在内网部署也可能在外网部署比如阿里云。内网一般不会出现此问题外网的网络出现问题的可能性大些。 节点负载 主节点的角色既为master又为data。数据访问量较大时可能会导致Master节点停止响应假死状态。 JVM内存回收 当Master节点设置的JVM内存较小时引发JVM的大规模内存回收造成ES进程失去响应。
避免脑裂 网络原因discovery.zen.ping.timeout 超时时间配置大一点。默认是3S 节点负载角色分离策略 候选主节点配置为 node.master: truenode.data: false 数据节点配置为 node.master: falsenode.data: true JVM内存回收修改 config/jvm.options 文件的 -Xms 和 -Xmx 为服务器的内存一半。