主頁 > 後端開發 > Spring Boot 教程 - Elasticsearch

Spring Boot 教程 - Elasticsearch

2020-10-11 17:20:37 後端開發

1. Elasticsearch簡介

Elasticsearch是一個基于Lucene的搜索服務器,它提供了一個分布式多用戶能力的全文搜索引擎,基于RESTful web介面,Elasticsearch是用Java語言開發的,并作為Apache許可條款下的開放原始碼發布,是一種流行的企業級搜索引擎,Elasticsearch用于云計算中,能夠達到實時搜索,穩定,可靠,快速,安裝使用方便,官方客戶端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和許多其他語言中都是可用的,根據DB-Engines的排名顯示,Elasticsearch是最受歡迎的企業搜索引擎,其次是Apache Solr,也是基于Lucene,以后再給大家詳細介紹solr,

它能很方便的使大量資料具有搜索、分析和探索的能力,充分利用Elasticsearch的水平伸縮性,能使資料在生產環境變得更有價值,Elasticsearch 的實作原理主要分為以下幾個步驟,首先用戶將資料提交到Elasticsearch 資料庫中,再通過分詞控制器去將對應的陳述句分詞,將其權重和分詞結果一并存入資料,當用戶搜索資料時候,再根據權重將結果排名,打分,再將回傳結果呈現給用戶,

Elasticsearch可以用于搜索各種檔案,它提供可擴展的搜索,具有接近實時的搜索,并支持多租戶,”Elasticsearch是分布式的,這意味著索引可以被分成分片,每個分片可以有0個或多個副本,每個節點托管一個或多個分片,并充當協調器將操作委托給正確的分片,再平衡和路由是自動完成的,“相關資料通常存盤在同一個索引中,該索引由一個或多個主分片和零個或多個復制分片組成,一旦創建了索引,就不能更改主分片的數量,

Elasticsearch使用Lucene,并試圖通過JSON和Java API提供其所有特性,它支持facetting和percolating,如果新檔案與注冊查詢匹配,這對于通知非常有用,另一個特性稱為“網關”,處理索引的長期持久性;例如,在服務器崩潰的情況下,可以從網關恢復索引,Elasticsearch支持實時GET請求,適合作為NoSQL資料存盤,但缺少分布式事務,

2. Elasticsearch深入了解

