ElasticSearch7.2 从安装部署到SpringBoot集成实战(基于Windows)

简介

基本概念

常用术语

详细说明

字段类型

注意事项

ES windows 版 安装部署

ElasticSearch-head 安装部署

IK分词器安装

使用Postman创建索引

创建一个空的索引 testindex (非结构化索引)

对索引的mappings进行赋值,并转换为结构化索引

SpringBoot 集成实战


 

 

简介

https://blog.csdn.net/heiyogl/article/details/103281714

基本概念

Elasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与MySQL类似的。

 

对比关系:

索引库(indices)

Databases 数据库

类型(type)

Table 数据表

文档(Document)

Row 行

字段(Field)

Columns 列

   

 

常用术语

1.1:文档Document:用户存储在Elasticsearch中的文档;

1.2:索引 Index:由具有相同字段的文档列表组成;

1.3:节点Node:一个Elasticsearch的运行实例,是集群的构成单元;

1.4:集群Cluster:由一个或多个节点组成,对外提供服务;

1.5:分片:每个索引都有多个分片,每个分片是一个Lucene索引;

1.6:备份:拷贝一份分片就完成了分片的备份;

 

详细说明

  • 索引库(indices): indices是index的复数,代表许多的索引;
  • 类型(type): 类型是模拟mysql中的table概念,一个索引库下可以有不同类型的索引,比如商品索引,订单索引,其数据格式不同。不过这会导致索引库混乱,因此未来版本中会移除这个概念;
  • 文档(document): 存入索引库原始的数据。比如每一条商品信息,就是一个文档;
  • 字段(field): 文档中的属性;
  • 映射配置(mappings): 字段的数据类型、属性、是否索引、是否存储等特性。

 

在Elasticsearch有一些集群相关的概念:

  • 索引集(Indices,index的复数):逻辑上的完整索引
  • 分片(shard):数据拆分后的各个部分
  • 副本(replica):每个分片的复制

 

 

 

字段类型

  • type:字段类型,是枚举:FieldType,可以是text、long、short、date、integer、object等
    • text:存储数据时候,会自动分词,并生成索引;
    • keyword:存储数据时候,不会分词建立索引;
    • Numerical:数值类型,分两类
      • 基本数据类型:long、interger、short、byte、double、float、half_float
      • 浮点数的高精度类型:scaled_float
        • 需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
    • Date:日期类型;
      • elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。
  • index:是否索引,布尔类型,默认是true
  • store:是否存储,布尔类型,默认是false
  • analyzer:分词器名称,这里的ik_max_word即使用ik分词器

 

 

注意事项

1、Elasticsearch本身就是分布式的,因此即便你只有一个节点,Elasticsearch默认也会对你的数据进行分片和副本操作;当你向集群添加新数据时,数据也会在新加入的节点中进行平衡;

2、ES创建索引时默认创建5个分片一个备份 , 分片的数量只能在创建索引时指定,备份可以动态修改;

3、索引命名规范:字母小写,且不含中划线。

 

ES windows 版 安装部署

下载地址: https://www.elastic.co/cn/downloads/elasticsearch

文件名: elasticsearch-7.4.0-windows-x86_64.zip

 

 

Es 安装包内本身内置了JDK,且内置的JDK版本是该Es所推荐使用的版本。

1、将文件 elasticsearch-7.4.0-windows-x86_64.zip 解压到安装目录下;

2、配置es使用内置的jdk(而非电脑所配置的JAVA_HOME环境变量)

使用文本编辑器编辑 bin/elasticsearch-env.bat 直接添加了JAVA_HOME 的配置,具体如下:

 

 

 

 

rem 配置自己的jdk

set JAVA_HOME=E:/xxx/elastic/elasticsearch-7.4.0/jdk

 

3、双击打开解压后 bin 目录下的 elasticsearch.bat

 

 

 

浏览器打开地址: http://127.0.0.1:9200/ 看到如下界面说明启动好了

 

 

 

 

ElasticSearch-head 安装部署

 

ElasticSearch-head 是一个用来查看ES的运行状态和数据的可视化工具,ElasticSearch-head依赖 node.js

 

1、node.js 安装

下载地址: http://nodejs.cn/download/

文件名: node-v12.13.0-x64.msi

 

双击打开即可进入安装环节,安装完成后,打开cmd 命令,输入 node --version 命令即可查看安装的版本。

 

