首页 > 技术文章 > ElasticSearch全文检索

Liuyunsan 2021-12-14 16:55 原文

Elasticsearch是一个基于Lucene的搜索服务器。
它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

Sql: like %xxx% ,如果是大数据,就十分的慢(用索引提升效率但是还是比较慢)

ElasticSearch/Soir:搜索(百度,github,淘宝,电商)

1. ElasticSearch创始人

Doug Cutting :Lucene(基于Lucene封装的搜索引擎:soir,ElasticSearch),全文检索功能(开源)

大数据的两个问题:存储(GFS,NDFS),计算(MapReduce编程模型)
NDFS+MapReduce=Hadoop

Lucene是一套信息检索工具包,jar包,不包括搜索引擎系统

  • 索引结构
  • 读取索引的工具
  • 排序
  • 联测
  • 搜索规则(工具类)

Lucene和ElasticSearch关系 :ElasticSearch是基于Lucene做了一些封装和增强(通过简单的Restful Api来隐藏Lucene的复杂性,从而让全文搜索变得简单)

日志数据分析(ELK,elasticsearch搜索+logstash过滤+kibana可视化分析)

2. 对比其他搜索引擎

ElasticSearch:(RestFult APi)基于Lucene,全文搜索,结构化搜索,分析(高亮,实时纠错,用户画像分析,数据清理)
Solr:(Web APi)基于Lucene,可以独立运行(用post方法向Solr服务器发送一个描述Field及其内容的XML文档,Slor根据XML文档添加,删除,更新索引),Solr不提供UI的功能,Solr提供一个管理界面,通过管理界面可以查询Solr的配置和运行情况
Lucene:Lucene是一套java信息检索工具包,jar包,不包括搜索引擎系统

ES和Solr的差别?

  • 对单纯已有的数据进行搜索,Solr更快
  • 当实时建立索引时,Solr会产生io阻塞,查询性能较差,ElasticSearch具有更明显的优势
  • 随着数据量的增加,Solr的搜索效率会变得更低,而ES无明显变化

总结:

  • es基本开箱即用,非常简单,Solr安装较为复杂
  • Solr利用Zookeeper进行分布式管理,而es自身带有分布式协调管理功能
  • Solr支持更多格式的数据,如json,xml,csv,而es仅支持json
  • Solr提供的功能很多,但es本身更注重核心功能,高级功能多有第三方插件提供,例如图形化界面的kibana
  • Solr查询快,但更新索引时慢(即插入删除慢),es为实时性查询
  • Solr比较成熟,es相对开发维护者较少,更新太快,学习成本较高

3. 安装

  1. 安装es
    java版本:jdk1.8以上

    node.js
    python
    npm

    下载解压即可


bin:启动文件
config配置文件

  • log4j2.properties:日志配置文件
  • jvm.options:java虚拟机相关配置
  • elasticsearch.yaml:es配置文件 默认9200端口!跨域
    jdk:环境
    lib:相关jar包lucene
    modules:功能模块
    plugins:插件(ik分词器)
    logs:日志

启动
双击bin/elasticsearch.bat文件

访问127.0.0.1:9200

  1. 安装可视化界面head
    下载地址:https://github.com/mobz/elasticsearch-head


    跨域问题(9100~9200)
    es配置文件

重启es服务查看head

索引就相当于一个数据库(表就相当于一个文档,类型相当于属性)

这个head就当作是一个数据展示工具,我们后面所有的查询可以在kabana做

  1. 安装kibana(可视化平台)
    ELK关系
  2. 收集清洗数据(Loastash)
  3. 搜索,存储(ES)
  4. 分析,展示(Kibana)

下载解压

启动

访问测试

开发工具(之后的所有操作都在这里进行)

修改语言

ES核心概念
集群,节点,索引,类型,文档,分片,映射是什么?
elasticSearch是面向文档 一切都是json

Mysql ElasticSearch
数据库(database) 索引(indices)
表(tables) 类型(types)【慢慢被弃用】
行(rows) 文档(documents)
字段(columns)属性 映射(fields)

物理设计:elasticSearch在后台把每个索引划分成多个切片,每分分片可以在集群中的不同服务器上迁移
一个人就是一个集群,默认集群名称就是elasticsearch

文档 :索引的最小数据就是文档(就是一条条数据)

类型 :字段类型映射(数据类型)

索引 :数据库(一个elasticsearch索引是由多个Lucene倒排索引组成的)

分片:每个分片都是一个Lucene倒排索引

倒排索引:采用Lucene倒排索作为底层,这种结构用于快速的全文搜索,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表。

4. 生态圈

5. ik分词器

分词:即把一段中文或者别的划分成一个个的关键词,我们在搜索时会把我们自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每一个字看成一个词,所以我们需要安装IK分词器来解决这个问题
ik分词器两种算法

