0
点赞
收藏
分享

微信扫一扫

ElasticSearch基本使用

一、概述


倒排索引:在搜索引擎中,每个文档都有一个对应的文档ID,文档内容被表示为一系列关键词的集合。倒排索引就是关键词到文档ID的映射,每个关键词都对应着一系列的文件,这些文件中都出现了关键词。

倒排索引中的所有词项对应一个或多个文档;
倒排索引中的词项根据字典顺序升序排列
Luence是一个开源的全文检索引擎工具包(类似于Java API),而ElasticSerach底层是基于这些包,对其进行了扩展,提供了比Luence更为丰富的查询语言,可以非常方便的通过Elasticsearch的HTTP接口与底层Luence交互。

ElasticSearch和Solr:当实时建立索引的时候,Solr会产生阻塞,而es不会,es查询性能要高于solr;在不断动态添加数据的时候,solr的检索效率会变的低下,而es则没有什么变化;Solr利用zookeeper进行分布式管理,而es自身带有分布式系统管理功能。Solr一般都要部署到web服务器,比如tomcat,启动tomcat的时候需要配置tomcat和solr的管理,solr的本质是一个动态的web项目;solr支持更多的数据格式(xml、json、csv等),而es仅支持json文件格式;Solr是传统搜索应用的有力解决方案,但是es更适用于新兴的实时搜索应用。单纯的对已有数据进行检索的时候,solr的效率高于es;solr官网提供的功能更多,而es本身更注重于核心功能,攻击功能有第三方插件。

二、Elasticsearch入门


Elasticsearch简介:

一个分布式的、Restful风格(前后端交互的标准,http请求的格式)的搜索引擎;支持对各种类型的数据的检索;搜索速度快,可以提供实时的服务;便于水平扩展(集群时增加服务器),每秒可以处理PB级海量数据。

本质上是一个分布式数据库。

Elasticsearch术语:

索引、类型、文档、字段,与数据库的定义相对应,即database、table、row、column

集群、节点、分片(对索引进行划分,提高并发能力)、副本(备份)。

在多台机器上启动多个es进程实例,组成了一个es集群。

三、Elasticsearch安装配置


需要安装elasticsearch和分词插件两个工具。直接下载压缩文件解压缩即可,修改配置文件。

es下载网址:https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-4-3

解压缩后更改配置文件:cluster.name(集群名字)、path.data(存储数据的路径)、path.log(日志路径)

分词插件下载:https://github.com/medcl/elasticseatch-analysis-ik/releases/tag/v6.4.3

加压缩到es的plugins目录并新建ik文件夹下。

启动es:elasticsearch.bat
 

Kibana安装

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

与ElasticSearch一样,下载后解压,找到bin/kibana.bat文件双击启动kibana。(注意:需要版本对应)

验证Kibana:打开浏览器,输入http://127.0.0.1:5601/,(前提需要先启动ElasticSearch)

QQ截图20210623084345

看到如下界面安装成功

这里面可以提供很多模拟数据,感兴趣的可以自己玩玩,咱们学习期间只要使用左下角Dev Tools(开发工具)就可以了,点击后,会出现如下界面:

QQ截图20210623084847

大家可以看到我的界面是中文的,但默认是英文的。这个是可以自己更改的。

找到kibana下面的config/kibana.yml文件
QQ截图20210623090948

 在文件末追加i18n.locale: “zh-CN”,保存后,重启kibana。就可以看到汉化后的界面了

四、ES内置分词器

咱们知道Elasticsearch之所以模糊查询这么快,是因为采用了倒排索引,而倒排索引的核心就是分词,把text格式的字段按照分词器进行分词并编排索引。为了发挥自己的优势,Elasticsearch已经提供了多种功能强大的内置分词器,它们的作用都是怎样的呢?能处理中文吗?

首先咱们可以对Elasticsearch提供的内置分词器的作用进行如下总结:

下面讲解下常见的几个分词器:

Standard Analyzer(默认)

POST _analyze
{
  "analyzer": "standard",
  "text":     "Like X 国庆放假的"
}

img

Simple Analyzer

POST _analyze
{
  "analyzer": "simple",
  "text":     "Like X 国庆放假 的"
}

img

 Whitespace Analyzer

POST _analyze
{
  "analyzer": "whitespace",
  "text":     "Like X 国庆放假 的"
}

img

 Keyword

GET _analyze
{
  "analyzer": "keyword",
  "text": "Like X 国庆放假的"
}