node.js 安装完成后,切换到 node.js 的安装目录(cmd中输入 npm config ls 命令可查看安装目录),运行命令安装 grunt,命令如下: npm install -g grunt-cli

 

2、ElasticSearch-head 安装

下载地址: https://github.com/mobz/elasticsearch-head

文件名: elasticsearch-head-master.zip

 

下载完成后,cmd 进入 elasticsearch-head-master 的解压目录,然后执行 npm install 命令(对改目录下的相关文件解压并安装)。安装完成后,输入启动命令 npm run start

浏览器地址输入: http://127.0.0.1:9100/ 可查看 head 界面

 

Es默认不允许跨域链接,所以默认会显示未连接的状态。

在es > config 目录下,打开 elasticsearch.yml 文件,并在末尾处添加配置:

http.cors.enabled: true

http.cors.allow-origin: "*"

 

保存,重启es,且刷新elasticsearch-head 即可链接。

 

 

IK分词器安装

 

下载地址: https://github.com/medcl/elasticsearch-analysis-ik

文件名: elasticsearch-analysis-ik-master.zip

 

安装步骤:

1、cmd 进入 elasticsearch-analysis-ik-master 解压目录;

2、执行 mvn clean package 命令进行打包,打包完后会多出一个 target 文件夹。打包zip文件路径为: target > releases > elasticsearch-analysis-ik-7.4.0.zip ;

3、在es安装目录下的 plugins 文件夹下创建 analysis-ik 文件夹,并将上一步打包好的 elasticsearch-analysis-ik-7.4.0.zip 解压到此处,如下图:

 

4、重启 ES,可以启动,则说明安装成功。

 

使用Postman创建索引

 

创建一个空的索引 testindex (非结构化索引)

http://127.0.0.1:9200/testindex

 

通过 elasticsearch-head 可以看到该索引的mappings 是空的(非结构化索引)

对索引的mappings进行赋值,并转换为结构化索引

mapping类似于数据库表的结构,主要用于

1.定义index下的字段名;

2.定义字段类型;

3.定义倒排索引相关的配置。

 

http://127.0.0.1:9200/testindex/_mapping

 

{"properties":{"name":{"type":"text"},"sex":{"type":"integer"}}}

 

通过 elasticsearch-head 可以看到该索引的mappings

 

 

SpringBoot 集成实战

 

基于SpringBoot实现新增/修改、删除、查询、聚合统计的功能

1、pom.xml 添加依赖

<!-- elasticsearch 依赖包 -->
<dependency>
    <groupId>org.elasticsearch</groupId>
  	<artifactId>elasticsearch</artifactId>
    <version>7.2.0</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
   <version>7.2.0</version>
</dependency>

2、application.properties 配置ES地址

elasticsearch.ip=127.0.0.1:9200

3、创建 ElasticsearchRestClient.java

@Configuration
public class ElasticsearchRestClient {
    private static final int ADDRESS_LENGTH = 2;
    private static final String HTTP_SCHEME = "http";
    
    private static final String ARTICLE_INDEX_NAME = "article_index";
    
    @Value("${elasticsearch.ip}")
    String[] ipAddress;
    
    private RestHighLevelClient highLevelClient;
    
    @Bean
    public RestClientBuilder restClientBuilder() {
        HttpHost[] hosts = Arrays.stream(ipAddress)
                .map(this::makeHttpHost)
                .filter(Objects::nonNull)
                .toArray(HttpHost[]::new);
        
        return RestClient.builder(hosts);
    }

    @Bean(name = "highLevelClient")
    public RestHighLevelClient highLevelClient(@Autowired RestClientBuilder restClientBuilder) {
        highLevelClient = new RestHighLevelClient(restClientBuilder);
        return highLevelClient;
    }

    
    private HttpHost makeHttpHost(String s) {
        assert StringUtils.isNotEmpty(s);
        String[] address = s.split(":");
        if (address.length == ADDRESS_LENGTH) {
            String ip = address[0];
            int port = Integer.parseInt(address[1]);

            return new HttpHost(ip, port, HTTP_SCHEME);
        } else {
            return null;
        }
    }
        