问题:字典中不存在的自定义词,需要自己加入到分词器字典中

ik分词器增加自己的配置:

重启es

查看

以后需要我们自己配置分词只需要在自己定义的dic文件中进行即可

6. RestFul操作ElasticSearch

RestFul风格说明:一种软件架构风格,只是提供了一组设计原则和约束条件,它主要用于客户端与服务器交互类的软件,基于这个风格设计的软件可以更加简洁,更有层次,更易于实现缓存等机制

基本Rest命令说明

method url地址 描述
PUT localhost:9200/索引名称/类型名称/文档id 创建文档(指定文档id)
POST localhost:9200/索引名称/类型名称 创建文档(随机文档id)
POST localhost:9200/索引名称/类型名称/文档id/_update 修改文档
DELETE localhost:9200/索引名称/类型名称/文档id 删除文档
GET localhost:9200/索引名称/类型名称/文档id 查询文档通过文档id
POST localhost:9200/索引名称/类型名称/_search 查询所有数据

基本测试

  1. 创建一个索引
    PUT /索引名/类型名/文档id
    {
    请求体
    }


完成了自动增加索引,数据也成功的添加了

数据类型
字符串类型:text,keyword(不可分割)
数值类型:long,integer,short,byte,double,float,scaled
日期类型:date
布尔值:boolean
二进制:binary

  1. 指定字段类型
    创建具体的索引规则

  2. 通过get请求获取信息



    如果自己的文档字段没有指定,那么es就会自动给我们配置字段类型

  3. 修改提交还是可以使用Put即可,进行覆盖
    版本号增加

    使用POST修改

  4. 删除索引
    通过DELETE命令实现删除

    使用RestFul风格是es推荐使用的

7. 文档的基本操作(重点)

  • 基本操作

    添加数据

    获取数据

    更新数据
    version代表这个数据被改动的次数(put如果不传值就会被覆盖)

    推荐使用一下这种方式POST

    查询数据
    简单查询:通过默认的映射规则,产生基本的查询
    GET /liuyunsan/user/1
    GET /liuyunsan/user/_search?q=name:修仙

    keyword只能整体搜索

    匹配度(匹配度越高分值越高)

  • 复杂操作(排序,分页,高亮,模糊查询,精准查询)

hit:索引和文档的信息,查询的结果总数,然后就是查询出来的具体文档,数据中的东西就可以遍历出来了,分数,我们可以通过score来判断谁更加符合结果

结果过滤

排序(desc,asc)

分页查询

/search/{current}/{pagesize}
boolean值查询(多条件精确查询)and or not

must(相当于and,所有条件都要符合)
should(相当于or,其中一个符合即可)
must_not(相当于not,都不符合的即可)

过滤器filter 区间
查询年龄大于10小于25的个数

gt:大于
gte:大于等于
lt:小于
lte:小于等于

匹配多个条件,通过权重来判别匹配的程度(多个条件空格隔开即可,只要满足其中一个结果就会被查出)

精确查询(term查询是通过倒排索引指定的词条精确查找的)

关于分词:
term:直接查找精确的值(效率高)
match:会使用分词器解析(先分析文档,再通过分析的文档进行查询)

两个类型

text:分词器解析

keyword:不会被分词器解析

多个值匹配的精确查询

高亮查询(搜索的结果会帮你自动增加一个html标签)自定义样式

  • 匹配
  • 按条件匹配
  • 精确匹配
  • 区间范围匹配
  • 匹配字段过滤
  • 多条件查询
  • 高亮查询
  • 倒排索引
  • mysql也可以做,但是效率很低

8. SpringBoot集成ElasticSearch(从原理分析)

文档:https://www.elastic.co/guide/index.html


  1. 导入依赖
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.15.2</version>
</dependency>
  1. 初始化

  1. 分析类中方法

    三个静态内部类
  • RestClientBuilderConfiguration.class
  • RestHighLevelClientConfiguration.class
  • RestClientSnifferConfiguration.class
    核心类只有一个
  • ElasticsearchRestClientConfigurations
  1. api测试
@SpringBootTest
class EsApiApplicationTests {
    @Autowired
    @Qualifier("restHighLevelClient")
    private RestHighLevelClient client;

    //索引创建