QQ截图20210623100528

可以发现,这些内置分词器擅长处理单词和字母,所以如果咱们要处理的是英文数据的话,它们的功能可以说已经很全面了!那处理中文效果怎么样呢?下面咱们举例验证一下。

内置分词器对中文的局限性

首先咱们创建一个索引,并批量插入一些包含中文和英文的数据:

// 创建索引
PUT /ropledata
{
  "settings": { 
    "number_of_shards": "2", 
    "number_of_replicas": "0"
  } 
}

QQ截图20210623101454

 es head中刷新即可看到新创建的索引QQ截图20210623101531

 批量插入数据

// 批量插入数据
POST _bulk
{ "create" : { "_index" : "ropledata", "_id" : "1001" } }
{"id":1,"name": "且听风吟","hobby": "music and movie"}
{ "create" : { "_index" : "ropledata", "_id" : "1002" } }
{"id":2,"name": "静待花开","hobby": "music"}
{ "create" : { "_index" : "ropledata", "_id" : "1003" } }
{"id":3,"name": "大数据","hobby": "movie"}
{ "create" : { "_index" : "ropledata", "_id" : "1004" } }
{"id":4,"name": "且听_风吟","hobby": "run"}

在kibana的Dev Tools里执行情况:img

五、 ElasticSearch基本概念

文档(Document)

我们知道Java是面向对象的,而Elasticsearch是面向文档的,也就是说文档是所有可搜索数据的最小单元。ES的文档就像MySql中的一条记录,只是ES的文档会被序列化成json格式,保存在Elasticsearch中;
这个json对象是由字段组成,字段就相当于Mysql的列,每个字段都有自己的类型(字符串、数值、布尔、二进制、日期范围类型);当我们创建文档时,如果不指定字段的类型,Elasticsearch会帮我们自动匹配类型;
img

每个文档都有一个ID,类似MySql的主键,咱们可以自己指定,也可以让Elasticsearch自动生成;
 

类型(Type)

类型就相当于MySql里的表,我们知道MySql里一个库下可以有很多表,最原始的时候ES也是这样,一个索引下可以有很多类型,但是从7.0版本开始,type已经废弃,一个索引就只能创建一个类型了(_doc)。

索引(Index)

索引就相当于MySql里的数据库,它是具有某种相似特性的文档集合。索引的名称必须全部是小写;

索引具有mapping和setting的概念,mapping用来定义文档字段的类型,setting用来定义不同数据的分布。除了这些常用的概念,我们还需要知道节点概念的作用,因此咱们接着往下看!

节点(node)

一个节点就是一个ES实例,其实本质上就是一个java进程;ES的节点类型主要分为如下几种:

Master Eligible节点:每个节点启动后,默认就是Master Eligible节点,可以通过设置node.master: false 来禁止。Master Eligible可以参加选主流程,并成为Master节点(当第一个节点启动后,它会将自己选为Master节点);注意:每个节点都保存了集群的状态,只有Master节点才能修改集群的状态信息。
Data节点:可以保存数据的节点。主要负责保存分片数据,利于数据扩展。
Coordinating 节点:负责接收客户端请求,将请求发送到合适的节点,最终把结果汇集到一起
 

分片(shard)

ES里面的索引可能存储大量数据,这些数据可能会超出单个节点的硬件限制。为了解决这个问题,ES提供了将索引细分为多个碎片的功能,这就是分片。

分片的好处

通过分片技术,咱们可以水平拆分数据量,同时它还支持跨碎片(可能在多个节点上)分布和并行操作,从而提高性能/吞吐量;
ES可以完全自动管理分片的分配和文档的聚合来完成搜索请求,并且对用户完全透明;
注意:主分片数在索引创建时指定,后续只能通过Reindex修改,但是较麻烦,一般不进行修改。

副本分片(replica shard)

为了实现高可用、遇到问题时实现分片的故障转移机制,ElasticSearch允许将索引分片的一个或多个复制成所谓的副本分片。

副本分片的好处

当分片或者节点发生故障时提供高可用性。因此,副本分片永远不会分配到复制它的原始或主分片所在的节点上;

可以提高扩展搜索量和吞吐量,因为ES允许在所有副本上并行执行搜索;

默认情况下,ES中的每个索引都分配5个主分片,并为每个主分片分配1个副本分片。主分片在创建索引时指定,不能修改,副本分片可以修改。

分数 score