2.1 Elasticsearch的底層實作

  • 2.1.1 lucene

    Es是一個比較復雜的搜索服務器,本身也是使用Java語言撰寫的,在上面的簡介中,說明了ES是一個基于lucene的搜索服務器,lucene是什么呢?Lucene是apache軟體基金會4 jakarta專案組的一個子專案,是一個開放源代碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文本分析引擎,lucene也是使用Java語言撰寫的,Java天下第一??!

    Lucene是一套用于全文檢索和搜尋的開源程式庫,由Apache軟體基金會支持和提供,Lucene提供了一個簡單卻強大的應用程式介面,能夠做全文索引和搜尋,在Java開發環境里Lucene是一個成熟的免費開源工具,就其本身而言,Lucene是當前以及最近幾年最受歡迎的免費Java資訊檢索程式庫,至于lucene到底是怎么實作的,牛牛們可能要自己去百度或者谷歌一下啦,

  • 2.1.2 Elasticsearch的基本概念

    1. 集群(Cluster):就是多臺ES服務器在一起構成搜索服務器,現在很多應用基本上都有集群的概念,提高性能,讓應用具有高可用性,一臺服務器掛掉,可以很快有另一臺ES服務器補上,

    2. 節點(Node):節點就是集群中的某一臺ES服務器就稱為一個節點,

    3. 索引庫(Index Indices):就是ES服務器上的某一個索引,相當于Mysql資料庫中的資料庫的概念,一個節點可以有很多個索引庫,

    4. 檔案型別(Type):這個概念就相當于Mysql資料庫中表的概念,一個索引庫可以有很多個檔案型別,但是這個概念現在慢慢淡化了,因為在ES中一個索引庫直接存資料檔案就挺好的,這個概念現在來說有點多余了,所以ES官方也在淡化這個概念,在ES8中,這個概念將會徹底的消失,

    5. 檔案(Doc):檔案就相當于Mysql是資料庫中某個表的一條資料記錄,現在ES已經到7.7版本了,我們也就忽略type這個概念,直接在索引庫中存檔案即可,另外需要說一下,我們一般把資料檔案存到Es服務器的某個索引庫的這個動作稱之為索引

      最后還有兩個比較重要的概念,但是可能不是那么直觀的可以感受得到:

      分片(Shards)和副本(Replicas)

      索引可能會存盤大量資料,這些資料可能超過單個節點的硬體限制,例如,十億個檔案的單個索引占用了1TB的磁盤空間,可能不適合單個節點的磁盤,或者可能太慢而無法單獨滿足來自單個節點的搜索請求,

      為了解決此問題,Elasticsearch提供了將索引細分為多個碎片的功能,創建索引時,只需定義所需的分片數量即可,每個分片本身就是一個功能齊全且獨立的“索引”,可以托管在群集中的任何節點上,

      分片很重要,主要有兩個原因:

      • 它允許您水平分割/縮放內容量
      • 它允許您跨碎片(可能在多個節點上)分布和并行化操作,從而提高性能/吞吐量

      分片如何分布以及其檔案如何聚合回到搜索請求中的機制由Elasticsearch完全管理,并且對您作為用戶是透明的,

      在隨時可能發生故障的網路/云環境中,非常有用,強烈建議您使用故障轉移機制,以防碎片/節點因某種原因脫機或消失,為此,Elasticsearch允許您將索引分片的一個或多個副本制作為所謂的副本分片(簡稱副本),

      復制很重要,主要有兩個原因:

      • 如果分片/節點發生故障,它可提供高可用性,因此,重要的是要注意,副本碎片永遠不會與從其復制原始/主要碎片的節點分配在同一節點上,
      • 由于可以在所有副本上并行執行搜索,因此它可以擴展搜索量/吞吐量,

      總而言之,每個索引可以分為多個碎片,索引也可以復制零(表示沒有副本)或多次,復制后,每個索引將具有主碎片(從中進行復制的原始碎片)和副本碎片(主碎片的副本),可以在創建索引時為每個索引定義分片和副本的數量,創建索引后,您可以隨時動態更改副本數,但不能事后更改分片數,

      默認情況下,Elasticsearch中的每個索引分配有5個主碎片和1個副本,這意味著如果集群中至少有兩個節點,則索引將具有5個主碎片和另外5個副本碎片(1個完整副本),總共每個索引10個碎片,

  • 2.1.3 Elasticsearch的索引原理

    Es作為一個全文檢索服務器,那么它在搜索方面肯定很在行啦!那它是怎么做到的呢?

    Es官方有這么一句話:一切設計都是為了提高搜索的性能!

    Es能夠快速的搜索出我們需要的內容,靠的就是倒排索引的思想,或者說是一種設計!

    在沒有使用倒排索引的情況下,正常思路是根據搜索關鍵字去查找相應的內容,但是使用了倒排索引之后,ES會先將檔案的所有內容拆分成多個詞條,創建一個包含所有不重復詞條的排序串列,然后列出每個詞條出現在哪個檔案,

    例如,假設我們有兩個檔案,每個檔案的 content 域包含如下內容:

    ? Doc_1:The quick brown fox jumped over the lazy dog

    ? Doc_2:Quick brown foxes leap over lazy dogs in summer

    ES首先會將這兩個檔案拆分成多個單獨的詞,或者叫做詞條,然后為所有的詞條創建一個排序串列,并記錄每個詞條出現的檔案的資訊,就像下面這樣:

    Term      Doc_1  Doc_2
    -------------------------
    Quick   |       |  X                        /*
    The     |   X   |								Term就是詞條,比如第一個Term就是Quick關鍵字,在Doc_1中不存
    brown   |   X   |  X							在,在Doc_2中存在,其他的以此類推,
    dog     |   X   |							*/
    dogs    |       |  X
    fox     |   X   |
    foxes   |       |  X
    in      |       |  X
    jumped  |   X   |
    lazy    |   X   |  X
    leap    |       |  X
    over    |   X   |  X
    quick   |   X   |
    summer  |       |  X
    the     |   X   |
    ------------------------
    

    現在,如果我們想搜索 quickbrown這兩個關鍵字,我們只需要查找包含每個詞條的檔案,就相當于我們查詢的時候,是通過這個索引表找到檔案,在通過檔案去找檔案內容中的搜索關鍵字,與傳統的通過關鍵字去找內容是不同的,

    倒排索引到底是個怎么實作的,怎么個思想,我在這里就不一一說明了,大家可以看下官方的詳細介紹:倒排索引的原理

    還有es官方的一系列的說明也都可以了解一下:什么是Elasticsearch?

