ElasticSearch(一)


前言


本篇内容为学习ElasticSearch 的课后总结,本文大致包括以下内容:

  • ElasticSearch的介绍
  • ElasticSearch与Kibana的单机环境搭建
  • Kibana中操作ES
  • 在集成环境中操作ES

整个过程记录详细,每个步骤亲历亲为实测可用


一、引言

  1. 什么是elasticsearch?
    :elasticsearch(以下简称es)是一款强大的开源搜索引擎,可以帮助我们从海量的数据中快速找到我们需要的内容,可以用来实现搜索日志统计分析系统监控等功能。例如:百度的搜索引擎就是基于ES 开发的。

  2. ES的好搭档:ELK。
    ELK代指ElasticSearchLogStashKibana。其中ES作为整个的核心,负责数据的存储;LogStash负责数据的收集和抓取;Kibana负责数据的可视化。

    ES为核心不可替代,LogStash和Kibana 可以使用其他工具进行替换。
    在这里插入图片描述

  3. ES是基于Lucene进行开发封装的。Lucene是一个Java语言编写的搜索引擎类库(可以理解为一个jar包),提供了搜索引擎得核心API,是Apache公司的顶级项目。其作者是大名鼎鼎的Doug Cutting,也就是Hadoop的创始人。(orz)

    在这里插入图片描述

    Lucene的优点: 易扩展 、高性能(倒排索引)

    Lucene的缺点: 只限于Java语言开发 、 学习曲线陡峭、不支持水平扩展。

  4. 由于Lucene 提供的接口实在是太不友好,直接使用难度较大。于是我们的Shay Banon 就封装了这些接口,开发了ElasticSearch 。据了解,该款框架最初名为Compass,是作者专门开发用于其妻子搜索食谱的。所以,要成为一个优秀的coder,我们得先…,new 一个对象。(qaq)

在这里插入图片描述

相较于Lucene,ES具备下列优势:

  • 支持分布式,可水平扩展。
  • 提供Restful 接口,可被任意语言调用。(只要能发Http 请求即可)

以上内容大致讲解了ES的作用以及一些与其相关小知识。下一节我们将学习关于ElasticSearch的理论知识。

二、学习ElasticSearch

  1. ES 中的一些概念与我们传统的关系型数据库有所不同。我们首先将两者进行对比比较:

    MySqlElasticSearch说明
    TableIndex索引(index),就是文档的集合,类似数据库中的表。
    RowDocument文档(Document),一条一条的数据,文档都是JSON格式的。
    ColumnField字段(Field),JSON文档中的字段
    SchemaMapping映射(Mapping),对索引中文案当的约束,例如字段类型的约束。
    SQLDSLDSL是ElasticSearch提供的JSON风格的请求语句,用来操作ES,实现CRUD
  2. ElasticSearch 是面向文档存储的,可以是数据库中的一条商品数据。文档数据先被序列化转为JSON格式后存储在es中。
    在这里插入图片描述
    3.引入ES的系统架构
    当然我们不会因为引入ES后而完全抛弃MySql,它们两者之间还是各有利弊的。

    • MySql擅长事务类型的操作,可以确保数据的安全和一致性。(ACID: 原子性、一致性、隔离性、持久性)
    • ElasticSearch 擅长海量数据的搜索、分析、计算。

    在这里插入图片描述 此时,当用户执行写操作时,我们将数据写入MySql中;当用户执行搜索相关大的操作时,我们则通过ES来进行查询。

    此时,出现了一个问题:如何保证MySql与ES中的数据一致性呢?

    呃,目前这个问题笔者也不太清楚。大概就是借助了某个工具实现了两者之间的数据同步。


上一小节,我们对ES的相关概念进行了讲解分析。接下来,我们先完成es与Kibana 的环境搭建。