关于查询时,分数越高排位更高。那么分数是如何计算的:

搜索的关键字在文档中出现的频次越高,分数就越高
指定的文档内容越短,分数就越高
我们在搜索时,指定的关键字也会被分词,这个被分词的内容,被分词库匹配的个数越多,分数越高
 

六、基本操作

索引ropledata中有如下数据

QQ截图20210623175520

GET查询

GET全局搜索数据:

GET /ropledata/_search

QQ截图20210623200002

指定文档id搜索数据:

GET /ropledata/_doc/1001

QQ截图20210623200501

根据关键字搜索数据

GET /ropledata/_search?q=name:"且听风吟"

QQ截图20210623200918

term查询

比如咱们查询id字段为2的数据

 QQ截图20210623175922

terms查询

比如查询id字段为1和3的数据:

 QQ截图20210623180144

match查询

match_all全局搜索数据

 QQ截图20210623181105

match查询

 QQ截图20210623181720

multi_match查询

 QQ截图20210623203059

bool查询

 

复合过滤器,将你的多个查询条件,以一定的逻辑组合在一起。

must: 所有的条件,用must组合在一起,表示And的意思
must_not:将must_not中的条件,全部都不能匹配,标识Not的意思
should:所有的条件,用should组合在一起,表示Or的意思
must(and),所有的条件都要符合
QQ截图20210623204155

must_not(not)全部都不能匹配 QQ截图20210623204509

范围查询

  • gt大于
  • gte大于等于
  • lte小于
  • lte小于等于

range查询

QQ截图20210623214934

filter查询

 QQ截图20210623220728

 

聚合查询

首先咱们需要了解几个非常常用的数学统计函数:

avg:平均值
max:最大值
min:最小值
sum:求和
cardinality:去重
value_count:计数统计
terms:词聚合可以基于给定的字段,并按照这个字段对应的每一个数据为一个桶,然后计算每个桶里的文档个数。默认会按照文档的个数排序。
比如咱们求id的平均值QQ截图20210623221747

terms词聚合可以基于给定的字段,并按照这个字段对应的每一个数据为一个桶,然后计算每个桶里的文档个数。默认会按照文档的个数排序。 QQ截图20210623222827

 

七、springboot集成

创建一个springboot的项目 同时勾选上springboot-web的包以及Nosql的elasticsearch的包

<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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

注意下spring-boot的parent包内的依赖的es的版本是不是你对应的版本

不是的话就在pom文件下写个properties的版本

<!--这边配置下自己对应的版本-->
<properties>
    <java.version>1.8</java.version>
   <elasticsearch.version>7.13.2</elasticsearch.version>
</properties>

注入RestHighLevelClient 客户端

@Configuration
public class ElasticSearchClientConfig {
    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(new HttpHost("127.0.0.1",9200,"http"))
        );
        return client;
    }
}

测试索引,文档增删改,即批量操作

package com.example.springbootes;

import com.alibaba.fastjson.JSON;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
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.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.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

@SpringBootTest
class SpringbootEsApplicationTests {
    @Autowired
    @Qualifier("restHighLevelClient")
   private RestHighLevelClient client;
    //测试索引的创建
    @Test
    void testCreateIndex() throws IOException {
        //1.创建索引的请求
        CreateIndexRequest request = new CreateIndexRequest("lisen_index");
        //2客户端执行请求,请求后获得响应
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(response);
    }

    //测试索引是否存在
    @Test
    void testExistIndex() throws IOException {
        //1.创建索引的请求
        GetIndexRequest request = new GetIndexRequest("lisen_index");
        //2客户端执行请求,请求后获得响应
        boolean exist =  client.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println("测试索引是否存在-----"+exist);
    }