    @Test
    void contextLoads() throws IOException {
        //1.创建索引的请求
        CreateIndexRequest request = new CreateIndexRequest("liuyunsan_index");
        //2.客户端执行请求 IndexClient, 请求后获得响应
        CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(createIndexResponse);
    }
    //获取索引,数据库只能判断存不存在
    @Test
    void testExist() throws IOException {
        //1.创建索引的请求
        GetIndexRequest request = new GetIndexRequest("liuyunsan_index");
        //2.判断索引是否存在
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);

    }
    //删除索引
    @Test
    void deeteExist() throws IOException {
        //1.创建索引的请求
        DeleteIndexRequest request = new DeleteIndexRequest("liuyunsan_index");
        //2.客户端执行请求 IndexClient, 请求后获得响应
        AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println(delete.isAcknowledged());
    }
    //测试添加文档
    @Test
    void insertDoc() throws IOException {
        //创建对象
        User user=new User("liuyunsan",3);
        //创建请求
        IndexRequest request = new IndexRequest("liuyunsan_index");
        //规则 put /liuyunsan_index/_doc/1
        request.id("1");
        request.timeout(TimeValue.timeValueSeconds(1));//超时
        //将我们的数据放入请求request json
        request.source(JSON.toJSONString(user), XContentType.JSON);
        //客户端发送请求 获取响应结果
        IndexResponse index = client.index(request, RequestOptions.DEFAULT);
        System.out.println(index.toString());
        System.out.println(index.status());  //对应命令返回的状态

    }
    //获取文档,判断是否存在 GET /index/_doc/1
    @Test
    void getDoc() throws IOException {
        GetRequest liuyunsan_index = new GetRequest("liuyunsan_index", "1");
        liuyunsan_index.fetchSourceContext(new FetchSourceContext(false));//不获取返回的_source的上下文
        liuyunsan_index.storedFields("_none_");//排序字段
        boolean exists = client.exists(liuyunsan_index, RequestOptions.DEFAULT);
        System.out.println(exists);

    }
    //获取文档的信息
    @Test
    void getDocInformation() throws IOException {
        GetRequest liuyunsan_index = new GetRequest("liuyunsan_index", "1");
        GetResponse documentFields = client.get(liuyunsan_index, RequestOptions.DEFAULT);
        System.out.println(documentFields.getSourceAsString());
        System.out.println(documentFields);  //返回的全部内容和命令是一样的
    }
    //更新文档记录
    @Test
    void updateDocInformation() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest("liuyunsan_index", "1");
        updateRequest.timeout("1s");

        User user = new User("一刹流云散", 77);
        UpdateRequest doc = updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);

        UpdateResponse update = client.update(doc, RequestOptions.DEFAULT);
        System.out.println(update);
    }
    //删除文档记录
    @Test
    void deleteDocInformation() throws IOException {
        DeleteRequest liuyunsan_index = new DeleteRequest("liuyunsan_index", "1");
        liuyunsan_index.timeout("1s");

        DeleteResponse delete = client.delete(liuyunsan_index, RequestOptions.DEFAULT);
        System.out.println(delete);
    }

    //真实项目一般会批量插入数据
    @Test
    void InsertDocInformation() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("3s");
        ArrayList<User> users = new ArrayList<>();
        users.add(new User("流云散1",3));
        users.add(new User("流云散2",4));
        users.add(new User("流云散3",5));
        users.add(new User("流云散4",6));
        users.add(new User("流云散5",7));
        users.add(new User("流云散6",8));
        users.add(new User("流云散7",9));

        for (int i = 0; i <users.size() ; i++) {
            //批量更新与删除就在这里修改请求即可
            //如果不设置id就会生成一个随机id
            bulkRequest.add(new IndexRequest("liuyunsan_index").id(""+(i+1)).source(JSON.toJSONString(users.get(i)),XContentType.JSON));
        }
        BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulk);
        System.out.println(bulk.hasFailures());//是否执行成功
    }
    //查询
    //SearchRequest 搜索请求
    //SearchSourceBuilder 条件构造
    // HighlightBuilder 构建高亮
    //TermQueryBuilder 精确查询
    //MatchALLQueryBuilder 全部查询
    // xxxQueryBuilder 对应kibana中的命令
    @Test
    void QueryDocInformation() throws IOException {
        SearchRequest liuyunsan_index = new SearchRequest("liuyunsan_index");
        //构建搜索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //查询条件,可以使用QueryBuilders快速构建
        //term 精确匹配
        //match 模糊匹配
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "流云散3");

        sourceBuilder.query(termQueryBuilder);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        //放入请求
        liuyunsan_index.source(sourceBuilder);

        SearchResponse search = client.search(liuyunsan_index,RequestOptions.DEFAULT);
        System.out.println(JSON.toJSONString(search.getHits()));  //从hit中取得查询到的数据

        for(SearchHit documentFields:search.getHits().getHits())
        {
            System.out.println(documentFields.getSourceAsMap());
        }
    }
}

9. 爬虫爬取数据存入ElasticSearch(模拟全文检索)

数据获取:数据库,消息队列中获取,都可以成为数据源,爬虫
爬取数据(获取请求返回的页面信息,筛选出我们想要的数据就可以了)
jsoup包

搜索相关的都可以使用ElasticSearch(最好大数据量的情况下使用)

推荐阅读