2.2 Elasticsearch的安裝

本演示專案ES版本為7.0.0版本,其他版本的ES的maven依賴與其他的jar包關系請自行查閱官方檔案,保證不沖突,

  • Windows

    Es服務器的安裝很簡單,Windows版本特別的簡單,直接去官網下載,運行 bin/elasticsearch 或者bin\elasticsearch.bat

  • Linux(CentOS7)

    首先我們去官網下載ES的tar.gz包,然后自建一個檔案夾放好,然后解壓tar.zg壓縮包:

    tar -xvf elasticsearch-7.0.0.tar.gz
    

    然后進入到bin目錄下:

    cd elasticsearch-7.0.0/bin
    

    然后運行elasticsearch:

    ./elasticsearch
    

    這個時候肯定會報錯的,因為沒有進行配置,所以我們先對es進行一些簡單的配置,保證能單機運行,進入elasticsearch-7.7.0/config目錄,對es的核心組態檔進行編輯:

    vim elasticsearch.yml
    

    進入到了elasticsearch.yml檔案的編輯頁面:

    首先我們配置集群名稱,集群名稱自己取一個喜歡的名字就好:

    接下來配置節點名稱,就是在這個集群中,這個es服務器的名稱:

    接下來配置一些必要的引數:

    bootstrap.memory_lock: 是否鎖住記憶體,避免交換(swapped)帶來的性能損失,默認值是: false,

    bootstrap.system_call_filter: 是否支持過濾掉系統呼叫,elasticsearch 5.2以后引入的功能,在bootstrap的時候check是否支持seccomp,

    配置network為所有人都可以訪問,因為我們一般是使用ssh連接工具在其他的電腦上操作Linux系統,所以我們需要配置一下:

    到這里就配置完成了,但是當你重新去運行.elasticsearch的可執行檔案的時候,依然會報錯,

    報錯資訊中可能包含以下幾個錯誤:

    • max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]

      原因:無法創建本地檔案問題,用戶最大可創建檔案數太小,

      解決方法:切換到root賬戶下,進入Linux系統檔案夾,編輯limits.conf檔案:

      vim /etc/security/limits.conf
      

      在檔案的末尾加上:

      *                soft    nofile          65536
      *                hard    nofile          65536
      *                soft    nproc           4096
      *                hard    nproc           4096
      
    • max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

      原因:最大虛擬記憶體太小,需要修改系統變數的最大值,

      解決方法:切換到root賬戶下,進入Linux系統檔案夾,編輯sysctl.conf檔案:

      vim /etc/sysctl.conf
      

      在檔案的末尾加上:

      vm.max_map_count=262144
      
    • max number of threads [1024] for user [es] likely too low, increase to at least [2048]

      原因:無法創建本地執行緒問題,用戶最大可創建執行緒數太小,

      解決方法:如果你是CentOS6及以下系統,編輯的檔案是90-nproc.conf這個檔案,如果你和我一樣使用的是CentOS7的話,編輯的檔案是20-nproc.conf檔案,其實這兩個檔案是一樣的,只是在不同CentOS系統中名稱不一樣而已,

      CentOS7使用這個命令:

      vim /etc/security/limits.d/20-nproc.conf
      

      CentOS6使用這個命令:

      vim /etc/security/limits.d/90-nproc.conf
      

      只需要在檔案中加上以下配置:

      *          soft    nproc     4096
      

      這個配置的意思是說賦予其他用戶的可創建本地執行緒數為4096,在這個檔案中本來就有一個配置,意思是說賦予root賬戶創建執行緒數不受限制,我們就把上面的配置加在本來存在的配置的下面一行就可以了,

      如果是CentOS7的使用者,還需要配置另一個檔案,否則這個最大執行緒數是不會生效的,CentOS 7 使用systemd替換了SysV,Systemd目的是要取代Unix時代以來一直在使用的init系統,兼容SysV和LSB的啟動腳本,而且夠在行程啟動程序中更有效地引導加載服務,在/etc/systemd目錄下有一個系統的默認管理配置,這里有登陸、日志、服務、系統等,所以CentOS7的使用者還需要配置下面這個檔案:

      vim /etc/systemd/system.conf
      

      對其中的選項進行配置,在檔案的末尾加上:

      DefaultLimitNOFILE=65536
      DefaultLimitNPROC=4096
      

    上面的所以錯誤解決完畢之后,我們再運行.elasticsearch可執行檔案,es才可以啟動成功,

