ElasticSearch框架

ElasticSearch框架

moran
2021-04-03 / 0 评论 / 538 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2021年04月14日,已超过72天没有更新,若内容或图片失效,请留言反馈。

ElasticSearch

聊聊Doug Cutting






Lucene是一套信息检索工具包,jar包,不包含引擎系统。
包含的结构:索引结构,读写索引的工具,排序,搜索规则...工具类。
Lucene和ElasticSearch关系:
ElasticSearch是基于Lucene做了一些封装和增强

简介

Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析和探索的能力。充分利用Elasticsearch的水平伸缩性,能使数据在生产环境变得更有价值。Elasticsearch 的实现原理主要分为以下几个步骤,首先用户将数据提交到Elasticsearch 数据库中,再通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据,当用户搜索数据时候,再根据权重将结果排名,打分,再将返回结果呈现给用户。
Elasticsearch是与名为Logstash的数据收集和日志解析引擎以及名为Kibana的分析和可视化平台一起开发的。这三个产品被设计成一个集成解决方案,称为“Elastic Stack”(以前称为“ELK stack”)。
Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。”Elasticsearch是分布式的,这意味着索引可以被分成分片,每个分片可以有0个或多个副本。每个节点托管一个或多个分片,并充当协调器将操作委托给正确的分片。再平衡和路由是自动完成的。“相关数据通常存储在同一个索引中,该索引由一个或多个主分片和零个或多个复制分片组成。一旦创建了索引,就不能更改主分片的数量。
Elasticsearch使用Lucene,并试图通过JSON和Java API提供其所有特性。它支持facetting和percolating,如果新文档与注册查询匹配,这对于通知非常有用。另一个特性称为“网关”,处理索引的长期持久性;例如,在服务器崩溃的情况下,可以从网关恢复索引。Elasticsearch支持实时GET请求,适合作为NoSQL数据存储,但缺少分布式事务。

据国际权威的数据库产品评测机构DB Engines的统计,在2016年1月,ElasticSearch已超过Solr,成为排名第一的搜索引擎应用。


教程说明

本文章学习自狂神的【狂神说Java】ElasticSearch7.6.x最新完整教程通俗易懂
狂神的视频对于框架的简单入门很是友好!!!

ElasticSearch和Solr对比








ElasticSearch的安装与使用

安装

安装ElasticSearch

声明:JDK1.8,最低要求!ElasticSearch客户端,界面工具。

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

ELK三剑客,解压即用!(因为是web项目,需要前端node环境)

  • 解压就可以使用了
  • 目录结构

    bin 启动文件
    config 配置文件
        log4j2 日志文件
        jvm.options Java虚拟机相关配置
        elasticsearch.yml elasticsearch的配置文件!默认端口9200。
    lib 相关jar包
    log 日志
    modules 功能模块
    plugins 插件(ik分词器)

解压后,进入bin目录,双击运行elasticsearch.bat就运行了。

浏览器访问9200端口

安装可视化插件-head

下载地址:github

因为elasticsearch-head是一个前端基于nodejs的项目,所以需要安装依赖
cmd进入解压后的文件夹
执行如下安装命令

cnpm install // 这里使用的是淘宝里的镜像
// 安装依赖完毕后运行elasticsearch-head
npm run start

默认端口9100,如下:


访问9100端口,连接elasticsearch会发现浏览器控制台报跨域问题,如下:

解决方法:配置elasticsearch开启跨域
找到config/elasticsearch.yml文件,在末尾加上如下配置:

http.cors.enabled: true
http.cors.allow-origin: "*"

然后重启服务,再点击连接就不会出现跨域。

通过右上角的信息就可以看到9200端口的基本信息

建议将elasticsearch-head当做一个数据展示工具,不建议使用它的查询功能,不是很方便。
查询功能建议使用Kibana。

  • 了解Elk

安装Kibana


下载地址:官网
注意:Kibana下载的版本要和ES一致。

Kibana也是一个web项目,需要nodejs环境。
不过,它提供了bat打开方式。在bin目录中。

  • 目录结构
  • 启动测试
    运行kibana.bat,执行后发现默认端口5601,如下:


页面如下:

  • 使用Kibana的开发工具测试

  • 对Kibana汉化(可选)
    修改的文件:config/kibana.yml

添加内容:

i18n.locale: "zh-CN"
汉化结果:
![](https://moran.pblog.top/usr/uploads/2021/04/754691196.png)

ElaticSearch的核心概念








ElasticSearch之ik分词器

概述


如果要使用中文,建议使用ik分词器。

安装

下载地址:github
下载完毕之后,解压放到elasticsearch的插件目录中(plugins)。

解压后,如果需要使用ik分词器需要重启ES。
重启会发现ik分词器被加载了

也可以通过bin/elasticsearch-plugin.bat这个文件查看加载的插件

上面的报错是因为最新版本的ik期望用jdk11,ik内置了jdk,如果配置了JAVA_HOME环境变量就会使用配置的。

使用分词器

使用不同的分词器
ik_smart和ik_max_word,其中ik_smart为最少切分,ik_max_word为最细粒度划分。

  • ik_smart
    如果词库匹配到多字的词句,那就就只返回这一条,如果没匹配到,就返回单字单字的数据。

  • ik_max_word
    返回匹配到的所有结果

其实就是通过字典匹配来分词的
如果需要匹配自己的词,比如"狂神说",在默认的词库中是不能匹配到的,只会分成一个一个的字,如下:

配置:
配置文件:es目录/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">my.dic</entry>
     <!--用户可以在这里配置自己的扩展停止词字典-->
    <entry key="ext_stopwords"></entry>
    <!--用户可以在这里配置远程扩展字典 -->
    <!-- <entry key="remote_ext_dict">words_location</entry> -->
    <!--用户可以在这里配置远程扩展停止词字典-->
    <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

写上其中的扩展字典名称
然后在当前目录下创建该词库,添加上数据。

修改配置就需要重启es。
启动的时候可以发现es加载了自己扩展的字典:

结果:

Rest风格api

使用

  • 创建索引

    PUT /索引名/类型名/文档id
    {json请求体}


    执行后,索引库中就会有添加的数据。

    • 字段类型

    网址:官网

    • 创建索引规则
  • 获取索引信息

获取默认索引信息(当索引不指定映射时,会有默认的字段类型)

+ 查看索引健康
    > GET _cat/heath
+ 查看索引指数信息
    > GET _cat/indices?v
  • 修改索引

    • 覆盖提交

      PUT /test3/_doc/1
      {
          "name":"狂神说111",
          "age":23,
          "birth":"2001-01-01"
      }

    覆盖提交后,如果有缺少了字段,那就是真的缺少了,而不会被赋值为空。

    • 更新提交

      POST /test3/_doc/1/_update
      {
          "doc":{
              "name":"张三说"
          }
      }

      这里的doc是固定写法,表明需要修改哪些文档(字段)。

    • 删除索引

      DELETE test2

      之前都是一些简单的crud操作。下面是复杂操作:

    • 条件搜索

      GET /kuangshen/user/_search?q=name:张三


      _score是匹配的精准度(权重)
      这种方式是分词查询,会将条件先分词,然后组合搭配分词后的结果是否匹配数据,匹配就返回吗,组合匹配如:狂Java,也是可以搜索到的。
      或者使用第二种方法:

      GET /kuangshen/user/_search
      {
          "query":{
              "match": {
              "name": "张三"
              }
          }
      }

      这两种方法其实都是类似的,都会分词后组合匹配查询。

      注意:右边匹配后的结果返回的json属性在Java中都可以使用(后面会用到)。

      • 选择显示字段
        类似于select xxx from xxx

      • 排序

        GET /kuangshen/user/_search
        {
            "query":{
                "match": {
                "name": "狂神"
                }
            },
            "sort":[
                {
                    "age":{
                    "order":"desc"
                    }
                }
                ]
            }

      • 分页

        GET /kuangshen/user/_search
        {
            "query":{
                "match": {
                "name": "狂神"
                }
            },
            "sort":[
                {
                    "age":{
                    "order":"desc"
                    }
                }
             ],
            "from":0,
            "size":1
        }

        from:第几条开始
        size: 一页显示几条

      • 多条件查询
        逻辑操作and

        GET /kuangshen/user/_search
        {
            "query":{
                "bool": { // 逻辑运算
                    "must": [  // 类似逻辑运算 and,所有条件都要满足
                        {
                        "match": {  // name是text类型的,因此分词匹配(分词+like)
                            "name": "Java"
                        }
                        },
                        {
                        "match": {  // 因为是long类型的,所以精准匹配(==)
                            "age": 23
                        }
                        }
                    ]
                } 
            }
        }

        逻辑操作or

        GET /kuangshen/user/_search
        {
            "query":{
                "bool": { // 逻辑运算
                    "should": [  // 类似逻辑运算 or,满足一个条件即可
                        {
                        "match": {  // name是text类型的,因此分词匹配(分词+like)
                            "name": "Java"
                        }
                        },
                        {
                        "match": {  // 因为是long类型的,所以精准匹配(==)
                            "age": 23
                        }
                        }
                    ]
                } 
            }
        }

        逻辑操作not

        GET /kuangshen/user/_search
        {
           "query":{
               "bool": { // 逻辑运算
                   "must_not": [  // 类似逻辑运算 not,所有条件都不满足
                       {
                       "match": {  // name是text类型的,因此分词匹配(分词+like)
                           "name": "Java"
                       }
                       },
                       {
                       "match": {  // 因为是long类型的,所以精准匹配(==)
                           "age": 23
                       }
                       }
                   ]
               } 
           }
        }

        filter过滤,筛选数据
        以年龄大小区间举例:

        GET /kuangshen/user/_search
        {
            "query":{
                "bool": {
                "must": [
                    {
                    "match": {
                        "name": "狂神"
                    }
                    }
                ],
                "filter": [ // 过滤
                    {
                    "range": { // 范围
                        "age": { // 被过滤的字段 
                            "gte": 10,  // 大于等于
                            "lte": 20   // 小于等于
                        }
                        // gt大于,lt小于,上面是多个条件过滤,也可以单条件过滤
                    }
                    }
                ]
                }
            }
        }

        如果像tags这种数组的查询:

      • 精准查询

        term 查询是直接通过倒排索引指定的词条进行精确查询的。

        term:直接查询精确的

        match:会使用分词器解析文档,然后再通过分析的文档进行查询

        • 两种类型text keyword

        keyword:

        text:



        嵌套操作

      • 高亮显示

      自义定前后缀

ElasticSearch整合SpringBoot

教程:官网

  • 原生依赖

    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.12.0</version>
    </dependency>
    如果是springboot项目则使用对应starter
  • 使用的对象

简单例子

  • 创建项目,勾选组件

除此之外还需要spring web

  • pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.4.4</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.kuang</groupId>
        <artifactId>kuangshen-es-api</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>kuangshen-es-api</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>1.8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>

    导入依赖后,可能出现导入的elasticsearch依赖的版本和我们在运行的版本不一致,需要自己定义版本,保证和本地一致。

  • 创建配置类

    package com.kuang.config;
    
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class ElasticSearchClientConfig {
    
        public RestHighLevelClient restHighLevelClient(){
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
            );
            return client;
        }
    }
  • 测试

    package com.kuang;
    
    import com.alibaba.fastjson.JSON;
    import com.kuang.pojo.User;
    import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
    import org.elasticsearch.action.bulk.BulkRequest;
    import org.elasticsearch.action.bulk.BulkResponse;
    import org.elasticsearch.action.delete.DeleteRequest;
    import org.elasticsearch.action.delete.DeleteResponse;
    import org.elasticsearch.action.get.GetRequest;
    import org.elasticsearch.action.get.GetResponse;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.index.IndexResponse;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.action.support.master.AcknowledgedResponse;
    import org.elasticsearch.action.update.UpdateRequest;
    import org.elasticsearch.action.update.UpdateResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.client.indices.CreateIndexRequest;
    import org.elasticsearch.client.indices.CreateIndexResponse;
    import org.elasticsearch.client.indices.GetIndexRequest;
    import org.elasticsearch.common.unit.TimeValue;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.index.query.TermQueryBuilder;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.concurrent.TimeUnit;
    
    @SpringBootTest
    class KuangshenEsApiApplicationTests {
    
        @Autowired
        private RestHighLevelClient client;
    
        // 创建索引
        @Test
        void testCreateIndex() throws IOException {
            // 1.创建索引请求
            CreateIndexRequest request = new CreateIndexRequest("kuang");
            // 2.客户端执行请求,请求后获得响应
            CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
            // 通过createIndexResponse的index方法获取索引名
            System.out.println(createIndexResponse.index());
        }
    
        // 判断索引是否存在
        @Test
        void testExistIndex() throws IOException {
            GetIndexRequest request = new GetIndexRequest("kuang_index2");
            boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
            System.out.println(exists);
        }
    
        // 删除索引
        @Test
        void testDeleteIndex() throws IOException {
            DeleteIndexRequest request = new DeleteIndexRequest("kuang");
            AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
            // 判断索引是否删除
            System.out.println(delete.isAcknowledged());
        }
    
        // 添加文档
        @Test
        void testAddDocument() throws IOException {
            // 创建对象
            User user = new User("狂神说",3);
            IndexRequest request = new IndexRequest("kuang");
            request.id("1");
            request.timeout(TimeValue.timeValueSeconds(1));
            // request.timeout("1s");
            // 将数据存入放入请求  json 引入fastjson
            request.source(JSON.toJSONString(user), XContentType.JSON);
            // 客户端发送请求
            IndexResponse response = client.index(request, RequestOptions.DEFAULT);
            // 输出
            System.out.println(response.status());  // CREATED
            // IndexResponse[index=kuang,type=_doc,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
            System.out.println(response.toString());
        }
    
        // 获取文档,判断是否存在
        @Test
        void testExists() throws IOException {
            // 获取kuang索引中的第一条文档(记录)
            GetRequest request = new GetRequest("kuang","1");
            // 不会获取返回的_source的上下文  可以不用
            request.fetchSourceContext(new FetchSourceContext(false));
            // 不存储
            request.storedFields("_none_");
            boolean exists = client.exists(request, RequestOptions.DEFAULT);
            System.out.println(exists);
        }
    
        // 获取文档信息
        @Test
        void testGetDocument() throws IOException {
            GetRequest request = new GetRequest("kuang","1");
            GetResponse getResponse = client.get(request, RequestOptions.DEFAULT);
            // {"age":3,"name":"狂神说"}
            System.out.println(getResponse.getSourceAsString()); // 打印文档的内容
            // {name=狂神说, age=3}
            System.out.println(getResponse.getSource());
        }
    
        // 更新文档内容
        @Test
        void testUpdateRequest() throws IOException {
            // 指定更新请求的索引和文档id
            UpdateRequest updateRequest = new UpdateRequest("kuang","1");
            // 更新对象
            User user = new User("狂神说Java",23);
            // 这里的doc和在kibana发送请求更新是一致的。那边怎么操作,这边也是这样的。
            UpdateRequest request = updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
            UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
            System.out.println(response.status());
        }
    
        // 删除文档
        @Test
        void testDeleteRequest() throws IOException {
            DeleteRequest request = new DeleteRequest("kuang", "1");
            request.timeout("1s");
    
            DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
            System.out.println(response.status());
        }
    
        // 批量插入数据  bulk大量
        @Test
        void testBulkRequest() throws IOException {
            BulkRequest bulkRequest = new BulkRequest();
            bulkRequest.timeout("10s");
    
            // 大量数据
            ArrayList<User> userList = new ArrayList<>();
            userList.add(new User("kuangshen1",2));
            userList.add(new User("kuangshen1",2));
            userList.add(new User("kuangshen1",2));
            userList.add(new User("kuangshen1",2));
            userList.add(new User("moran",1));
            userList.add(new User("moran",1));
            userList.add(new User("moran",1));
            userList.add(new User("moran",1));
            userList.add(new User("moran",1));
    
            // 遍历
            // 批处理请求
            for (int i = 0; i < userList.size(); i++) {
                // 这里是批量增加,批量删除和批量修改只要修改对应的对象即可。
                bulkRequest.add(
                        new IndexRequest("kuang")
                                .id(""+(i+1))
                                .source(JSON.toJSONString(userList.get(i)),XContentType.JSON));
            }
    
            BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
            // 是否有失败
            System.out.println(response.hasFailures());
        }
    
        // 查询
        @Test
        void testSearch() throws IOException {
            // 构建搜索请求对象
            SearchRequest searchRequest = new SearchRequest("kuang");
            // 构建搜索条件对象
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 使用QueryBuilders工具类精确匹配
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "moran");
            sourceBuilder.query(termQueryBuilder);
            // 设置超时时间
            sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
            // 这里还可以进行分页操作,方法和json时操作一致,from,size,
            // 高亮也可以在这里设置
    
            // 放入搜索条件
            searchRequest.source(sourceBuilder);
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(JSON.toJSONString(response.getHits()));
            System.out.println("==============================");
            for (SearchHit documentFields : response.getHits().getHits()) {
                System.out.println(documentFields.getSourceAsMap());
            }
        }
    }

小知识点

解析网页可以使用:jsoup
该工具解析器类似于dom4j。

可以用来代替HttpClient发送请求解析网页。因为他有类似js的语法。操作起来很简单。

如果用来解析视频需要使用tika

es高亮显示

0

评论 (0)

取消