三、ES与Kibana 的安装配置

  1. 单点部署ES
    我们还需要部署Kibana 容器,因此需要让ES 和Kibana 互连。这里先利用docker 创建一个网络。
    docker network create es-net

    创建成功后,可通过docker network ls 进行查看:
    在这里插入图片描述

  2. 加载镜像。这里我们采用elasticsearch的7.12.1版本的镜像,这个镜像体积非常大,接近1G。不建议大家自己pull。建议大家通过jar 包的形式传给虚拟机,然后再通过docker 进行 load 一下。

    在这里插入图片描述

    # 导入数据
    docker load -i es.tar
    

    同样的操作导入Kibana的jar 包,并load一下。

    此时,我们就准备好了ES 和Kibana 的镜像了!(两者的版本需保持一致

    在这里插入图片描述

  3. 运行ES
    运行docker,单点部署ES

    docker run -d \
       --name es \
       -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
       -e "discovery.type=single-node" \
       -v es-data:/usr/share/elasticsearch/data \
       -v es-plugins:/usr/share/elasticsearch/plugins \
       --privileged \
       --network es-net \
       -p 9200:9200 \
       -p 9300:9300 \
    elasticsearch:7.12.1
    

    命令解释:

    • -e "ES_JAVA_OPTS=-Xms512m -Xmx512m":内存大小;
    • -e "discovery.type=single-node":非集群模式;
    • -v es-data:/usr/share/elasticsearch/data:挂载逻辑卷,绑定es的数据目录;
    • -v es-plugins:/usr/share/elasticsearch/plugins:挂载逻辑卷,绑定es的插件目录;
    • --privileged:授予逻辑卷访问权;
    • --network es-net :加入一个名为es-net的网络中
    • -p 9200:9200:端口映射配置。此为接收Http请求的端口。
    • -p 9200:9200:端口映射配置。此为ES内部与Kibana 或集群环境交换数据的端口。

    运行成功后,此时,我们即可通过浏览器查看ES的启动效果啦!

    (对了,ES的启动通常来说速度比较慢,所以大火们需要耐心等待,也可以通过docker logs -f es 查看ES 的启动日志)

    在这里插入图片描述
    成功访问啦!记住此处的访问地址以及响应的内容,在Kibana 中做一个对比。

  4. 运行Kibana
    kibana可以提供一个elasticsearch的可视化界面。

    运行docker命令,部署kibana

    docker run -d \
    --name kibana \
    -e ELASTICSEARCH_HOSTS=http://es:9200 \
    --network=es-net \
    -p 5601:5601  \
    kibana:7.12.1
    

    命令解释:

    • --network es-net :加入一个名为es-net的网络中,与elasticsearch在同一个网络中;
    • -e ELASTICSEARCH_HOSTS=http://es:9200":设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch;
    • -p 5601:5601:端口映射配置。可通过此端口访问Kibana 的控制台。

    启动成功后,我们通过浏览器访问Kibana

    在这里插入图片描述5. DevTools: Kibana 提供了一个devtools 页面。在此页面可以编写DSL来操作ES,并且具备良好的代码补全功能。

    在这里插入图片描述

    在这里插入图片描述

    在此页面,我们可以直接编写DSL 语句来操作ES啦! 例如:我们之前可以直接通过浏览器向ES发起请求,在地址栏,我们输入了IP地址:9200。在此处用斜杠代替,此外,请求为Restful 风格,所以我们可以得到:
    在这里插入图片描述 细心的友友们就会注意到:此内容和我们直接直接通过浏览器访问页面的内容相同!


在上一节的内容,我们已经完成了ES 和Kibana 的环境搭建,接下来我们介绍ES 倒排索引中的分词器。

四、分词器

1. 分词器,es在创建倒排索引时需要对文档进行分词,在搜索时也需要对用户输入的内容进行分词。但是默认的分词规则对中文处理并不友好。我们先尝试默认的分词规则:

POST /_analyze
{
  "text": "今晚月色真美!",
  "analyzer": "standard"
}

语法说明:

  • POST:请求方式;

  • /_analyze:请求路径。这里忽略了IP:端口 ,由Kibana 补充。

  • 请求参数,json 风格:

    • analyzer:分词器类型,这里选择默认的standard 分词器;
    • text:分词的内容;

    发起请求后,我们可以发现:默认的分词器对于中文无法很好的识别。
    在这里插入图片描述
    此时,我们需要借助另外一个分词器:ik处理中文分词,一般会使用ik 分词器。

  1. ik 分词器的安装:
    这里提供两种安装ik 插件方式:离线和在线。在线安装的话,主机不能使用校园网。这里推荐使用离线安装,方便后续的扩展词典以及停用词典。

    a) 在线安装ik插件(较慢)

    # 进入容器内部
    docker exec -it elasticsearch /bin/bash
    
    # 在线下载并安装
    ./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
    
    #退出
    exit
    #重启容器
    docker restart elasticsearch
    

    b) 离线安装ik插件(推荐)
    ① 查看数据卷目录:安装插件需要知道elasticsearch的plugins目录位置,这里使用了数据卷挂载。

    docker volume inspect es-plugins
    在这里插入图片描述

    ② 将ik 目录上传到es 的插件数据卷中:

    在这里插入图片描述

    ③ 重启容器

    # 重启容器
    docker restart es
    
    # 查看es日志
    docker logs -f es
    
  2. ik 分词器的使用:
    ik 分词器包含两种模式:

    • ik_smart:最小切分;
    • ik_max_word:最细切分。

    将上面的内容再次通过 ik 分词:

    POST /_analyze
    {
      "text": "今晚月色真美!",
      "analyzer": "ik_smart"
    }
    

    结果如下,是不是清晰多了!

    在这里插入图片描述

  3. 扩展词词典与禁用词典
    不难分析,ik 的底层就像一本新华字典,将中文进行比对,然后再进行分词。随着时代的发展,很多新颖词汇ik 可不认识,因此,我们可以手动为ik 词典进行扩容。此外,俗话说得好:饭可以乱吃,话可不能乱说哦!,有些敏感词汇,我们肯定不能让它存在,ik 同时也能够配置敏感词过滤。

    在此目录/var/lib/docker/volumes/es-plugins/_data/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">ext.dic</entry>
    	 <!--用户可以在这里配置自己的扩展停止词字典-->
    	<entry key="ext_stopwords">stopword.dic</entry>
    	<!--用户可以在这里配置远程扩展字典 -->
    	<!-- <entry key="remote_ext_dict">words_location</entry> -->
    	<!--用户可以在这里配置远程扩展停止词字典-->
    	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
    </properties>
    

    扩展词词典与禁用词典就是一个文件,文件所在位置与该配置文件的位置相同。(没有文件,则自己创建文件。touch ext.dic

    在这里插入图片描述

    配置后,按理来说,ik 就能识别这些词啦!

    在这里插入图片描述

    大家可以亲自去试试,笔者这里很遗憾的告诉大家,我我我…失败了!

    具体发生了甚么问题,俺也不清楚,就是没成功!实在是太悲伤了…


到此为止,我们已经完成了单一架构的ES、Kibana 的全部环境搭建啦!接下来,我们将在Kibana 中的DevTools 学习DSL 的使用。

五、ES 的相关操作

  1. ES 中的索引相当于MySql 中的Table。 我们理所应当的开始介绍ES 索引库的CRUD。在此之前,我们先介绍ES 中的mapping 属性

    mapping 是对索引库中文档的约束(简单来说就是属性的一系列规定),常见的mapping 属性包括:

    • type:字段的数据类型,常见的简单类型 有:(当然还有其他复杂类型啦)
      • 字符串:text(可分词的文本)、keyword(精确值,不进行分词,例如:地区名称)
      • 数值:byte、short、integer、long、float、double
      • 布尔:boolean
      • 日期:date
      • 对象:object
    • index:是否创建索引(一般该字段不参与搜索时,可置为false)。默认为true。
    • analyzer:使用哪种分词器。
    • properties:该字段的子字段。
  2. 索引库的操作:
    a) 创建索引库:

    # 创建索引库
    PUT /heima
    {
      "mappings": {
        "properties": {
          "info": {
            "type": "text",
            "analyzer": "ik_smart"
          },
          "email": {
            "type": "keyword",
            "index": false
          },
          "name": {
            "type": "object",
            "properties": {
              "firstName": {
                "type": "keyword"
              },
              "lastName": {
                "type": "keyword"
              }
            }
          }
        }
      }
    }
    

    b) 查询索引库:

    # 查询索引库信息
    GET /heima
    

    c) 删除索引库:

    # 删除索引库
    DELETE /heima
    

    d) 修改索引库: 索引库和mapping 一旦创建无法修改,但是可以添加新的字段。

    PUT /heima/_mapping
    {
      "properties":{
        "新字段名":{
          "type":"integer"
        }
      }
    }
    
  3. 文档操作:
    a) 创建文档:
    POST /索引库/_doc/文档ID

    # 新增文档:
    POST /heima/_doc/1
    {
      "info":"重庆脚痛大学大学牲",
      "age":20,
      "email":"1522@qq.com",
      "name":{
        "firstName":"张",
        "lastName":"三"
      }
    }
    

    b) 查询文档:

    # 查询文档
    GET /heima/_doc/1
    

    c) 删除文档:

    # 删除指定文档
    DELETE /heima/_doc/1
    

    d) 修改文档:
    修改文档包括两种形式:全量修改局部修改

    # 方式一:全量修改,会根据文档id 先删除旧的文档,再添加新文档。因此,全量修改也可以用作添加文档!
    PUT /索引库名/_doc/文档id 
    {
      "info":"重庆脚痛大学大学牲(update)",
      "age":20,
      "email":"1522@qq.com",
      "name":{
        "firstName":"张",
        "lastName":"三"
      }
    }
    
    # 方式二:局部修改,修改指定字段
    POST /索引库名/_update/文档id
    {
      "doc": {
        "字段名":"新值"
      }
    }
    

