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

 

 

 

 

 

 

 

 

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