    //删除索引
    @Test
    void testDeleteIndex() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("lisen_index");
        AcknowledgedResponse delete = client.indices().delete(request,RequestOptions.DEFAULT);
        System.out.println("删除索引--------"+delete.isAcknowledged());
    }


    //测试添加文档
    @Test
    void testAddDocument() throws IOException {
        User user = new User("lisen",27);
        IndexRequest request = new IndexRequest("lisen_index");
        request.id("1");
        //设置超时时间
        request.timeout("1s");
        //将数据放到json字符串
        request.source(JSON.toJSONString(user), XContentType.JSON);
        //发送请求
        IndexResponse response = client.index(request,RequestOptions.DEFAULT);
        System.out.println("添加文档-------"+response.toString());
        System.out.println("添加文档-------"+response.status());
//        结果
//        添加文档-------IndexResponse[index=lisen_index,type=_doc,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
//        添加文档-------CREATED
    }

    //测试文档是否存在
    @Test
    void testExistDocument() throws IOException {
        //测试文档的 没有index
        GetRequest request= new GetRequest("lisen_index","1");
        //没有indices()了
        boolean exist = client.exists(request, RequestOptions.DEFAULT);
        System.out.println("测试文档是否存在-----"+exist);
    }

    //测试获取文档
    @Test
    void testGetDocument() throws IOException {
        GetRequest request= new GetRequest("lisen_index","1");
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        System.out.println("测试获取文档-----"+response.getSourceAsString());
        System.out.println("测试获取文档-----"+response);

//        结果
//        测试获取文档-----{"age":27,"name":"lisen"}
//        测试获取文档-----{"_index":"lisen_index","_type":"_doc","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"age":27,"name":"lisen"}}

    }

    //测试修改文档
    @Test
    void testUpdateDocument() throws IOException {
        User user = new User("李逍遥", 55);
        //修改是id为1的
        UpdateRequest request= new UpdateRequest("lisen_index","1");
        request.timeout("1s");
        request.doc(JSON.toJSONString(user),XContentType.JSON);

        UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
        System.out.println("测试修改文档-----"+response);
        System.out.println("测试修改文档-----"+response.status());

//        结果
//        测试修改文档-----UpdateResponse[index=lisen_index,type=_doc,id=1,version=2,seqNo=1,primaryTerm=1,result=updated,shards=ShardInfo{total=2, successful=1, failures=[]}]
//        测试修改文档-----OK

//        被删除的
//        测试获取文档-----null
//        测试获取文档-----{"_index":"lisen_index","_type":"_doc","_id":"1","found":false}
    }


    //测试删除文档
    @Test
    void testDeleteDocument() throws IOException {
        DeleteRequest request= new DeleteRequest("lisen_index","1");
        request.timeout("1s");
        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
        System.out.println("测试删除文档------"+response.status());
    }

    //测试批量添加文档
    @Test
    void testBulkAddDocument() throws IOException {
        ArrayList<User> userlist=new ArrayList<User>();
        userlist.add(new User("cyx1",5));
        userlist.add(new User("cyx2",6));
        userlist.add(new User("cyx3",40));
        userlist.add(new User("cyx4",25));
        userlist.add(new User("cyx5",15));
        userlist.add(new User("cyx6",35));

        //批量操作的Request
        BulkRequest request = new BulkRequest();
        request.timeout("1s");

        //批量处理请求
        for (int i = 0; i < userlist.size(); i++) {
            request.add(
                    new IndexRequest("lisen_index")
                            .id(""+(i+1))
                            .source(JSON.toJSONString(userlist.get(i)),XContentType.JSON)
            );
        }
        BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
        //response.hasFailures()是否是失败的
        System.out.println("测试批量添加文档-----"+response.hasFailures());

//        结果:false为成功 true为失败
//        测试批量添加文档-----false
    }


    //测试查询文档
    @Test
    void testSearchDocument() throws IOException {
        SearchRequest request = new SearchRequest("lisen_index");
        //构建搜索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //设置了高亮
        sourceBuilder.highlighter();
        //term name为cyx1的
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "cyx1");
        sourceBuilder.query(termQueryBuilder);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        request.source(sourceBuilder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        System.out.println("测试查询文档-----"+JSON.toJSONString(response.getHits()));
        System.out.println("=====================");
        for (SearchHit documentFields : response.getHits().getHits()) {
            System.out.println("测试查询文档--遍历参数--"+documentFields.getSourceAsMap());
        }

//        测试查询文档-----{"fragment":true,"hits":[{"fields":{},"fragment":false,"highlightFields":{},"id":"1","matchedQueries":[],"primaryTerm":0,"rawSortValues":[],"score":1.8413742,"seqNo":-2,"sortValues":[],"sourceAsMap":{"name":"cyx1","age":5},"sourceAsString":"{\"age\":5,\"name\":\"cyx1\"}","sourceRef":{"fragment":true},"type":"_doc","version":-1}],"maxScore":1.8413742,"totalHits":{"relation":"EQUAL_TO","value":1}}
//        =====================
//        测试查询文档--遍历参数--{name=cyx1, age=5}
    }

举报

相关推荐

0 条评论