2.3 Elasticsearch的使用

首先給大家介紹一個谷歌瀏覽器插件,這個插件是用來可視化展示es的索引庫資料的,這個插件叫做ElasticVue,個人感覺挺好用的,展示也比較方便,給大家截個圖看看:

大家可以使用這個建立索引庫,然后呼叫es官方的es專用的語法操作es服務器進行CRUD操作,但是此處我只介紹Java語言如何呼叫es服務器API,廢話不多說,我們直接開始下一步,

  • 2.3.1 引入依賴

    搭建工程的程序我就不演示了,直接上pom.xml依賴檔案,

    pom.xml

    <!--springboot父工程-->
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <dependencies>
            <!--springboot-web組件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.2.2.RELEASE</version>
            </dependency>
            <!--elasticsearch-rest-client組件-->
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-client</artifactId>
                <version>7.7.0</version>
            </dependency>
            <!--elasticsearch-rest-high-level-client組件-->
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-high-level-client</artifactId>
                <version>7.7.0</version>
            </dependency>
            <!--elasticsearch組件-->
            <dependency>
                <groupId>org.elasticsearch</groupId>
                <artifactId>elasticsearch</artifactId>
                <version>7.7.0</version>
            </dependency>
            <!--mybatis整合springboot組件-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.0</version>
            </dependency>
            <!--mysql資料庫連接驅動-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.18</version>
            </dependency>
            <!--lombok組件-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.10</version>
            </dependency>
            <!--json組件gson-->
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>2.8.5</version>
            </dependency>
            <!--springboot-test組件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-test</artifactId>
            </dependency>
            <!--單元測驗junit組件-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <!--spring-test組件-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.2.2.RELEASE</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <!--springboot的maven插件-->
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <compilerArgs>
                            <arg>-parameters</arg>
                        </compilerArgs>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
  • 2.3.2 Elasticsearch的配置類和Gson配置類和應用組態檔

    application.yml

    butterflytri:
      databaseurl-port: 127.0.0.1:3306 # 資料庫埠
      database-name: student_db # 資料庫名
      host: 192.168.129.100:9200 # es服務端
    server:
      port: 8080 # 應用埠
      servlet:
        context-path: /butterflytri # 應用映射
    spring:
      application:
        name: mybatis # 應用名稱
      datasource:
        url: jdbc:mysql://${butterflytri.databaseurl-port}/${butterflytri.database-name}?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
    mybatis:
      type-aliases-package: com.butterflytri.entity # entity別名
      mapper-locations: classpath:com/butterflytri/mapper/*Mapper.xml # mapper映射包掃描
    

    注意:yml檔案中的192.168.129.100:9200是es對外的埠,使用的http協議進行操作,es服務器還有個9300埠,這個埠是es集群中各個節點進行交流的埠,使用的是tcp協議,所以我們連接的時候,埠要使用9200埠,

    專案啟動類沒有什么特別的東西,就不展示了,

    ElasticsearchConfig.java

    package com.butterflytri.config;
    
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.FactoryBean;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author: WJF
     * @date: 2020/5/22
     * @description: ElasticSearchConfig
     */
    @Configuration
    public class ElasticSearchConfig implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
    
        /**
         * {@link FactoryBean<T>}:FactoryBean<T>是spring對外提供的對接介面,當向spring物件使用getBean("..")方法時,
         *                         spring會使用FactoryBean<T>的getObject 方法回傳物件,所以當一個類實作的factoryBean<T>介面時,
         *                         那么每次向spring要這個類時,spring就回傳T物件,
         *
         * {@link InitializingBean}:InitializingBean介面為bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,
         *                          凡是繼承該介面的類,在初始化bean的時候會執行該方法,在spring初始化bean的時候,如果該bean是
         *                          實作了InitializingBean介面,并且同時在組態檔中指定了init-method,系統則是
         *                          先呼叫afterPropertiesSet方法,然后在呼叫init-method中指定的方法,
         *
         * {@link DisposableBean}:DisposableBean介面為bean提供了銷毀方法destroy-method,會在程式關閉前銷毀物件,
         */
    
        @Value("#{'${butterflytri.host}'.split(':')}")
        private String[] host;
    
        private RestHighLevelClient restHighLevelClient;
    
        private RestHighLevelClient restHighLevelClient() {
            restHighLevelClient = new RestHighLevelClient(
    
                    RestClient.builder(new HttpHost(host[0],Integer.valueOf(host[1]),"http"))
    
            );
            return restHighLevelClient;
        }
    
        @Override
        public void destroy() throws Exception {
            restHighLevelClient.close();
        }
    
        @Override
        public RestHighLevelClient getObject() throws Exception {
            return restHighLevelClient;
        }
    
        @Override
        public Class<?> getObjectType() {
            return RestHighLevelClient.class;
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            restHighLevelClient();
        }
    
    }
    

    ES的配置類,這個配置類實作了三個介面,三個介面的作用我也寫上了注釋,大家可以看下,需要注意的是FactoryBean這個介面,一但實作了這個介面,每當你需要使用泛型表示的物件T的時候,Spring不會從容器中去拿這個物件,而是會呼叫這個FactoryBean.getObject()方法去拿物件,其他的就沒有什么了,

    Gson.java

    Gson是一個操作json資料的類,它的執行效率可能會慢一點,但是它在決議json資料的時候不會出Bug,

    package com.butterflytri.config;
    
    import com.google.gson.Gson;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author: WJF
     * @date: 2020/5/22
     * @description: GsonConfig
     */
    @Configuration
    public class GsonConfig {
    
        /**
         * {@link Gson}:一個操作json的物件,有比較好的json操作體驗,相對于Alibaba的FastJson來說速度慢一些,但是FastJson在決議
         *              復雜的的json字串時有可能會出現bug,
         * @return Gson
         */
    
        @Bean
        public Gson gson() {
            return new Gson();
        }
    
    }
    

    Constants.java

    這是我寫的常量類,放一些ES使用的常量,直接寫字串也行,但是我建議這樣做,

    package com.butterflytri.constants;
    
    
    /**
     * @author: WJF
     * @date: 2020/5/22
     * @description: Constants
     */
    public class Constants {
    
        /**
         * es搜索關鍵字
         */
        public static final String KEYWORD = ".keyword";
    
        /**
         * es的type型別:type欄位將在 elasticsearch-version:8 中徹底洗掉,本來就覺得沒得啥用,
         */
        public static final String DOC_TYPE = "_doc";
    
        /**
         * 學生資訊索引型別
         */
        public static final String INDEX_STUDENT = "student_info";
    
    
        /**
         * 自定連接符
         */
        public static final String CONNECTOR = " --> ";
    
    }
    

    Student.java

    package com.butterflytri.entity;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    import java.io.Serializable;
    
    /**
     * @author: WJF
     * @date: 2020/5/16
     * @description: Student
     */
    
    @ToString
    @Getter
    @Setter
    public class Student implements Serializable {
    
        private Long id;
    
        private String studentName;
    
        private String studentNo;
    
        private String sex;
    
        private Integer age;
    
        private String clazz;
    
    }
    

    StudentMapper.java

    package com.butterflytri.mapper;
    
    import com.butterflytri.entity.Student;
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    
    /**
     * @author: WJF
     * @date: 2020/5/16
     * @description: StudentMapper
     */
    @Mapper
    public interface StudentMapper {
    
        /**
         * 查詢所有學生資訊
         * @return List<Student>
         */
        List<Student> findAll();
    
        /**
         * 通過id查詢學生資訊
         * @param id:學生id
         * @return Student
         */
        Student findOne(Long id);
    
        /**
         * 通過學號查詢學生資訊
         * @param studentNo:學生學號
         * @return Student
         */
        Student findByStudentNo(String studentNo);
    
    }
    

    mybatis的SQL映射檔案我就不展示了,也很簡單,大家看介面方法名就應該可以想象得到SQL陳述句是怎樣的,

  • 2.3.3 索引資料到ES服務器

    IndexServiceImpl.java

    package com.butterflytri.service.impl;
    
    import com.butterflytri.constants.Constants;
    import com.butterflytri.entity.Student;
    import com.butterflytri.service.IndexService;
    import com.google.gson.Gson;
    import org.elasticsearch.action.ActionListener;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.index.IndexResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.io.IOException;
    
    /**
     * @author: WJF
     * @date: 2020/5/22
     * @description: IndexServiceImpl
     */
    @Service
    public class IndexServiceImpl implements IndexService {
    
        @Resource
        private Gson gson;
    
        @Resource
        private RestHighLevelClient restHighLevelClient;
    
        @Override
        public String index(Student student) {
            StringBuilder builder = new StringBuilder();
            IndexRequest indexRequest = this.initIndexRequest(student);
            try {
                // 同步索引到elasticsearch服務器,獲取索引回應IndexResponse
                IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
                String statusName = indexResponse.status().name();
                int statusCode = indexResponse.status().getStatus();
                builder.append(statusName).append(Constants.CONNECTOR).append(statusCode);
            } catch (IOException e) {
                builder.append("Fail").append(Constants.CONNECTOR).append(e.getMessage());
            }
            return builder.toString();
        }
    
    
        @Override
        public String indexAsync(Student student) {
            StringBuilder builder = new StringBuilder();
            IndexRequest indexRequest = this.initIndexRequest(student);
            // 異步索引到elasticsearch服務器,獲取索引回應IndexResponse
            restHighLevelClient.indexAsync(indexRequest, RequestOptions.DEFAULT,actionListener(builder));
            return builder.toString();
        }
    
    
    
        /**
         * 初始化IndexRequest,并設定資料源,
         * @param student
         * @return IndexRequest
         */
        private IndexRequest initIndexRequest(Student student) {
            // 構建IndexRequest,設定索引名稱,索引型別,索引id
            IndexRequest indexRequest = new IndexRequest(Constants.INDEX_STUDENT);
            // 可以不設定,默認就是'_doc'
            indexRequest.type(Constants.DOC_TYPE);
            // 設定索引id為studentId
            indexRequest.id(String.valueOf(student.getId()));
            // 設定資料源
            String studentJson = gson.toJson(student);
            indexRequest.source(studentJson, XContentType.JSON);
            return indexRequest;
        }
    
        /**
         * 異步索引的回呼監聽器,根據不同的結果做出不同的處理
         * @param builder
         * @return ActionListener<IndexResponse>
         */
        private ActionListener<IndexResponse> actionListener(StringBuilder builder) {
            return new ActionListener<IndexResponse>() {
                // 當索引資料到es服務器時,回傳不同的狀態
                @Override
                public void onResponse(IndexResponse indexResponse) {
                    String statusName = indexResponse.status().name();
                    int statusCode = indexResponse.status().getStatus();
                    builder.append(statusName).append(Constants.CONNECTOR).append(statusCode);
                }
    
                // 當索引資料時出現例外
                @Override
                public void onFailure(Exception e) {
                    builder.append("Fail").append(Constants.CONNECTOR).append(e.getMessage());
                }
            };
        }
    }
    

    上面的內容很簡單,就是將Student物件格式化為Json字串,然后存到es服務器中,大家只要遵守一個規則就好,就是操作es服務器,不管是什么操作都是用RestHighLevelClient這個類去操作,上面的就是student物件索引的es服務器中,使用restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT),首先就是構建indexRequest物件,這個物件就是索引請求物件,具體干了什么看代碼上的注釋,這里還有個restHighLevelClient.indexAsync()這個方法,這個方法和上面的index方法一樣的效果,只不過是異步呼叫,

    接下來我們測驗一下這個代碼,請看:

    @Test
        public void indexTest() {
            List<Student> list = studentMapper.findAll();
            for (Student student : list) {
                String message = indexService.index(student);
                System.out.println(message);
            }
        }
    

    我們使用ElasticVue插件連接es服務器即可看到有一個索引庫:

    當我們點擊到show按鈕的時候,可以看到student_info索引庫中有幾條記錄:

    索引資料到資料庫成功了,

  • 2.3.4 獲取Es服務器資料

    獲取資料,是es提供給我們的API,這個Api只能獲取某個索引的某一條檔案,示例如下:

    GetServiceImpl.java

    	@Override
        public Student get(String id) {
            Student student = new Student();
            GetRequest getRequest = new GetRequest(Constants.INDEX_STUDENT, id);
            try {
                GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
                String source = getResponse.getSourceAsString();
                student = gson.fromJson(source, Student.class);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return student;
        }
    

    接著我們在測驗類中,呼叫這個方法然后列印一下結果:

    GetServiceTest.java

    	@Test
        public void getTest() {
            Student student = getService.get("1");
            System.out.println(student);
        }
    

    結果如下:

    更新資料檔案和洗掉資料檔案我就不演示了,都是大同小異,大家可以拉下我的代碼,好好研究一下,都有詳細的注釋,覺得可以的話,給我點下star也是極好的,下面演示一下searchApi,這個Api是我們經常需要使用的,特別重要,

  • 2.3.5 搜索Es服務器資料

    ES的搜索API包含很多,比如說組合搜索,區間搜索,高亮顯示,分詞搜索等等,我先給大家演示一下組合搜索,區間搜索其實也是組合搜索的一個子條件,其他的搜索其實也都是,代碼如下:

    SearchServiceImpl.java

    	@Override
        public List<Student> searchRange(Object from, Object to, String field, String index) {
            List<Student> list = new ArrayList<>();
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            // 需要搜索的區間欄位field
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(field);
            // 左區間
            if (from != null) {
                rangeQueryBuilder.from(from, true);
            }
            // 右區間
            if (to != null) {
                rangeQueryBuilder.to(to, true);
            }
            boolQueryBuilder.must();
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(boolQueryBuilder);
            SearchRequest searchRequest = new SearchRequest(index);
            searchRequest.source(searchSourceBuilder);
            try {
                SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                for (SearchHit hit : search.getHits()) {
                    String source = hit.getSourceAsString();
                    Student student = gson.fromJson(source, Student.class);
                    list.add(student);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return list;
        }
    

    上面的代碼其實很簡單,就是一個區間查詢構建器,查詢指定欄位處于區間的所有資料,rangeQueryBuilder.from(from, true)的第一個引數就是欄位的下邊界,第二個引數代表是否包含邊界,SearchResponse就是搜索的回應物件,所有的資料都在SearchHit物件中,

    接下來給大家演示一些組合查詢,這個方法搜索年齡在18到19歲并且班級為'G0305'的學生,記得ES默認是分頁的,如果想不分頁,一定要記得給搜索欄位加上.keyword(字串加,數字不支持),

    SearchServiceImpl.java

    @Override
        public List<Student> searchBool() {
            List<Student> list = new ArrayList<>();
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            boolQuery.must(QueryBuilders.rangeQuery("age").gte(18).lte(19));
            boolQuery.must(QueryBuilders.termQuery("clazz" + Constants.KEYWORD,"G0305"));
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(boolQuery);
            SearchRequest searchRequest = new SearchRequest(Constants.INDEX_STUDENT);
            searchRequest.source(searchSourceBuilder);
            try {
                SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                for (SearchHit hit : search.getHits()) {
                    String source = hit.getSourceAsString();
                    Student student = gson.fromJson(source, Student.class);
                    list.add(student);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return list;
        }
    

    上面的代碼中的類BoolQueryBuilder就是組合查詢構建器,這個類可以用來構建組合的條件查詢,boolQuery.must()方法就是用來拼接條件的一種方式,使用這個方法代表必須滿足這個條件才會查詢出來,上面的代碼說明必須滿足年齡為18(包含18)到19(包含19)歲,并且班級為'G0305'的學生才會查詢出來,還有其他的一些常見的組合查詢方法,如下:

    • boolQuery.must():必須滿足此條件,相當于=或者&
    • boolQuery.mustNot():必須不滿足此條件,相當于!=
    • boolQuery.should():相當于||或者or
    • boolQuery.filter():過濾,

    然后是聚合查詢,很類似于MySQL中的聚合函式,這個示例我就不再解釋了,代碼注釋很清楚:

    @Override
        public void searchBoolAndAggregation() {
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            boolQuery.must(QueryBuilders.rangeQuery("age").gte(18).lte(19));
            boolQuery.must(QueryBuilders.termQuery("clazz" + Constants.KEYWORD,"G0305"));
            // 聚合分組:按clazz欄位分組,并將結果取名為clazz,es默認是分詞的,為了精確配置,需要加上‘.keyword’關鍵詞后綴,
            TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("clazz").field("clazz" + Constants.KEYWORD);
            // 聚合求和:求符合查詢條件的學生的年齡的和,并將結果取名為ageSum,因為不是字串,所以默認是精確匹配,不支持分詞,
            aggregationBuilder.subAggregation(AggregationBuilders.sum("ageSum").field("age"));
            // 聚合求平均:求符合查詢條件的學生的年齡的平均值,并將結果取名為ageAvg,因為不是字串,所以默認是精確匹配,不支持分詞,
            aggregationBuilder.subAggregation(AggregationBuilders.avg("ageAvg").field("age"));
            // 聚合求數量:按學號查詢符合查詢條件的學生個數,并將結果取名為count,es默認是分詞的,為了精確配置,需要加上‘.keyword’關鍵詞后綴,
            aggregationBuilder.subAggregation(AggregationBuilders.count("count").field("studentNo" + Constants.KEYWORD));
            SearchSourceBuilder builder = new SearchSourceBuilder();
            builder.query(boolQuery);
            builder.aggregation(aggregationBuilder);
            // 按年齡降序排序,
            builder.sort("age", SortOrder.DESC);
            SearchRequest request = new SearchRequest("student_info");
            request.source(builder);
            try {
                SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                for (SearchHit hit : search.getHits()) {
                    String source = hit.getSourceAsString();
                    Student student = gson.fromJson(source, Student.class);
                    System.out.println(student);
                }
                // 使用Terms物件接收
                Terms clazz = search.getAggregations().get("clazz");
                for (Terms.Bucket bucket : clazz.getBuckets()) {
                    System.out.println(bucket.getDocCount());
    
                    System.out.println("=====================");
                    // 使用ParsedSum物件接收
                    ParsedSum ageCount = bucket.getAggregations().get("ageSum");
                    System.out.println(ageCount.getType());
                    System.out.println(ageCount.getValue());
                    System.out.println(ageCount.getValueAsString());
                    System.out.println(ageCount.getMetaData());
                    System.out.println(ageCount.getName());
    
                    System.out.println("=====================");
                    // 使用ParsedAvg物件接收
                    ParsedAvg ageAvg = bucket.getAggregations().get("ageAvg");
                    System.out.println(ageAvg.getType());
                    System.out.println(ageAvg.getValue());
                    System.out.println(ageAvg.getValueAsString());
                    System.out.println(ageAvg.getMetaData());
                    System.out.println(ageAvg.getName());
    
                    System.out.println("=====================");
                    // 使用ParsedValueCount物件接收
                    ParsedValueCount count = bucket.getAggregations().get("count");
                    System.out.println(count.getType());
                    System.out.println(count.getValue());
                    System.out.println(count.getValueAsString());
                    System.out.println(count.getMetaData());
                    System.out.println(count.getName());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    最后還有分詞查詢,分詞查詢就不加.keyword關鍵字即可,

    @Override
        public List<Student> searchMatch(String matchStudentName) {
            List<Student> list = new ArrayList<>();
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            // 分詞查詢時不加'.keyword'關鍵字
            boolQueryBuilder.must(QueryBuilders.matchQuery("studentName",matchStudentName));
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(boolQueryBuilder);
            SearchRequest searchRequest = new SearchRequest("student_info");
            searchRequest.source(searchSourceBuilder);
            try {
                SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                for (SearchHit hit : search.getHits().getHits()) {
                    String source = hit.getSourceAsString();
                    Student student = gson.fromJson(source, Student.class);
                    list.add(student);
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }
            return list;
        }
    

    請記住,一般的進行分詞都是字串才進行分詞搜索,數字等型別只能是精準匹配,

    最后,ES功能很強大,作為搜索界的扛把子,ES的功能遠遠不止這些,它還可以高亮搜索,資料分析等等,我在這里演示的僅僅只是皮毛,甚至都不是皮毛,僅作為初學者的參考,如有大佬覺得我哪里寫錯了,或者有不同見解,歡迎留言,

3. 專案地址

本專案傳送門:

  • GitHub ---> spring-boot-elasticsearch
  • Gitee ---> spring-boot-elasticsearch

此教程會一直更新下去,覺得博主寫的可以的話,關注一下,也可以更方便下次來學習,


  • 作者:Butterfly-Tri
  • 出處:Butterfly-Tri個人博客
  • 著作權所有,歡迎保留原文鏈接進行轉載??

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/167811.html

標籤:Java

上一篇:Maven安裝與配置

下一篇:大公司都在做的大資料平臺,為你精選這一份書單

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more