在上一节的内容,我们大致学会了如何在DevTools 来操作ES,在下一节的内容中,我们将学习在IDEA中使用RestClient 操作ES。

六、RestClient

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句通过http请求发送给ES

其中的Java Rest Client又包括两种:

  • Java Low Level Rest Client
  • Java High Level Rest Client

此处,我们使用 Java High Level Rest Client 的API。

  1. 导入依赖
    由于Spring-boot 的父工程的启动依赖中,已经设置好了ES 的安装版本了。
    在这里插入图片描述
    因此,我们需要在项目中手动添加与自己适应版本的ES 版本属性信息。

        <properties>
            <elasticsearch.version>7.12.1</elasticsearch.version>
        </properties>
    	 <dependencies>
    		<!--  引入ES RestClient依赖-->
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-high-level-client</artifactId>
            </dependency>
            		<!--FastJson-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.71</version>
            </dependency>
        </dependencies>
    
    
  2. 初始化RestHighLevelClient

        private RestHighLevelClient client;
    
        @BeforeEach
        void setUp(){
            this.client = new RestHighLevelClient(RestClient.builder(
                    HttpHost.create("http://192.168.220.137:9200")
            ));
        }
    
        @AfterEach
        void tearDown() throws IOException {
            this.client.close();
        }
    
  3. 索引库相关的操作
    a) 创建索引库

    @Test
    void createHotelIndex() throws IOException {
        // 1. 创建Request 对象。指定索引库的名称
        CreateIndexRequest request = new CreateIndexRequest("hotel");
    
        // 2. 准备请求的参数:DSL语句
        // MAPPING_TEMPLATE 为JSON 风格的语句
        request.source(HotelConstans.MAPPING_TEMPLATE, XContentType.JSON);
    
        // 3. 发送请求
        // indices() 方法:获取对索引库操作的方法,包含:create()、get()、delete()、update()方法
        client.indices().create(request, RequestOptions.DEFAULT);
    }
    

    代码分为三步:

    • 1)创建Request对象。 因为是创建索引库的操作,因此Request是CreateIndexRequest。
    • 2)添加请求参数, 其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
    • 3)发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。

    HotelConstans.MAPPING_TEMPLATE的完整示例:

    public class HotelConstans {
    public static final String MAPPING_TEMPLATE = "" +
            "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"name\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"address\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"price\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"score\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"brand\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"city\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"star_name\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"bussiness\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"location\":{\n" +
            "        \"type\": \"geo_point\"\n" +
            "      },\n" +
            "      \"pic\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"all\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
    }
    
    

    b) 查看索引库

        @Test
        void getHotelIndex() throws IOException {
            GetIndexRequest request = new GetIndexRequest("hotel");
    
            GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT);
    
            System.out.println(response);
        }
    

    c) 删除索引库

    	@Test
        void createHotelIndex() throws IOException {
            // 1. 创建Request 对象。指定索引库的名称
            CreateIndexRequest request = new CreateIndexRequest("hotel");
    
            // 2. 准备请求的参数:DSL语句
            // MAPPING_TEMPLATE 为JSON 风格的语句
            request.source(HotelConstans.MAPPING_TEMPLATE, XContentType.JSON);
    
            // 3. 发送请求
            // indices() 方法:获取对索引库操作的方法,包含:create()、get()、delete()、update()方法
            client.indices().create(request, RequestOptions.DEFAULT);
        }
    

    d) 修改索引库:一般不对索引库进行修改。

  4. 文档相关的操作
    a) 添加文档

     @Test
    void testAddDocument() throws IOException {
        // 根据id 查询(先通过数据库准备数据)
        Hotel hotel = iHotelService.getById(61083L);
    
        HotelDoc hotelDoc = new HotelDoc(hotel);
    
        // 1. 准备Request 对象
        IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
    
        // 2. 准备JSON文档
        request.source(JSON.toJSONString(hotelDoc),XContentType.JSON);
    
        // 3. 发送请求
        client.index(request,RequestOptions.DEFAULT);
    }
    

    b) 查看文档

    @Test
    void testGetByIdDocument() throws IOException {
        GetRequest request = new GetRequest("hotel","61083");
    
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
    
        String json = response.getSourceAsString();
    
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    
        System.out.println(hotelDoc);
    }
    

    c) 删除文档

    @Test
    void testDeleteDocument() throws IOException {
        DeleteRequest request = new DeleteRequest("hotel","61083");
    
        client.delete(request, RequestOptions.DEFAULT);
    }
    

    d) 修改文档:此处为局部修改。

    @Test
    void testUpdateDocument() throws IOException {
        UpdateRequest request = new UpdateRequest("hotel","61083");
    
        request.doc(
                "price","952",
                "starName","四钻"
        );
    
        client.update(request, RequestOptions.DEFAULT);
    }
    

    e) 批量添加文档

    @Test
    void testBulkRequest() throws IOException {
        // 批量查询酒店数据(先从数据库中查询数据)
        List<Hotel> hotelList = iHotelService.list();
    
        // 1. 创建Request
        BulkRequest request = new BulkRequest();
    
        // 2. 添加多个Index
        hotelList.forEach(hotel -> {
            HotelDoc hotelDoc = new HotelDoc(hotel);
            request.add(new IndexRequest("hotel")
                    .id(hotelDoc.getId().toString())
                    .source(JSON.toJSONString(hotelDoc),XContentType.JSON));
        });
    
        // 3. 发送请求
        client.bulk(request,RequestOptions.DEFAULT);
    }
    
  5. 总结

    索引库操作的基本步骤:

    • 初始化RestHighLevelClient
    • 创建xxxIndexRequest。xxx是Create、Get、Delete
    • 准备DSL( Create时需要,其它是无参)
    • 发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete

    文档操作的基本步骤:

    • 初始化RestHighLevelClient
    • 创建xxxRequest。xxx是Index、Get、Update、Delete、Bulk
    • 准备参数(Index、Update、Bulk时需要)
    • 发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
    • 解析结果(Get时需要)

以上就为本篇文章的全部内容啦!
多多点赞支持一下呗!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值