1. 程式人生 > 實用技巧 >ELASTICSEARCH使用入門和整合springboot

ELASTICSEARCH使用入門和整合springboot

ELASTICSEARCH使用入門

  1. 安裝

    • 使用的是docker進行的安裝,安裝步驟見docker筆記總結

    • 安裝完成後訪問對應ip的9200埠既可檢視到docker的各種版本資訊

    {
      "name" : "48f14487625a",
      "cluster_name" : "elasticsearch",
      "cluster_uuid" : "TKkifGUjSNSYeOvtE_PZ1Q",
      "version" : {
        "number" : "7.6.2",//版本號
        "build_flavor" : "default",
        "build_type" : "docker",
        "build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f",
        "build_date" : "2020-03-26T06:34:37.794943Z",
        "build_snapshot" : false,
        "lucene_version" : "8.4.0",
        "minimum_wire_compatibility_version" : "6.8.0",
        "minimum_index_compatibility_version" : "6.0.0-beta1"
      },
      "tagline" : "You Know, for Search"
    }
    

    通過訪問kibana 5601埠可對ELASTICSEARCH進行視覺化介面。

  2. 基本概念

    • 關係型資料庫和es對比(一列的差不多是一個東西)

      Relational DB ‐> Databases ‐> Tables ‐> Rows ‐> Columns

      Elasticsearch ‐> Indices ‐> Types ‐> Documents ‐> Fields

  3. 基本操作

    這些命令都可以在kibana 5601的:

    有二個快捷鍵:

    ctrl+home回到頭

    ctrl+end回到尾部

    • _cat檢視命令

      • GET /_cat/nodes:檢視所有節點
      • GET/cat/health: 檢視es健康狀況
      • GET /cat/master:檢視主節點
      • GET /_cat/indicies:檢視所有索引 ;
    • 文件增刪改操作

      • PUT customer/external/1建立文件

        {
        "name":"John Doe"
        }

        put和post請求的可以新增文件,有一點差異

        POST新增。如果不指定id,會自動生成id。指定id就會修改這個資料,並新增版本號;

        PUT可以新增也可以修改。PUT必須指定id;

        PUT請求更多的用來做修改操作

        返回資料:

        {
            "_index": "customer",
            "_type": "external",
            "_id": "1",
            "_version": 1,
            "result": "created",
            "_shards": {
                "total": 2,
                "successful": 1,
                "failed": 0
            },
            "_seq_no": 0,
            "_primary_term": 1
        }
        

        下劃線欄位代表了元資料:

        "_index": "customer" 表明該資料在哪個資料庫下;

        "_type": "external" 表明該資料在哪個型別下;

        "_id": "1" 表明被儲存資料的id;

        "_version": 1, 被儲存資料的版本

        "result": "created" 這裡是建立了一條資料,如果重新put一條資料,則該狀態會變為updated,並且版本號也會發生變化。

      • GET /customer/external/1檢視文件

        返回欄位及其作用

        {
            "_index": "customer",//在哪個索引
            "_type": "external",//在哪個型別
            "_id": "1",//記錄id
            "_version": 3,//版本號
            "_seq_no": 6,//併發控制欄位,每次更新都會+1,用來做樂觀鎖
            "_primary_term": 1,//同上,主分片重新分配,如重啟,就會變化
            "found": true,
            "_source": {
                "name": "John Doe"
            }
        }
        

        通過“if_seq_no=1&if_primary_term=1 ”,當序列號匹配的時候,才進行修改,否則不修改。可以實現樂觀鎖。保證只能被一個使用者搶奪過去

      • 帶_update的更新,只能傳送post請求

        POST /customer/external/1/_update

        {

        ​ "doc":{

        ​ "name":"jon

        ​ }

        }

        返回結果

        {
          "_index" : "customer",
          "_type" : "external",
          "_id" : "1",
          "_version" : 3,
          "result" : "noop",//如果沒有發生變化這個欄位為noop,發生了變化為updated
          "_shards" : {
            "total" : 0,
            "successful" : 0,
            "failed" : 0
          },
          "_seq_no" : 2,
          "_primary_term" : 1
        }
        
        

        帶不帶_update進行更新的場景
        對於大併發更新,不帶 _update;
        對於大併發查詢偶爾更新,帶 _update;對比更新,重新計算分配規則。保證資料的一致性吧。

      • 刪除文件或者索引

        不能後刪除型別

        DELETE customer/external/1

        返回資料

        {
          "_index" : "customer",
          "_type" : "external",
          "_id" : "1",
          "_version" : 5,
          "result" : "deleted",
          "_shards" : {
            "total" : 2,
            "successful" : 1,
            "failed" : 0
          },
          "_seq_no" : 4,
          "_primary_term" : 1
        }
        
        

        DELETE customer

        返回資料

        {
          "acknowledged" : true
        }
        
      • 批量操作

        語法格式

        {action:{metadata}}\n
        {request body  }\n   如果沒有請求體可以不寫
        

        例子:

        POST /_bulk
        {"delete":{"_index":"website","_type":"blog","_id":"123"}}//沒有請求體
        {"create":{"_index":"website","_type":"blog","_id":"123"}}
        {"title":"my first blog post"}
        {"index":{"_index":"website","_type":"blog"}}//插入操作
        {"title":"my second blog post"}
        {"update":{"_index":"website","_type":"blog","_id":"123"}}
        {"doc":{"title":"my updated blog post"}}
        
  4. 文件查詢操作

    https://www.elastic.co/guide/en/elasticsearch/reference/7.6/index.html在這個連結中檢視所有的操作命令

    • 檢索的二種方式

      • 通過REST request uri 傳送搜尋引數 (uri +檢索引數)
      • 通過REST request body 來發送它們(uri+請求體);
      • 響應結果解釋:
        took - Elasticsearch執行搜尋的時間(毫秒)
        time_ out- 告訴我們搜尋是否超時
        shards- 告訴我們多少個分片被搜尋了,以及統計了成功/失敗的搜尋分片
        hits-搜尋結果
        hits.total-搜尋結果
        hits.hits-實際的搜尋結果陣列(預設為前10的文件)
        sort -
        結果的排序key (鍵) (沒有則按 score
        排序)
        score
        和max
        _score -相關性得分和最高得分(全文檢索用)
    • uri+請求體進行檢索(使用fromsize可以進行分頁查詢)

      GET /bank/_search  
      {
        "query": { "match_all": {} },
        "sort": [
          { "account_number": "asc" },
          {"balance":"desc"}
        ],
        "from": 20,
        "size": 10
      }
      

      HTTP客戶端工具(),get請求不能夠攜帶請求體

    • uri +檢索引數

      GET bank/_search?q=*&sort=account_number:asc

  5. Query DSL

    Elasticsearch提供了一個可以執行查詢的Json風格的DSL。這個被稱為Query DSL,該查詢語言非常全面。

    • 常用結構

      • QUERY_NAME:{
        ARGUMENT:VALUE,
        ARGUMENT:VALUE,...
        }

        例如:"query": { "match_all": {} },

      • QUERY_NAME:{
        FIELD_NAME:{
        ARGUMENT:VALUE,
        ARGUMENT:VALUE,...
        }
        }

        例如:"sort": balance: { "order": "asc" }

      • QUERY_NAME:VALUE
        例如:"from": 0

    • match進行全文檢索

      查詢字串是模糊查詢(全文檢索,最終會按照評分進行排序,會對檢索條件進行分詞匹配。)

      查詢數字是精確查詢

      GET bank/_search
      {
      "query": {
      "match": {
      "account_number": "20"
      }
      }
      }

    • match_phrase [短句匹配]

      • 將需要匹配的當成一整個單詞進行檢索(不分詞)

      GET bank/_search
      {
      "query": {
      "match_phrase": {
      "address": "mill road"
      }
      }
      }

      • match的另一種查詢方式

      GET bank/_search
      {
      "query": {
      "match": {
      "address.keyword": "990 Mill Road"
      }
      }
      }

      使用了keyword後就是對這個欄位進行精確查詢只有完全等於所要查的欄位名時才能進行匹配。

    • multi_math【多欄位匹配】

      同時對state和address欄位進行查詢

      GET bank/_search
      {
      "query": {
      "multi_match": {
      "query": "mill",
      "fields": [
      "state",
      "address"
      ]
      }
      }
      }

    • bool用來做複合查詢(同時使用must,must_not,should關鍵字)

      GET bank/_search
      {
      "query": {
      "bool": {
      "must": [
      {
      "match": {
      "gender": "M"
      }
      },
      {
      "match": {
      "address": "mill"
      }
      }
      ],
      "must_not": [
      {
      "match": {
      "age": "38"
      }
      }
      ]
      }
      }

      地址必須包含mill,性別必須包含M,年齡必須不是38(數字型別是精確查詢),should並不會改變查詢結果,只會更改相關性得分,

    • Filter【結果過濾】

      不會影響得分,只會過濾結果

      GET bank/_search
      {
      "query": {
      "bool": {
      "must": [
      {
      "match": {
      "address": "mill"
      }
      }
      ],
      "filter": {
      "range": {
      "balance": {
      "gte": "10000",
      "lte": "20000"
      }
      }
      }
      }
      }
      }

      查詢addrees中含有mill的並且工資大於10000小於20000的

    • term使用

      自己在使用es的時候查詢沒有遇到過得關鍵字的方法:

 在查詢結果裡面找到

 這個點進去檢視

 文字欄位不要使用term進行檢索,非文字欄位使用term進行檢索

 GET bank/_search
 {
   "query": {
     "term": {
       "address": "mill Road"
     }
   }
 }

 這樣查是檢索不到任何資訊的,

 GET bank/_search
 {
   "query": {
     "term": {
       "age": "38"
     }
   }
 }
  • Aggregation(執行聚合)

    相當於sql中的groupby和sql中的聚合函式

    使用語法:

    "aggs":{
    "aggs_name這次聚合的名字,方便展示在結果集中":{
    "AGG_TYPE聚合的型別(avg,term,terms)":{}
    }
    },

    例如:

    "aggs": {
    "ageAgg": {
    "terms": {
    "field": "age",
    "size": 10
    }
    },
    "ageAvg": {
    "avg": {
    "field": "age"
    }
    },
    "balanceAvg": {
    "avg": {
    "field": "balance"
    }
    }
    },

    terms,avg都是聚合的方式,terms是按那個欄位進行聚合,avg是求均值,這些都是平行的聚合方式。聚合也可以進行巢狀,就是在ageAgg的基礎上在進行聚合。

    "aggs": {
    "ageAgg": {
    "terms": {
    "field": "age",
    "size": 100
    },
    "aggs": {
    "genderAgg": {
    "terms": {
    "field": "gender.keyword"
    },
    "aggs": {
    "balanceAvg": {
    "avg": {
    "field": "balance"
    }
    }
    }
    },
    "ageBalanceAvg": {
    "avg": {
    "field": "balance"
    }
    }
    }
    }
    },

    文字欄位進行聚合要加上keyword,上面聚合的意思是按照年齡先進行分類,然後在此基礎上按照男女進行聚合後,在算出男女的平均工資。ageBalanceAvg是按照年齡進行的平均值

  1. Mapping對映

    Maping是用來定義一個文件(document),以及它所包含的屬性(field)是如何儲存和索引的。第一次存檔的時候會自動進行確定文件欄位的型別

    可以包含的型別有:

    核心型別
    字串( string )
    text, keyword
    數字型別( Numenic》
    long, integer, short, byte, double, floathalf floatscaled_ float
    日期型別(Date )
    date
    布林美型( Boolean )
    boolean
    二進位制型別(binary )
    binary

    複合型別
    陣列美型(Aray )
    Array支援不針對特定的美型
    物件美型(Object )
    object用於單JSON物件
    巢狀型別(Nested )
    nested用於JSON物件陣列

    地理型別( Geo )
    地理座標( Geo-points )
    geo. ,point用於悔述經緯度座標
    地理陽形(Geo-snape )
    geo. shape用於強述複雜形狀,如多邊形..

    ElasticSearch7-去掉type概念所以我們在查詢的時候只使用了索引,沒有使用型別

    • GET bank/_mapping

      檢視索引mapping

    • PUT /my_index

      建立索引,並設定filed的型別

      PUT /my_index
      {
      "mappings": {
      "properties": {
      "age": {
      "type": "integer"
      },
      "email": {
      "type": "keyword"//是keyword就會做精確匹配,不會做全文檢索
      },
      "name": {
      "type": "text",

      ​ "index": false

      ​ }
      ​ }
      }
      }

      index:false表明欄位不能被檢索,

      "gender" : {
      "type" : "text",
      "fields" : {
      "keyword" : {
      "type" : "keyword",
      "ignore_above" : 256
      }
      }

      欄位下還可以有keyword對映,在查詢的時候可以使用欄位keyword

      新增新的欄位對映

      PUT /my_index/_mapping
      {
      "properties": {
      "employee-id": {
      "type": "keyword",
      "index": false
      }
      }
      }

      更新對映

      對於已經存在的欄位對映,我們不能更新。更新必須建立新的索引,進行資料遷移。

      資料遷移:

      POST _reindex
      {
      "source":{
      "index":"bank",
      "type":"account"//老的資料才有type
      },
      "dest":{
      "index":"new_bank"
      }
      }

  2. 分詞

    • 自帶的分詞器大部分都是分英文的

      POST _analyze
      {
      "analyzer": "standard",
      "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
      }

    • 中文分詞器使用的是ik分詞器

      https://github.com/medcl/elasticsearch-analysis-ik/releases/download 對應es版本安裝

      我們已經將elasticsearch容器的“/usr/share/elasticsearch/plugins”目錄,對映到宿主機的“ /mydata/elasticsearch/plugins”目錄下,所以比較方便的做法就是下載“/elasticsearch-analysis-ik-7.6.2.zip”檔案,然後解壓到該資料夾下即可。安裝完畢後,需要重啟elasticsearch容器。

    • 測試

      GET my_index/_analyze
      {
      "analyzer": "ik_max_word",
      "text":"我是中國人"
      }

      因為有些詞彙沒有,所以需要自己定義詞庫

      修改/usr/share/elasticsearch/plugins/ik/config中的IKAnalyzer.cfg.xml更改這個檔案

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
      <properties>
      	<comment>IK Analyzer 擴充套件配置</comment>
      	<!--使用者可以在這裡配置自己的擴充套件字典 -->
      	<entry key="ext_dict"></entry>
      	 <!--使用者可以在這裡配置自己的擴充套件停止詞字典-->
      	<entry key="ext_stopwords"></entry>
      	<!--使用者可以在這裡配置遠端擴充套件字典 -->
      	<entry key="remote_ext_dict">http://#/es/fenci.txt</entry>//更改這裡 
      	<!--使用者可以在這裡配置遠端擴充套件停止詞字典-->
      	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
      </properties>
      

      修改完成後,需要重啟elasticsearch容器,否則修改不生效。

  3. elasticsearch-Rest-Client(springboot整合elasticsearch)

    • 9300: TCP

      spring-data-elasticsearch:transport-api.jar;

      • springboot版本不同,ransport-api.jar不同,不能適配es版本
      • 7.x已經不建議使用,8以後就要廢棄
    • 9200: HTTP

    • 匯入依賴

      <dependency>
          <groupId>org.elasticsearch.client</groupId>
          <artifactId>elasticsearch-rest-high-level-client</artifactId>
          <version>7.6.2</version>
      </dependency>
      

      在spring-boot-dependencies中所依賴的ELK版本位6.8.7

      springboot中匯入了版本需要改一下:

      ​ <elasticsearch.version>6.8.7</elasticsearch.version>

      更改:

       <properties>
              ...
              <elasticsearch.version>7.6.2</elasticsearch.version>
        </properties>
      

      怎麼樣通過java呼叫儲存資料:
      https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-document-index.html參考

    • 編寫配置檔案

        public static final RequestOptions COMMON_OPTIONS;
          static {
              RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
      //        builder.addHeader("Authorization", "Bearer " + TOKEN);
      //        builder.setHttpAsyncResponseConsumerFactory(
      //                new HttpAsyncResponseConsumerFactory
      //                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
              COMMON_OPTIONS = builder.build();
          }
      
          @Bean
          public RestHighLevelClient esRestClient(){
              RestClientBuilder builder = RestClient.builder(new HttpHost("192.168.0.111", 9200));
              RestHighLevelClient client = new RestHighLevelClient(builder);
      //        RestHighLevelClient client = new RestHighLevelClient(
      //                RestClient.builder(
      //                        new HttpHost("localhost", 9200, "http")));
              return client;
          }
      
    • 寫資料

       @Test
          public void indexData() throws IOException {
              IndexRequest indexRequest = new IndexRequest ("users");
      
              User user = new User();
              user.setUserName("張三");
              user.setAge(20);
              user.setGender("男");
              String jsonString = JSON.toJSONString(user);
              //設定要儲存的內容
              indexRequest.source(jsonString, XContentType.JSON);
              //執行建立索引和儲存資料
              IndexResponse index = client.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
      
              System.out.println(index);
      
          }
      
    • 獲取資料

       public void searchData() throws IOException {
              GetRequest getRequest = new GetRequest(
                      "users",
                      "_-2vAHIB0nzmLJLkxKWk");
      
              GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
              System.out.println(getResponse);
              String index = getResponse.getIndex();
              System.out.println(index);
              String id = getResponse.getId();
              System.out.println(id);
              if (getResponse.isExists()) {
                  long version = getResponse.getVersion();
                  System.out.println(version);
                  String sourceAsString = getResponse.getSourceAsString();
                  System.out.println(sourceAsString);
                  Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
                  System.out.println(sourceAsMap);
                  byte[] sourceAsBytes = getResponse.getSourceAsBytes();
              } else {
      
              }
          }
      
    • 複雜查詢

        //1. 建立檢索請求
              SearchRequest searchRequest = new SearchRequest();
      
              //1.1)指定索引
              searchRequest.indices("bank");
              //1.2)構造檢索條件
              SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
              //有前面各種各樣的查詢條件from sort size啊
              sourceBuilder.query(QueryBuilders.matchQuery("address","Mill"));
      
              //1.2.1)按照年齡分佈進行聚合
              TermsAggregationBuilder ageAgg=AggregationBuilders.terms("ageAgg").field("age").size(10);
              sourceBuilder.aggregation(ageAgg);
      
              //1.2.2)計算平均年齡
              AvgAggregationBuilder ageAvg = AggregationBuilders.avg("ageAvg").field("age");
              sourceBuilder.aggregation(ageAvg);
              //1.2.3)計算平均薪資
              AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
              sourceBuilder.aggregation(balanceAvg);
      
              System.out.println("檢索條件:"+sourceBuilder);
              searchRequest.source(sourceBuilder);
              //2. 執行檢索
              SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
              System.out.println("檢索結果:"+searchResponse);
      
              //3. 將檢索結果封裝為Bean
              SearchHits hits = searchResponse.getHits();
              SearchHit[] searchHits = hits.getHits();
              for (SearchHit searchHit : searchHits) {
                  String sourceAsString = searchHit.getSourceAsString();
                  Account account = JSON.parseObject(sourceAsString, Account.class);
                  System.out.println(account);
      
              }
      
              //4. 獲取聚合資訊
              Aggregations aggregations = searchResponse.getAggregations();
      
              Terms ageAgg1 = aggregations.get("ageAgg");
      
              for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
                  String keyAsString = bucket.getKeyAsString();
                  System.out.println("年齡:"+keyAsString+" ==> "+bucket.getDocCount());
              }
              Avg ageAvg1 = aggregations.get("ageAvg");
              System.out.println("平均年齡:"+ageAvg1.getValue());
      
              Avg balanceAvg1 = aggregations.get("balanceAvg");
              System.out.println("平均薪資:"+balanceAvg1.getValue());
      
          }