    // 新增 或 修改文档(若ID存在,则修改)
    public void insertOrUpdateArticle(EsArticle article) throws IllegalArgumentException, IllegalAccessException{
        try {
            XContentBuilder builder = XContentFactory.jsonBuilder();
            builder.startObject();
            
            // 遍历实体类
            Class clas = article.getClass();
            Field[] fields = clas.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                Field f = fields[i];
                f.setAccessible(true);
                
                // es字段赋值
                if (f.getType().toString().equals("class java.sql.Timestamp")) { //若字段类型为时间类型,则将其转换为 long 进行存储
                    builder.field(f.getName(), ((Timestamp)f.get(article)).getTime());
                } else {
                    builder.field(f.getName(), f.get(article));
                }
            }
            
            
            builder.endObject();
            
            IndexRequest request = new IndexRequest(ARTICLE_INDEX_NAME).source(builder);
            request.id(article.getId()); // 通过指定 id 来实现:若存在则更新记录,否则插入记录
            highLevelClient.index(request, RequestOptions.DEFAULT);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    
    // 批量新增 或 修改文档(若ID存在,则修改)
    public int insertOrUpdateBatchArticle(List<EsArticle> list) {
        int result = -1;
        try {
            BulkRequest request = new BulkRequest();
            
            for (EsArticle article : list) {
                XContentBuilder builder = XContentFactory.jsonBuilder();
                builder.startObject();
                
                // 遍历实体类
                Class clas = article.getClass();
                Field[] fields = clas.getDeclaredFields();
                for (int i = 0; i < fields.length; i++) {
                    Field f = fields[i];
                    f.setAccessible(true);
                    
                    // es字段赋值
                    if (f.getType().toString().equals("class java.sql.Timestamp")) { //若字段类型为时间类型,则将其转换为 long 进行存储
                        if (null == f.get(article)) {
                            builder.field(f.getName(), 0l);
                        } else {
                            builder.field(f.getName(), ((Timestamp)f.get(article)).getTime());
                        }
                    } else {
                        builder.field(f.getName(), f.get(article));
                    }
                }
                
                
                builder.endObject();
                
                IndexRequest requestItem = new IndexRequest(ARTICLE_INDEX_NAME).source(builder);
                requestItem.id(article.getId()); // 通过指定 id 来实现:若存在则更新记录,否则插入记录

                request.add(requestItem);
            }
            
            highLevelClient.bulk(request, RequestOptions.DEFAULT);
            
            result = list.size();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return result;
    }
    
    
    // 批量删除
    public <T> void deleteBatchArticle(Collection<T> idList) {
        BulkRequest request = new BulkRequest();
        idList.forEach(item -> request.add(new DeleteRequest(ARTICLE_INDEX_NAME, item.toString())));
        try {
            highLevelClient.bulk(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    
    /**
     * 通过查询条件实现分页查询
     * @param queryParam    查询条件(Map<String, Object>)
     * @param pageIndex     分页索引,从第  1 页开始
     * @param pageSize      每页条数
     * @param order         排序数组,[排序字段][排序方式(asc | desc)]
     */
    public void queryArticle(Map<String, Object> queryParam, int pageIndex, int pageSize, String[][] order) {
        List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
        SearchRequest searchRequest = new SearchRequest(ARTICLE_INDEX_NAME);
        
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        queryBuilder(pageIndex, pageSize, queryParam, ARTICLE_INDEX_NAME, order, searchRequest, searchSourceBuilder);
        
        
        try {
            SearchResponse response = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            
            for (SearchHit hit : response.getHits().getHits()) {
                Map<String, Object> map = hit.getSourceAsMap();
                map.put("id", hit.getId());
                result.add(map);
                
                
                // 取高亮结果
                if (null != queryParam.get("content")) { // 如果查询条件中有内容关键词,则使用内容关键词处理高亮
                    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                    HighlightField highlight = highlightFields.get("content");
                    Text[] fragments = highlight.fragments(); // 多值的字段会有多个值
                    String fragmentString = fragments[0].string();
                    
                    System.out.println("高亮:" + fragmentString);
                }
            }
            
            System.out.println("pageIndex:" + pageIndex);
            System.out.println("pageSize:" + pageSize);
            System.out.println(response.getHits().getTotalHits());
            System.out.println(result.size());
            
            for (Map<String, Object> map : result) {
                System.out.println(map.get("title"));
                System.out.println(map.get("content"));
                System.out.println(map.get("id"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    /**
     * 构建查询条件
     * @param pageIndex         分页索引,从第  1 页开始
     * @param pageSize          每页条数
     * @param query             查询条件集合(Map<String, Object>)
     * @param indexName         索引名称
     * @param order             排序数组,[排序字段][排序方式(asc | desc)]
     * @param searchRequest
     * @param searchSourceBuilder 
     */
    private void queryBuilder(Integer pageIndex, Integer pageSize, Map<String, Object> query, String indexName, String[][] order, SearchRequest searchRequest, SearchSourceBuilder searchSourceBuilder) {
        if (query != null && !query.keySet().isEmpty()) {
            if (pageIndex != null && pageSize != null) {
                searchSourceBuilder.size(pageSize);
                if (pageIndex <= 0) {
                    pageIndex = 0;
                }
                searchSourceBuilder.from((pageIndex - 1) * pageSize);
            }
            
            //1.创建QueryBuilder(即设置查询条件)这儿创建的是组合查询(也叫多条件查询),后面会介绍更多的查询方法
            // 组合查询BoolQueryBuilder -> must(QueryBuilders):AND ; mustNot(QueryBuilders):NOT; should(QueryBuilders):OR
            BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
            
            // 构建查询条件
            query.keySet().forEach(
                    key -> {
                        boolBuilder.must(QueryBuilders.matchQuery(key, query.get(key)));// builder下有must、should以及mustNot 相当于sql中的and、or以及not
                    });
            
            searchSourceBuilder.query(boolBuilder);

            HighlightBuilder highlightBuilder = new HighlightBuilder();
            HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("content").preTags("<strong>").postTags("</strong>");
            highlightTitle.highlighterType("unified");
            highlightBuilder.field(highlightTitle);
            searchSourceBuilder.highlighter(highlightBuilder);
            
        }
        searchRequest.source(searchSourceBuilder);
        
        
        // 排序
        if (null != order && order.length > 0) {
            for (int i = 0; i < order.length; i++) {
                searchSourceBuilder.sort(new FieldSortBuilder(order[i][0]).order(SortOrder.fromString(order[i][1])));
            }
        }
    }
    
     
    /**
     * 通过条件实现聚合查询(统计)  
     * @param queryParam    查询条件(Map<String, Object>)
     * @param pageIndex     分页索引,从第  1 页开始
     * @param pageSize      每页条数
     * @param order         排序数组,[排序字段][排序方式(asc | desc)]
     * @param groupName     聚合查询别名
     * @param groupField    聚合字段
     * @return 
     */
    public LinkedHashMap<String, Long> countArticle(Map<String, Object> queryParam, int pageIndex, int pageSize, String[][] order, String groupName, String groupField) {
        SearchRequest searchRequest = new SearchRequest(ARTICLE_INDEX_NAME);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        
        // 构建聚合条件
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms(groupName).field(groupField);
        sourceBuilder.aggregation(termsAggregationBuilder);
        
        
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        
        // 构建查询条件
        queryBuilder(pageIndex, pageSize, queryParam, ARTICLE_INDEX_NAME, order, searchRequest, sourceBuilder);
        
        // 若不需要上述“构建查询条件”,直接使用下文即可
        // searchRequest.source(sourceBuilder);
        
        try {
            SearchResponse searchResponse = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            Aggregations aggregations = searchResponse.getAggregations();
            Map<String, Aggregation> stringAggregationMap = aggregations.asMap();
            ParsedStringTerms parsedLongTerms = (ParsedStringTerms) stringAggregationMap.get(groupName);
            List<? extends Terms.Bucket> buckets = parsedLongTerms.getBuckets();
            
            // 使用LinkedHashMap 确保结果有序输出
            LinkedHashMap<String, Long> map = new LinkedHashMap<String, Long>();
            
            for (Terms.Bucket bucket : buckets) {
                long docCount = bucket.getDocCount();//个数
                String v = bucket.getKeyAsString(); // 

                map.put(v, docCount);
            }
            
            return map;
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        return null;
    }
    
    
    
    /**
     * 通过条件实现多字段聚合查询(统计)  
     * @param queryParam    查询条件(Map<String, Object>)
     * @param pageIndex     分页索引,从第  1 页开始
     * @param pageSize      每页条数
     * @param order         排序数组,[排序字段][排序方式(asc | desc)](暂不支持)
     * @param groupField1   聚合字段1
     * @param groupField2   聚合字段2
     * @return 
     */
    public List<HashMap<String, Object>> countArticleMultiple(Map<String, Object> queryParam, int pageIndex, int pageSize, String[][] order, String groupField1, String groupField2) {
        //搜索结果状态信息
        List<HashMap<String, Object>> result = new ArrayList<HashMap<String, Object>>();
        
        try {
            // 1、创建search请求
            //SearchRequest searchRequest = new SearchRequest();
            SearchRequest searchRequest = new SearchRequest(ARTICLE_INDEX_NAME);
            
            // 2、用SearchSourceBuilder来构造查询请求体 ,请仔细查看它的方法,构造各种查询的方法都在这。
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.size(0);
            
            
            //加入聚合
            //字段值项分组聚合
            TermsAggregationBuilder aggregation = AggregationBuilders.terms("by_mutiple_" + groupField1 + "_" + groupField2)
                    .script(new Script("doc['" + groupField1 + "'] +'#'+doc['" + groupField2 + "']")) //(分组如果在字符串字段上,需要建立字段对应的.keyword字段(如:media_type.keyword),该字段支持聚合处理,直接用字符串字段会报错。)
                    //.field("fngroup.keyword")
//                    .size(Integer.MAX_VALUE)
                    .size(500) // 开发环境,避免出现性能问题,设置返回buckets数量相对小一些;一次性返回buckets数量大于10000,需设置search.max_buckets参数,否则会报错。
                    .order(BucketOrder.aggregation("count", true));
            //计算每组的平均balance指标
            aggregation.subAggregation(AggregationBuilders.count("count").field("sessionid"));
            sourceBuilder.aggregation(aggregation);
            
            
            // 构建查询条件
            queryBuilder(pageIndex, pageSize, queryParam, ARTICLE_INDEX_NAME, order, searchRequest, sourceBuilder);
            
            
            //3、发送请求
            SearchResponse searchResponse = highLevelClient.search(searchRequest,RequestOptions.DEFAULT);
            
            int row = 0;
            
            //4、处理响应
            if(RestStatus.OK.equals(searchResponse.status())) {
                // 获取聚合结果
                Aggregations aggregations = searchResponse.getAggregations();
                Terms byAgeAggregation = aggregations.get("by_mutiple_" + groupField1 + "_" + groupField2);
                for(Terms.Bucket buck : byAgeAggregation.getBuckets()) {
                    HashMap<String, Object> map=new HashMap<String, Object>();
                    String[] arr= buck.getKeyAsString().split("#");
                    map.put(groupField1, arr[0].replace("[","").replace("]",""));
                    map.put(groupField2, arr[1].replace("[","").replace("]",""));
                    map.put("count", buck.getDocCount()); // 获取统计数
                    
                    result.add(map);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        return result;
    }

}

4、调用测试

    // 新增记录
    public void create() {
        EsArticle article = new EsArticle();
        article.setTid("tidv");
        article.setTitle("标题");
                
        try {
            client.insertOrUpdateArticle(article);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
        
        System.out.println("success ..");
    }
    
    // 查询
    public void query() {
        Map<String, Object> queryParam = new HashMap<String, Object>();
        queryParam.put("content", "深圳创业板");
        
        client.queryArticle(queryParam, 1, 5, null);
        System.out.println("success..");
    }
    
    // 分组统计
    public void count() {
        Map<String, Object> queryParam = new HashMap<String, Object>();
        
        String[][] order = {{"tid", "desc"}};
        LinkedHashMap<String, Long> resultMap = client.countArticle(queryParam, 1, 5, order, "by_tid", "tid");
        int row = 1;
        for (Entry<String, Long> entry : resultMap.entrySet()) {
            System.out.println(row++ + " > " + entry.getKey() + ":" + entry.getValue());
        }
        
        System.out.println("success..");
    }
    
    // 多字段分组统计
    public void countArticleMultiple() {
        String[][] order = {{"media_type", "desc"}};
        Map<String, Object> queryParam = new HashMap<String, Object>();
        queryParam.put("content", "深圳创业板");
        
        client.countArticleMultiple(queryParam, 1, 5, order, "tid", "media_type");

        System.out.println("success..");
    }

至此,就完成了从安装部署到集成常用功能的实战工作。

 

 

参考文献:

https://blog.csdn.net/u014082714/article/details/89963046

https://blog.csdn.net/chen_2890/article/details/83895646

https://blog.csdn.net/weixin_37703281/article/details/90974279

https://www.jianshu.com/p/1fbfde2aefa5

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章