精华内容
下载资源
问答
  • 全文检索
    千次阅读
    2021-08-30 16:50:10

           今天我们来聊一下全文检索,想必做搜索相关业务朋友对这个概念不会陌生,尤其是做搜索引擎,或者类似CSDN、知乎类的社区网站,全文检索是逃不开的业务。文,即文章、文档。全文搜索就是给定关键词,在所有的文档数据中找到符合关键词的文档。不管是哪种业务模式下的全文检索功能,其实大体的实现思路类似,如下所示:

           使用文字进行描述,就是:

    (1)获取原始文档数据。

    (2)对文档进行分析,分词(所为分词,就是按照分词符,如空格,将一句话分隔成若干的单词)

    (3)存档存入数据库,并通过分词建立索引。

    (4)查询时根据关键词,通过索引查询到索引指向的数据。

           Postgresql本身就支持全文检索的功能,尤其是Postgresql10.0之后,对于全文检索的支持更加成熟。配合它的GIN索引,Postgresql的全文检索具有很高的查询性能。下面,我们来演示下Postgresql中全文索引的使用。

    一、测试数据的准备

           在继续下面的内容之前,我们还是要先创建一个测试表,用来进行后面内容的演示:

    postgres=# create table blog (id serial,recoredtime timestamp default now(),content text);
    CREATE TABLE
    postgres=# \d+ blog
                                                                 Table "public.blog"
       Column    |            Type             | Collation | Nullable |             Default              | Storage  | Stats target | Description 
    -------------+-----------------------------+-----------+----------+----------------------------------+----------+--------------+-------------
     id          | integer                     |           | not null | nextval('blog_id_seq'::regclass) | plain    |              | 
     recoredtime | timestamp without time zone |           |          | now()                            | plain    |              | 
     content     | text                        |           |          | 

           可以看到,笔者创建了一个名叫blog的测试表,用来模拟博客网站存储的博文,表中一共有3个字段:

    • id —— 为每一篇博文分配的唯一的自增长ID
    • recoredtime —— 博文提交的时间,默认是提交的当前时间
    • content —— 博文的内容

           接下来,向blog表里面插入一些测试数据:

    postgres=# COPY blog(content) FROM '/data/1.txt';
    COPY 20
    postgres=# COPY blog(content) FROM '/data/2.txt';
    COPY 12
    postgres=# COPY blog(content) FROM '/data/3.txt';
    COPY 6
    postgres=# COPY blog(content) FROM '/data/4.txt';
    COPY 22
    postgres=# COPY blog(content) FROM '/data/5.txt';
    COPY 9

           笔者通过Copy命令将位于本地的5个文本文件的内容插入到了blog表中,每个文本文件里面实际上都是一片英文的文章,但是Copy命令遇到换行符会结束然后插入新行,所以每篇文章实际插入了多行数据。

    postgres=# select count(*) from blog;
    -[ RECORD 1 ]
    count | 69

           可以看到,整个blog表一共有69行数据,而且其中不乏空行。但是为了验证在大数据量下全文检索的性能,69行数据还是远远不够的,我们可以以1.txt为源数据,重复插入:

    postgres=# do $$
    postgres$# declare
    postgres$# v_idx integer := 1;
    postgres$# begin
    postgres$#   while v_idx < 10000 loop
    postgres$#   v_idx = v_idx+1;
    postgres$#     COPY blog(content) FROM '/data/1.txt';
    postgres$#   end loop;
    postgres$# end $$;
    
    DO

           最终我们插入了200W+的数据:

    postgres=# select count(*) from blog;
     count  
    --------
     201009
    (1 row)

           接下来,我们就以这200W+行数据为数据源来介绍下Postgresql中全文检索功能的使用。

    二、Postgresql的全文检索原理

           Postgresql会对长文本进行分词,分词的标准一般是按照空格进行拆分。分词之后长文本实际上被分成了很多个key的集合,这个key的集合叫做tsvector。所有的搜索都是在tsvector中进行的。

           我们先来简单验证下,Postgresql是怎么对一个简单文本字符串进行分词的:

    postgres=# select 'I will be back'::tsvector;
            tsvector        
    ------------------------
     'I' 'back' 'be' 'will'
    (1 row)

           我们将长字符串声明成了tsvector类型,Postgresql就自动按照空格对其进行了分词,并打印出来。Postgresql中也提供了一个to_tsvector的函数,可以实现类似的功能:

    postgres=# select to_tsvector('I will be back');
     to_tsvector 
    -------------
     'back':4
    (1 row)

           先看下to_tsvector函数返回的结果,第4行‘back’是提取的关键字,冒号后面的4表示词在句子中的位置。有朋友一定会觉得奇怪,为什么前面使用tsvector分词时分出来4个词,而使用to_tsvector只分出来了back一个?实际上tssvector会自动忽略掉I、Well等等这类主语词或者谓词、虚词,这也很容易理解,因为这类词往往是量最多,但是却很少使用来进行查询的。

           我们再来看一个Postgresql官方文档中使用to_tsvector的例子:

    postgres=# SELECT to_tsvector('english', 'a fat  cat sat on a mat - it ate a fat rats');
                         to_tsvector                     
    -----------------------------------------------------
     'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4
    (1 row)

           在上面这个例子中我们看到,作为结果的tsvector不包含词a、on或it,词rats变成了rat,并且标点符号-被忽略了。

           to_tsvector函数在内部调用了一个解析器,它把文档文本分解成记号并且为每一种记号分配一个类型。对于每一个记号,会去查询一个词典列表,该列表会根据记号的类型而变化。第一个识别记号的词典产生一个或多个正规化的词位来表示该记号。例如,rats变成rat是因为一个词典识别到该词rats是rat的复数形式。一些词会被识别为停用词,这将导致它们被忽略,因为它们出现得太频繁以至于在搜索中起不到作用。在我们的例子中有a、on和it是停用词。如果在列表中没有词典能识别该记号,那它将也会被忽略。在这个例子中标点符号-就属于这种情况,因为事实上没有词典会给它分配记号类型(空间符号),即空间记号不会被索引。对于解析器、词典以及要索引哪些记号类型是由所选择的文本搜索配置决定的。可以在同一个数据库中有多种不同的配置,并且有用于很多种语言的预定义配置。在我们的例子中,我们使用用于英语的默认配置english。

           介绍完了tsvector,要完成全文检索功能,我们还需要引入另外一个类型——检索条件tsquery,tsquery是一个由简单逻辑运算符组成的字符串,如下:

    postgres=# select 'we & back'::tsquery;
        tsquery    
    ---------------
     'we' & 'back'
    (1 row)

           'we & back'的意思就是查询条件中既包含‘we’这个单词,也包括'back'这个单词。如果使用这个tsquery进行查询,就可以组成类似下面的SQL语句:

    postgres=# select 'I will be back'::tsvector @@ 'we & back'::tsquery;
     ?column? 
    ----------
     f
    (1 row)

           上面查询语句的意思,当然就是在'I will be back'这个tsvector类型中确认是不是符合既包含‘we’这个单词,也包括'back'这个单词?答案当然是否定的,所以结果为false。如果我们把tsquery中的&换成|,就会是另一种结果:

    postgres=# select 'I will be back'::tsvector @@ 'we | back'::tsquery;
     ?column? 
    ----------
     t
    (1 row)

           在'I will be back'中查找,确认其是否满足含有‘we’或者'back',因为它含有'back',索引结果就是true。

           和tsvector类似,将字符串转换成tsquery类型,Postgresql也提供了对应的to_tsquery函数:

    postgres=# select to_tsquery('we & back');
     to_tsquery 
    ------------
     'back'
    (1 row)

           从上面的结果中也可以看到,to_tsquery也忽略了we这个主语单词。

    三、在数据表中使用全文检索

           前面,我们介绍了Postgresql中实现全文检索的原理,接下来,开始在之前创建的blog表中使用全文检索。我们从上文中了解到:Postgresql实现全文检索是在tsvector类型之上的,因此要想在blog表中实现这一功能,我们还必须添加一个tsvector的列,在此列的基础之上进行全文检索。

           先添加列:

    postgres=# alter table blog add column tscontent tsvector;
    ALTER TABLE

           加完成列之后,然后将content里面的内容分词转换成tsvector类型:

    postgres=# update blog set tscontent=to_tsvector(content);
    UPDATE 69

           然后,在此基础之上,我们进行查找包含单词mother的数据行,为了方便查看性能,我们在执行计划里面去执行:

    postgres=# explain (analyze,verbose,buffers,costs,timing) select * from blog where tscontent @@ 'mother'::tsquery;
                                                                QUERY PLAN                                                             
    -----------------------------------------------------------------------------------------------------------------------------------
     Gather  (cost=1000.00..302075.58 rows=1 width=347) (actual time=18140.837..18140.878 rows=1 loops=1)
       Output: id, recoredtime, content, tscontent
       Workers Planned: 2
       Workers Launched: 2
       Buffers: shared hit=16156 read=273456
       ->  Parallel Seq Scan on public.blog  (cost=0.00..301075.48 rows=1 width=347) (actual time=14030.975..17958.129 rows=0 loops=3)
             Output: id, recoredtime, content, tscontent
             Filter: (blog.tscontent @@ '''mother'''::tsquery)
             Rows Removed by Filter: 733663
             Buffers: shared hit=16156 read=273456
             Worker 0: actual time=6087.447..17868.909 rows=1 loops=1
               Buffers: shared hit=5343 read=88589
             Worker 1: actual time=17864.982..17864.982 rows=0 loops=1
               Buffers: shared hit=5292 read=86997
     Planning Time: 1.990 ms
     Execution Time: 18144.970 ms
    (16 rows)

          从上面的执行计划信息中可以看到,整个查询采用了并行扫描全表,一共查询到了1条数据,查询实际耗时18144.970ms。为了加快查询速度,我们还可以在tscontent字段上加上GIN索引:

    postgres=# create index on blog using gin(tscontent);
    CREATE INDEX

           创建成功GIN索引之后,再次执行查询计划:

    postgres=# explain (analyze,verbose,buffers,costs,timing) select * from blog where tscontent @@ 'mother'::tsquery;
                                                             QUERY PLAN                                                         
    ----------------------------------------------------------------------------------------------------------------------------
     Bitmap Heap Scan on public.blog  (cost=28.00..32.01 rows=1 width=347) (actual time=3.176..3.179 rows=1 loops=1)
       Output: id, recoredtime, content, tscontent
       Recheck Cond: (blog.tscontent @@ '''mother'''::tsquery)
       Heap Blocks: exact=1
       Buffers: shared hit=1 read=3
       ->  Bitmap Index Scan on blog_tscontent_idx  (cost=0.00..28.00 rows=1 width=0) (actual time=0.690..0.691 rows=1 loops=1)
             Index Cond: (blog.tscontent @@ '''mother'''::tsquery)
             Buffers: shared hit=1 read=2
     Planning Time: 2.786 ms
     Execution Time: 3.238 ms
    (10 rows)

           加了索引之后,再去查询,查询过程走了索引,采用位图扫描,整个的查询过程只消耗了3.238ms,单单从数字上比较,性能提高了6000倍不止。

    四、使用tsquery的全文查询和like模糊查询的性能比较

           有的朋友可能会有疑问:如果全文搜索使用like等模糊查询方式是不是也可以实现呢?可以实现,但是如果使用like等模糊查询,主要有两个弊端:

    (1)like模糊查询要进行全表扫描,查询起来会相当吃力,性能很低;

    (2)查询结果中包含了所有mother这个字符串的数据,无法做到精确匹配。

           我们可以再次在执行计划中使用like模糊查询测试下:

    ^Cpostgres=explain (analyze,verbose,buffers,costs,timing) select * from blog where content like '%mother%';
                                                                    QUERY PLAN                                                                
    ------------------------------------------------------------------------------------------------------------------------------------------
     Gather  (cost=1000.00..313181.99 rows=111627 width=649) (actual time=6209.370..18396.139 rows=110048 loops=1)
       Output: id, recoredtime, content, tscontent
       Workers Planned: 2
       Workers Launched: 2
       Buffers: shared hit=16145 read=273467
       ->  Parallel Seq Scan on public.blog  (cost=0.00..301019.29 rows=46511 width=649) (actual time=6248.323..16996.203 rows=36683 loops=3)
             Output: id, recoredtime, content, tscontent
             Filter: (blog.content ~~ '%mother%'::text)
             Rows Removed by Filter: 696980
             Buffers: shared hit=16145 read=273467
             Worker 0: actual time=6249.688..16447.847 rows=21945 loops=1
               Buffers: shared hit=5242 read=66229
             Worker 1: actual time=6286.170..16309.549 rows=21570 loops=1
               Buffers: shared hit=5181 read=65342
     Planning Time: 0.109 ms
     Execution Time: 18484.319 ms
    (16 rows)

           因为也采用的是并行的全表扫描,所以使用like查询的耗时和使用索引前的全文检索耗时差不多,用了18484.319 ms。而且从查询结果中,我们可以看到,使用模糊查询我们查出来了110048条结果,而实际上包含mothor这个单词的数据只有一行。

    五、支持中文全文检索的zhparser

           可能细心的朋友已经发现,我们现在做的全文检索功能,是完全建立在检索英文的基础之上的。实际上,Postgresql默认的全文检索只支持英文,如果需要支持中文的全文检索,我们需要安装zhparser插件。由于篇幅有限,笔者就不再这里展开了,如果感兴趣可以自行百度或google。

    六、总结

           按照惯例,我们还是对本篇的内容进行总结:

    (1)Postgresql支持全文检索的功能,它提供了两个类型tsvector和tsquery分别表示全文检索索引的集合以及查询条件

    (2)全文检索的原理就是将长的字符串按照空格进行分词,将分词存入到类型为tsvector的集合中,tsvector中存储每个单词和其在长语句中的位置。

    (3)tsquery类型是由查询的key和&、|等逻辑运算符拼接在一起。

    (4)在某个表上进行全文检索,需要创建专门的tsvector类型的字段,而且字段上可以创建gin索引来加速查询。

    (5)Postgresql默认的全文检索只支持英文,需要需要使用支持中文的全文检索,需要安装zhparser插件。

    更多相关内容
  • 1、Lucene框架的熟练应用实现类似百度、京东商城等应用的全文检索效果; 1、Solr简介 2、Solr下载安装以及目录结构介绍 3、Solr应用部署至Tomcat服务器 4、Solr后台管理界面介绍 5、通过managed-schema配置Field...
  • SQL Server 的全文搜索(Full-Text Search)是基于分词的文本检索功能,依赖于全文索引。全文索引不同于传统的平衡树(B-Tree)索引和列存储索引,它是由数据表构成的,称作倒转索引(Invert Index),存储分词和行的...
  • JSP中的全文检索 全文检索一直都是web方面的关键技术,如何在浩如烟海的信息中找到自己想要的信息是人们最关心的。鼎鼎大名的GOOGLE就是一个很成功的例子,网络上的人们大部分都用GOOGLE来查找自己需要的内容。全文...
  • 1.使用Mysql全文检索fulltext的先决条件 表的类型必须是MyISAM建立全文检索的字段类型必须是char,varchar,text2.建立全文检索先期配置由于Mysql的默认配置是索引的词的长度是4,所以要支持中文单字的话,首先更改这个...
  • 今天一个同事问我,如何使用 Mysql 实现类似于 ElasticSearch 的全文检索功能,并且对检索关键词跑分?我当时脑子里立马产生了疑问?为啥不直接用es呢?简单好用还贼快。但是听他说,数据量不多,客户给的时间非常...
  • 基于EntityFrameworkCore和Lucene.NET实现的全文检索搜索引擎 基于EntityFrameworkCore和Lucene.NET实现的全文检索搜索引擎,可轻松实现高性能的全文检索。可以轻松应用于任何基于EntityFrameworkCore的实体框架...
  • Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单,它不仅包括了全文搜索功能,还可以进行以下...
  • Solr全文检索.pdf

    2021-09-12 15:52:09
    Solr全文检索
  • 搜索引擎使用whoosh,是一个纯python实现的全文搜索引擎,小巧简单。 中文搜索需要进行中文分词,使用jieba。 直接在django项目中使用whoosh需要关注一些基础细节问题,而通过haystack这一搜索框架,可以方便地在...
  • 一、全文检索的简介 1、全文检索的介绍 1.1、数据分类 结构化数据:格式固定、长度固定、数据类型固定,如:数据库数据。 非结构化数据:格式不固定、长度不固定、数据类型不固定,如:word文档、pdf文档、邮件、...
  • NC65全文检索配置说明

    2018-10-21 07:39:27
    文档UAP65如何配置全文检索功能进行了详细的描述,包括如何配置sysconfig,如何配置索引等相关内容
  • Lucene全文检索(一)

    2021-01-07 14:49:43
    Lucene实现全文检索的流程 创建索引 获得文档 原始文档:要基于那些数据来进行搜索,那么这些数据就是原始文档。 搜索引擎:使用爬虫获得原始文档 站内搜索:数据库中的数据。 本地搜索:直接使用io流读取磁盘上的...
  • 1.快速学习到最新版本的全文检索技术,从视频、文章、圈子、粉丝交流等快速促进学习 2.通过该技术,获得面试进阶指导 3.结交人脉(庞大的粉丝群) .. End初期学员100人,价格不会太高,也是为了帮
  • ElasticSearch实现全文检索

    千次阅读 2022-03-01 18:45:12
    它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到...

    1、技术选型

    搜索引擎服务使用 ElasticSearch

    提供的对外 web 服务选则 Springboot web

    1.1 ElasticSearch

    Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
    官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。

    现在开源的搜索引擎在市面上最常见的就是ElasticSearch和Solr,二者都是基于Lucene的实现,其中ElasticSearch相对更加重量级,在分布式环境表现也更好,二者的选则需考虑具体的业务场景和数据量级。对于数据量不大的情况下,完全需要使用像Lucene这样的搜索引擎服务,通过关系型数据库检索即可。

    1.2 Spring Boot

    现在 Spring Boot 在做 web 开发上是绝对的主流,其不仅仅是开发上的优势,在布署,运维各个方面都有着非常不错的表现,并且 Spring 生态圈的影响力太大了,可以找到各种成熟的解决方案。

    1.3 ik分词器

    ElasticSearch 本身不支持中文的分词,需要安装中文分词插件,如果需要做中文的信息检索,中文分词是基础,此处选则了ik,下载好后放入 elasticSearch 的安装位置的 plugin 目录即可。

    2、环境准备

    需要安装好elastiSearch以及kibana(可选),并且需要lk分词插件。

    1、安装elasticSearch elasticsearch官网. 笔者使用的是7.5.1。

    2、ik插件下载 ik插件github地址. 注意下载和你下载elasticsearch版本一样的ik插件。

    3、将ik插件放入elasticsearch安装目录下的plugins包下,新建报名ik,将下载好的插件解压到该目录下即可,启动es的时候会自动加载该插件。
    在这里插入图片描述

    3、项目架构

    1、获取数据使用ik分词插件

    2、将数据存储在es引擎中

    3、通过es检索方式对存储的数据进行检索

    4、使用es的java客户端提供外部服务
    在这里插入图片描述

    4、实现效果

    在这里插入图片描述

    5、具体代码实现

    5.1 全文检索的实现对象

    按照博文的基本信息定义了如下实体类,主要需要知道每一个博文的url,通过检索出来的文章具体查看要跳转到该url。

    package com.lbh.es.entity;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    
    import javax.persistence.*;
    
    /**
     * PUT articles
     * {
     * "mappings":
     * {"properties":{
     * "author":{"type":"text"},
     * "content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},
     * "title":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},
     * "createDate":{"type":"date","format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"},
     * "url":{"type":"text"}
     * } },
     * "settings":{
     *     "index":{
     *       "number_of_shards":1,
     *       "number_of_replicas":2
     *     }
     *   }
     * }
     * ---------------------------------------------------------------------------------------------------------------------
     * Copyright(c)lbhbinhao@163.com
     * @author liubinhao
     * @date 2021/3/3
     */
    @Entity
    @Table(name = "es_article")
    public class ArticleEntity {
        @Id
        @JsonIgnore
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private long id;
        @Column(name = "author")
        private String author;
        @Column(name = "content",columnDefinition="TEXT")
        private String content;
        @Column(name = "title")
        private String title;
        @Column(name = "createDate")
        private String createDate;
        @Column(name = "url")
        private String url;
    
        public String getAuthor() {
            return author;
        }
    
        public void setAuthor(String author) {
            this.author = author;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getCreateDate() {
            return createDate;
        }
    
        public void setCreateDate(String createDate) {
            this.createDate = createDate;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    }
    

    5.2 客户端配置

    通过java配置es的客户端

    /**
     * Copyright(c)lbhbinhao@163.com
     * @author liubinhao
     * @date 2021/3/3
     */
    @Configuration
    public class EsConfig {
    
        @Value("${elasticsearch.schema}")
        private String schema;
        @Value("${elasticsearch.address}")
        private String address;
        @Value("${elasticsearch.connectTimeout}")
        private int connectTimeout;
        @Value("${elasticsearch.socketTimeout}")
        private int socketTimeout;
        @Value("${elasticsearch.connectionRequestTimeout}")
        private int tryConnTimeout;
        @Value("${elasticsearch.maxConnectNum}")
        private int maxConnNum;
        @Value("${elasticsearch.maxConnectPerRoute}")
        private int maxConnectPerRoute;
    
        @Bean
        public RestHighLevelClient restHighLevelClient() {
            // 拆分地址
            List<HttpHost> hostLists = new ArrayList<>();
            String[] hostList = address.split(",");
            for (String addr : hostList) {
                String host = addr.split(":")[0];
                String port = addr.split(":")[1];
                hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));
            }
            // 转换成 HttpHost 数组
            HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
            // 构建连接对象
            RestClientBuilder builder = RestClient.builder(httpHost);
            // 异步连接延时配置
            builder.setRequestConfigCallback(requestConfigBuilder -> {
                requestConfigBuilder.setConnectTimeout(connectTimeout);
                requestConfigBuilder.setSocketTimeout(socketTimeout);
                requestConfigBuilder.setConnectionRequestTimeout(tryConnTimeout);
                return requestConfigBuilder;
            });
            // 异步连接数配置
            builder.setHttpClientConfigCallback(httpClientBuilder -> {
                httpClientBuilder.setMaxConnTotal(maxConnNum);
                httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);
                return httpClientBuilder;
            });
            return new RestHighLevelClient(builder);
        }
    
    }
    

    5.3 业务代码编写

    包括一些检索文章的信息,可以从文章标题,文章内容以及作者信息这些维度来查看相关信息。

    /**
     * Copyright(c)lbhbinhao@163.com
     * @author liubinhao
     * @date 2021/3/3
     */
    @Service
    public class ArticleService {
    
        private static final String ARTICLE_INDEX = "article";
    
        @Resource
        private RestHighLevelClient client;
        @Resource
        private ArticleRepository articleRepository;
    
        public boolean createIndexOfArticle(){
            Settings settings = Settings.builder()
                    .put("index.number_of_shards", 1)
                    .put("index.number_of_replicas", 1)
                    .build();
    // {"properties":{"author":{"type":"text"},
    // "content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"}
    // ,"title":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},
    // ,"createDate":{"type":"date","format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"}
    // }
            String mapping = "{\"properties\":{\"author\":{\"type\":\"text\"},\n" +
                    "\"content\":{\"type\":\"text\",\"analyzer\":\"ik_max_word\",\"search_analyzer\":\"ik_smart\"}\n" +
                    ",\"title\":{\"type\":\"text\",\"analyzer\":\"ik_max_word\",\"search_analyzer\":\"ik_smart\"}\n" +
                    ",\"createDate\":{\"type\":\"date\",\"format\":\"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd\"}\n" +
                    "},\"url\":{\"type\":\"text\"}\n" +
                    "}";
            CreateIndexRequest indexRequest = new CreateIndexRequest(ARTICLE_INDEX)
                    .settings(settings).mapping(mapping,XContentType.JSON);
            CreateIndexResponse response = null;
            try {
                response = client.indices().create(indexRequest, RequestOptions.DEFAULT);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (response!=null) {
                System.err.println(response.isAcknowledged() ? "success" : "default");
                return response.isAcknowledged();
            } else {
                return false;
            }
        }
    
        public boolean deleteArticle(){
            DeleteIndexRequest request = new DeleteIndexRequest(ARTICLE_INDEX);
            try {
                AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
                return response.isAcknowledged();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return false;
        }
    
        public IndexResponse addArticle(ArticleEntity article){
            Gson gson = new Gson();
            String s = gson.toJson(article);
            //创建索引创建对象
            IndexRequest indexRequest = new IndexRequest(ARTICLE_INDEX);
            //文档内容
            indexRequest.source(s,XContentType.JSON);
            //通过client进行http的请求
            IndexResponse re = null;
            try {
                re = client.index(indexRequest, RequestOptions.DEFAULT);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return re;
        }
    
        public void transferFromMysql(){
            articleRepository.findAll().forEach(this::addArticle);
        }
    
        public List<ArticleEntity> queryByKey(String keyword){
            SearchRequest request = new SearchRequest();
            /*
             * 创建  搜索内容参数设置对象:SearchSourceBuilder
             * 相对于matchQuery,multiMatchQuery针对的是多个fi eld,也就是说,当multiMatchQuery中,fieldNames参数只有一个时,其作用与matchQuery相当;
             * 而当fieldNames有多个参数时,如field1和field2,那查询的结果中,要么field1中包含text,要么field2中包含text。
             */
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    
            searchSourceBuilder.query(QueryBuilders
                    .multiMatchQuery(keyword, "author","content","title"));
            request.source(searchSourceBuilder);
            List<ArticleEntity> result = new ArrayList<>();
            try {
                SearchResponse search = client.search(request, RequestOptions.DEFAULT);
                for (SearchHit hit:search.getHits()){
                    Map<String, Object> map = hit.getSourceAsMap();
                    ArticleEntity item = new ArticleEntity();
                    item.setAuthor((String) map.get("author"));
                    item.setContent((String) map.get("content"));
                    item.setTitle((String) map.get("title"));
                    item.setUrl((String) map.get("url"));
                    result.add(item);
                }
                return result;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public ArticleEntity queryById(String indexId){
            GetRequest request = new GetRequest(ARTICLE_INDEX, indexId);
            GetResponse response = null;
            try {
                response = client.get(request, RequestOptions.DEFAULT);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (response!=null&&response.isExists()){
                Gson gson = new Gson();
                return gson.fromJson(response.getSourceAsString(),ArticleEntity.class);
            }
            return null;
        }
    }
    

    5.4 对外接口

    和使用springboot开发web程序相同。

    /**
     * Copyright(c)lbhbinhao@163.com
     * @author liubinhao
     * @date 2021/3/3
     */
    @RestController
    @RequestMapping("article")
    public class ArticleController {
    
        @Resource
        private ArticleService articleService;
    
        @GetMapping("/create")
        public boolean create(){
            return articleService.createIndexOfArticle();
        }
    
        @GetMapping("/delete")
        public boolean delete() {
            return articleService.deleteArticle();
        }
    
        @PostMapping("/add")
        public IndexResponse add(@RequestBody ArticleEntity article){
            return articleService.addArticle(article);
        }
    
        @GetMapping("/fransfer")
        public String transfer(){
            articleService.transferFromMysql();
            return "successful";
        }
    
        @GetMapping("/query")
        public List<ArticleEntity> query(String keyword){
            return articleService.queryByKey(keyword);
        }
    }
    

    5.5 页面

    此处页面使用thymeleaf,主要原因是笔者真滴不会前端,只懂一丢丢简单的h5,就随便做了一个可以展示的页面。

    搜索页面

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>YiyiDu</title>
        <!--
            input:focus设定当输入框被点击时,出现蓝色外边框
            text-indent: 11px;和padding-left: 11px;设定输入的字符的起始位置与左边框的距离
        -->
        <style>
            input:focus {
                border: 2px solid rgb(62, 88, 206);
            }
            input {
                text-indent: 11px;
                padding-left: 11px;
                font-size: 16px;
            }
    </style>
        <!--input初始状态-->
        <style class="input/css">
            .input {
                width: 33%;
                height: 45px;
                vertical-align: top;
                box-sizing: border-box;
                border: 2px solid rgb(207, 205, 205);
                border-right: 2px solid rgb(62, 88, 206);
                border-bottom-left-radius: 10px;
                border-top-left-radius: 10px;
                outline: none;
                margin: 0;
                display: inline-block;
                background: url(/static/img/camera.jpg) no-repeat 0 0;
                background-position: 565px 7px;
                background-size: 28px;
                padding-right: 49px;
                padding-top: 10px;
                padding-bottom: 10px;
                line-height: 16px;
            }
    </style>
        <!--button初始状态-->
        <style class="button/css">
            .button {
                height: 45px;
                width: 130px;
                vertical-align: middle;
                text-indent: -8px;
                padding-left: -8px;
                background-color: rgb(62, 88, 206);
                color: white;
                font-size: 18px;
                outline: none;
                border: none;
                border-bottom-right-radius: 10px;
                border-top-right-radius: 10px;
                margin: 0;
                padding: 0;
            }
    </style>
    </head>
    <body>
    <!--包含table的div-->
    <!--包含input和button的div-->
        <div style="font-size: 0px;">
            <div align="center" style="margin-top: 0px;">
                <img src="../static/img/yyd.png" th:src = "@{/static/img/yyd.png}"  alt="一亿度" width="280px" class="pic" />
            </div>
            <div align="center">
                <!--action实现跳转-->
                <form action="/home/query">
                    <input type="text" class="input" name="keyword" />
                    <input type="submit" class="button" value="一亿度下" />
                </form>
            </div>
        </div>
    </body>
    </html>
    

    搜索结果页面

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/css/bootstrap.min.css">
        <meta charset="UTF-8">
        <title>xx-manager</title>
    </head>
    <body>
    <header th:replace="search.html"></header>
    <div class="container my-2">
        <ul th:each="article : ${articles}">
            <a th:href="${article.url}"><li th:text="${article.author}+${article.content}"></li></a>
        </ul>
    </div>
    <footer th:replace="footer.html"></footer>
    </body>
    </html>
    

    整体思路解析:
    当用户输入关键词搜索时,首先使用ik分词器分词把数据存储在es客户端中,通过业务的代码来对数据进行检索,检索到了数据,通过对外接口返回给界面,向用户展示搜索的结果,就是按图开发代码。

    展开全文
  • 使用全文检索技术可以构建像百度、谷歌、京东搜索、淘宝搜索等系统和功能. 在本套课程中,我们将全面的讲解Lucene技术,从简单应用到细节使用再到底层原理都有深入讲解。尤其对Lucene底层的存储结构,搜索算法,...
  • 全文检索服务 _ ElasticSearch

    千次阅读 2021-11-10 16:18:48
    全文检索是一种非结构化数据的搜索方式。 结构化数据:指具有固定格式固定长度的数据,如数据库中的字段。 非结构化数据:指格式和长度不固定的数据,如电商网站的商品详情。 结构化数据一般存入数据库,使用...

    一、Elasticsearch介绍

    Elasticsearch是一个全文检索服务器

    1 全文检索

    全文检索是一种非结构化数据的搜索方式
    结构化数据:指具有固定格式固定长度的数据,如数据库中的字段。
    在这里插入图片描述
    非结构化数据:指格式和长度不固定的数据,如电商网站的商品详情。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    结构化数据一般存入数据库,使用sql语句即可快速查询。但由于非结构化数据的数据量大且格式不固定,我们需要采用全文检索的方式进行搜索。全文检索通过建立倒排索引加快搜索效率。

    2 倒排索引

    1. 索引:将数据中的一部分信息提取出来,重新组织成一定的数据结构,我们可以根据该结构进行快速搜索,这样的结构称之为索引。
      索引即目录,例如字典会将字的拼音提取出来做成目录,通过目录即可快速找到字的位置。
      在这里插入图片描述
      索引分为正排索引和倒排索引。
    2. 正排索引(正向索引):将文档id建立为索引,通过id快速可以快速查找数据。如数据库中的主键就会创建正排索引。
      在这里插入图片描述
    3. 倒排索引(反向索引):非结构化数据中我们往往会根据关键词查询数据。此时我们将数据中的关键词建立为索引,指向文档的 id,这样的索引称为倒排索引。
      在这里插入图片描述
      创建倒排索引流程:
      在这里插入图片描述

    3 Elasticsearch的出现

    多年前,一个刚结婚的名叫Shay的失业开发者,跟着妻子去了伦敦,他的妻子在那里学习厨师。

    Shay使用全文检索工具——lucene,给他的妻子做一个食谱搜索引擎。
    在这里插入图片描述
    Lucene的操作非常复杂,且Lucene是一个单机软件,不支持联网访问。因此 Shay基于Lucene开发了开源项目 Elasticsearch。Elasticsearch本质是一个java语言开发的web项目,我们可以通过RESTful风格的接口访问该项目内部的Lucene,从而让全文搜索变得简单。
    在这里插入图片描述
    从此以后,Elasticsearch 已经成为了 Github 上最活跃的项目之一, Elastic公司已经开始围绕Elasticsearch提供商业服务,并开发新的特性。并且Elasticsearch 将永远开源并对所有人可用。

    4 Elasticsearch应用场景

    • 2013年初,GitHub抛弃了Solr,采取Elasticsearch来做PB级的搜索。GitHub使用Elasticsearch搜 索20TB 的数据,包括13亿文件和1300亿行代码。
    • 维基百科:以Elasticsearch为基础的核心搜索架构。
    • 百度:百度目前广泛使用Elasticsearch作为文本数据分析,采集百度所有服务器上的各类指标数据
      及用户自定义数据。目前覆盖百度内部20多个业务线(包括casio、云分析、网盟、预测、文库、
      直达号、钱包、风控等),单集群最大100台机器,200个ES节点,每天导入30TB+数据
    • 新浪使用ES分析处理32亿条实时日志。
    • 阿里使用ES构建自己的日志采集和分析体系。
    • 我们可以使用ES实现全站搜索,线上商城系统的搜索,分析日志等功能。

    5 Elasticsearch对比Solr

    Solr也是基于Lucene的一款全文搜索引擎,下面是他们的对比。

    • Solr利用 Zookeeper 进行分布式管理,而 Elasticsearch自身带有分布式协调管理功能;
    • Solr支持更多格式的数据,而Elasticsearch仅支持 json文件格式;
    • Solr官方提供的功能更多,而Elasticsearch本身更注重于核心功能,高级功能多由第三方插件提供;
    • Solr在传统的搜索应用中表现好于Elasticsearch,但在处理实时搜索应用时效率明显低于Elasticsearch。

    目前Elasticsearch的市场占有率越来越高,Spring从2020年起也已经停止Spring Data Solr的维护,更多的公司使用Elasticsearch作为搜索引擎。

    6 Elasticsearch数据结构

    文档(Document):文档是可被查询的最小数据单元,一个 Document 就是一条数据。类似于关系型
    数据库中的记录的概念。

    类型(Type):具有一组共同字段的文档定义成一个类型,类似于关系型数据库中的数据表的概念。

    索引(Index):索引是多种类型文档的集合,类似于关系型数据库中的库的概念。

    域(Fied):文档由多个域组成,类似于关系型数据库中的字段的概念。

    Elasticsearch跟关系型数据库中概念的对比:
    在这里插入图片描述
    注:ES7.X之后删除了type的概念,一个索引不会代表一个库,而是代表一张表。我们这里使用ES7.12,所以目前的ES中概念对比为:
    在这里插入图片描述

    二、Elasticsearch安装

    1 安装ES服务

    1. 解压elasticsearch压缩文件
      在这里插入图片描述

    2. 修改es服务器config目录下的yml文件,加入以下配置,用于连接ES服务:
      在这里插入图片描述

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

    在这里插入图片描述

    1. 启动bin/elasticsearch.bat
      在这里插入图片描述

    2. 访问 http://127.0.0.1:9200
      5.

    2 安装kibana

    ES需要一个图形化管理软件方便我们操作,此处我们安装kibana。

    1. 解压kibana压缩文件
    2. 启动bin/kibana.bat
    3. 访问http://127.0.0.1:5601
      在这里插入图片描述

    3 安装head

    我们也可以使用head插件作为ES的图形化管理软件,head插件是使用Javascript语言开发的。在安装head插件前,需要先安装JS的运行环境nodejs,和JS项目构建工具Grunt。
    1. 安装nodejs

    1. 运行nodejs安装包
    2. 查看版本:cmd控制台输入
    node -v
    

    2. 安装Grunt

    1. 配置镜像:在cmd控制台输入
    npm config set registry https://registry.npm.taobao.org
    
    1. 下载安装:在cmd控制台输入
    npm install -g grunt-cli
    

    3. 安装head插件

    1. 解压elasticsearch-head-master.zip
    2. phantomjs-2.1.1-windows.zip文件复制
      C:\Users\Administrator\AppData\Local\Temp\phantomjs
    3. 在head插件解压路径下打开cmd控制台输入
    npm install
    
    1. 运行head插件:在解压路径下打开cmd控制台输入
    grunt server
    

    4. 访问 http://127.0.0.1:9100
    在这里插入图片描述

    三、Elasticsearch常用操作

    Elasticsearch是使用Restful风格的http请求访问的,请求参数和返回值都是Json格式的,我们可以使用kibana发送http请求操作ES

    1 索引操作

    • 创建没有结构的索引
      路径:ip地址:端口号/索引名
      注:在kibana中所有的请求都会省略ip地址:端口号,之后的路径我们省略写ip地址:端口号请求方式:PUT
    • 为索引添加结构:
      在这里插入图片描述
    POST /索引名/_mapping 
    { 
    	"properties":{ 
    		"域名1":{ 
    			"type":域的类型, 
    			"store":是否存储, 
    			"index":是否创建索引, 
    			"analyzer":分词器 
    		},
    		"域名2":{ 
    			... 
    			} 
    		} 
    	}
    
    • 创建有结构的索引
      在这里插入图片描述
    PUT /索引名 
    { 
    	"mappings":{ 
    		"properties":{ 
    			"域名1":{ 
    				"type":域的类型, 
    				"store":是否单独存储, 
    				"index":是否创建索引, 
    				"analyzer":分词器 
    			},
    			"域名2":{ 
    				... 
    			} 
    		} 
    	} 
    }
    

    域的类型:
    在这里插入图片描述
    index:该域是否创建索引。只有值设置为true,才能根据该域的关键词查询文档。
    根据关键词查询文档:

    GET /索引名/_search 
    { 
    	"query":{ 
    		"term":{ 
    			搜索字段: 关键字 
    		} 
    	} 
    }
    

    store:是否单独存储。如果设置为true,则该域能够单独查询。
    单独查询某个域:

    GET /索引名/_search 
    { 
    	"stored_fields": ["域名"] 
    }
    
    • 删除索引
    DELETE /索引名
    

    2 文档操作

    • 新增/修改文档
      id值不写时自动生成文档 id,id 和已有 id重复时修改文档
    POST /索引/_doc/[id值] 
    { 
    	"field名":field值 
    }
    

    在这里插入图片描述

    • 根据id查询查询文档
    GET /索引/_doc/id值
    

    在这里插入图片描述

    • 删除文档
    DELETE /索引/_doc/id值
    

    在这里插入图片描述

    • 根据 id 批量查询文档
    GET /索引/_mget 
    { 
    	"docs":[ 
    		{"_id":id值}, 
    		{"_id":id值} 
    	] 
    }
    

    在这里插入图片描述

    • 查询所有文档
    GET /索引/_search 
    { 
    	"query": { 
    		"match_all": {} 
    	} 
    }
    

    在这里插入图片描述

    • 修改文档部分字段
    POST /索引/_doc/[id值]/_update 
    { 
    	"doc":{ 
    		域名:} 
    }
    

    在这里插入图片描述
    注:
    ElasticSearch执行删除操作时,ES先标记文档为 deleted 状态,而不是直接物理删除。当ES存储空间不足或工作空闲时,才会执行物理删除操作。

    ElasticSearch执行修改操作时,ES 不会真的修改Document中的数据,而是标记ES中原有的文档为deleted状态,再创建一个新的文档来存储数据。

    四、分词器

    ES文档的数据拆分成一个个有完整含义的关键词,并将关键词与文档对应,这样就可以通过关键词查询文档。要想正确的分词,需要选择合适的分词器。

    1 默认分词器

    standard analyzer:Elasticsearch 默认分词器,根据空格和标点符号对英文进行分词,会进行单词的
    大小写转换。
    默认分词器是英文分词器,对中文的分词是一字一词。

    • 查看分词效果
    GET /_analyze { 
    	"text":测试语句, 
    	"analyzer":分词器 
    }
    

    在这里插入图片描述
    默认中文查不出来
    在这里插入图片描述
    查看分词效果
    在这里插入图片描述
    默认英文可以查出
    在这里插入图片描述

    2 IK分词器

    1. 概念
    IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。提供了两种分词算法:

    • ik_smart:最少切分
    • ik_max_word:最细粒度划分

    2. 安装

    • 解压elasticsearch-analysis-ik,将解压后的文件夹拷贝到 elasticsearch 的 plugins 目录下。
      ik分词器的版本要和es版本保持一致。
    • 重启es。

    3. 词典
    IK分词器根据词典进行分词,词典文件在IK分词器的 config 目录中。

    • main.dic:IK 中内置的词典。记录了 IK 统计的所有中文单词。

    • IKAnalyzer.cfg.xml :用于配置自定义词库。
      ext_dict:自定义扩展词库,是对 main.dic 文件的扩展。

      ext_stopwords:自定义停用词。

    ik的所有的 dic 词库文件,必须使用UTF-8 字符集。不建议使用记事本编辑,记事本使用的是GBK字符集。

    4. 测试分词器效果

    GET /_analyze 
    { 
    	"text":测试语句, 
    	"analyzer":ik_smart/ik_max_word 
    }
    

    在这里插入图片描述

    3 拼音分词器

    1. 概念
    拼音分词器可以将中文分成对应的全拼,全拼首字母等。
    2. 安装

    • 解压elasticsearch-analysis-pinyin,将解压后的文件夹拷贝到 elasticsearch 的 plugins 目录下。
      注:拼音分词器的版本要和es版本保持一致。
    • 重启es。

    3. 测试分词效果

    GET /_analyze 
    { 
    	"text":测试语句, 
    	"analyzer":pinyin 
    }
    

    在这里插入图片描述

    4 自定义分词器

    真实开发中我们往往需要对一段内容既进行文字分词,又进行拼音分词,此时我们需要自定义ik+pinyin 分词器。

    4.1 创建自定义分词器

    • 在创建索引时自定义分词器
    PUT /索引名 
    { 
    	"settings" : { 
    		"analysis" : { 
    			"analyzer" : { 
    				"ik_pinyin" : { //自定义分词器名 
    					"tokenizer":"ik_max_word", // 基本分词器 
    						"filter":"pinyin_filter" // 配置分词器过滤 
    					} 
    				},
    				"filter" : { // 分词器过滤时配置另一个分词器,相当于同时使用两个分词器
    					"pinyin_filter" : { 
    						"type" : "pinyin", // 另一个分词器 // 拼音分词器的配置 	
    						"keep_separate_first_letter" : false, // 是否分词每个字的首字母 
    						"keep_full_pinyin" : true, // 是否分词全拼 
    						"keep_original" : true, // 是否保留原始输入 
    						"remove_duplicated_term" : true // 是否删除重复项 
    					} 
    				} 
    			} 
    		},
    		"mappings":{ 
    			"properties":{ 
    				"域名1":{ 
    					"type":域的类型, 
    					"store":是否单独存储, 
    					"index":是否创建索引, 
    					"analyzer":分词器 
    				},
    				"域名2":{ 
    					... 
    				} 
    			} 
    		} 
    	}
    

    在这里插入图片描述
    在这里插入图片描述

    4.2 测试自定义分词器

    GET /索引/_analyze 
    { 
    	"text": "你好百战程序员", 
    	"analyzer": "ik_pinyin" 
    }
    

    在这里插入图片描述

    五、Elasticsearch搜索文档

    请求路径:/索引/_search
    请求方式:GET

    { 
    	"query":{ 
    		搜索方式:搜索参数 
    	} 
    }
    

    搜索前我们添加一些示例数据

    { 
    	"id":2,
    	"name":"美羊羊", 
    	"info":"美羊羊是羊村最漂亮的人" 
    }
    { 
    	"id":3, 
    	"name":"懒羊羊", 
    	"info":"懒羊羊的成绩不是很好" 
    }
    { 
    	"id":4, 
    	"name":"小灰灰", 
    	"info":"小灰灰的年纪比较小" 
    }
    { 
    	"id":5, 
    	"name":"沸羊羊", 
    	"info":"沸羊羊喜欢美羊羊" 
    }
    { 
    	"id":6, 
    	"name":"村长", 
    	"info":"村长德高望重" 
    }
    { 
    	"id":7, 
    	"name":"灰太狼", 
    	"info":"灰太狼是小灰灰的父亲,每次都会说我一定会回来的" 
    }
    

    在这里插入图片描述

    1 搜索方式

    • match_all:查询所有数据
    搜索参数: 
    {}
    

    在这里插入图片描述

    • match:全文检索。将查询条件分词后再进行搜索。
    搜索参数: 
    { 
    	搜索字段:搜索条件 
    }
    

    在这里插入图片描述

    • match_phrase:短语检索。搜索条件不做任何分词解析,在搜索字段对应的倒排索引中精确匹配。
    搜索参数: 
    { 
    	搜索字段:搜索条件 
    }
    

    在这里插入图片描述

    • range:范围搜索。对数字类型的字段进行范围搜索
    搜索参数: 
    { 
    	搜索字段:{ 
    		"gte":最小值, 
    		"lte":最大值 
    	} 
    }gt/lt:大于/小于 
    gte/lte:大于等于/小于等于
    

    在这里插入图片描述

    • term/terms:单词/词组搜索。搜索条件不做任何分词解析,在搜索字段对应的倒排索引中精确匹配
    term参数: 
    { 
    	搜索字段: 搜索条件 
    }
    terms参数: 
    { 
    	搜索字段: [搜索条件1,搜索条件2] 
    }
    

    在这里插入图片描述
    补充:在搜索时关键词有可能会输入错误,ES搜索提供了自动纠错功能,即ES的模糊查询。使用match方式可以实现模糊查询。模糊查询对中文的支持效果一般,我们使用英文数据测试模糊查询。

    请求体:

    { 
    	"query": { 
    		"match": { 
    			"域名": { 
    				"query": 搜索条件, 
    				"fuzziness": 最多错误字符数,不能超过2 
    			} 
    		} 
    	} 
    }
    

    在这里插入图片描述
    在这里插入图片描述

    2 复合搜索

    路径: /索引/_search
    请求方式:GET
    请求体:

    { 
    	"query": { 
    		"bool": { 
    			// 必须满足的条件
    			"must": [ 
    			搜索方式:搜索参数, 
    			搜索方式:搜索参数 
    		],
    		// 多个条件有任意一个满足即可 
    		"should": [ 
    			搜索方式:搜索参数, 
    			搜索方式:搜索参数 
    		],
    		// 必须不满足的条件 
    		"must_not":[ 
    			搜索方式:搜索参数, 
    			搜索方式:搜索参数 
    		] 
    	} 
      } 
    }
    

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    3 结果排序

    ES中默认使用相关度分数实现排序,可以通过搜索语法定制化排序。
    请求体:

    { 
    	"query": 搜索条件, 
    	"sort": [ 
    		{ 
    			"字段1":{ 
    				"order":"asc" 
    			} 
    		},
    		{ 
    			"字段2":{ 
    				"order":"desc" 
    			} 
    		} 
    	] 
    }
    

    在这里插入图片描述
    由于ES对text 类型字段数据会做分词处理,使用哪一个单词做排序都是不合理的,所以 ES中默认不允许对text 类型的字段做排序。如果需要使用字符串做结果排序,可以使用 keyword 类型的字段作为排序依据,因为 keyword 字段不做分词处理。

    4 分页查询

    请求体:

    { 
    	"query": 搜索条件, 
    	"from": 起始下标, 
    	"size": 查询记录数 
    }
    

    在这里插入图片描述

    5 高亮查询

    在进行关键字搜索时,搜索出的内容中的关键字会显示不同的颜色,称之为高亮。如:
    在这里插入图片描述
    为什么在网页中关键字会显示不同的颜色,我们通过开发者工具查看网页源码:
    在这里插入图片描述
    我们可以在关键字左右加入标签字符串,数据传入前端即可完成高亮显示,ES可以对查询出的内容中关键字部分进行标签和样式的设置。

    请求体:

    { 
    	"query":搜索条件, 
    	"highlight":{ 
    		"fields": { 
    			"高亮显示的字段名": { 
    			// 返回高亮数据的最大长度 
    			"fragment_size":100, 
    			// 返回结果最多可以包含几段不连续的文字 
    			"number_of_fragments":5 
    		} 
    	},
    	"pre_tags":["前缀"], 
    	"post_tags":["后缀"] 
       } 
    }
    

    在这里插入图片描述

    6 SQL查询

    在ES7之后,支持SQL语句查询文档:

    GET /_sql?format=txt 
    { 
    	"query": SQL语句 
    }
    

    在这里插入图片描述
    在这里插入图片描述
    开源版本的ES并不支持通过Java操作SQL进行查询,如果需要操作 SQL查询,则需要氪金(购买白金版)

    六、Elasticsearch集群

    1 概念

    在单台ES服务器上,随着一个索引内数据的增多,会产生存储、效率、安全等问题。

    此时我们可以采用ES集群,将单个索引的数据分成几份,每份数据还拥有不同的副本,分别存储在不同的物理机器上,从而可以实现高可用、容错性等。
    在这里插入图片描述
    在这里插入图片描述
    节点(node):一个节点是集群中的一台服务器,是集群的一部分。它存储数据,参与集群的索引和搜索功能。集群中有一个为主节点,主节点通过ES内部选举产生。
    集群(cluster):一组节点组织在一起称为一个集群,它们共同持有整个的数据,并一起提供索引和搜索功能。
    分片(shards):ES可以把完整的索引分成多个分片,分别存储在不同的节点上。
    副本(replicas):ES可以为每个分片创建副本,提高查询效率,保证在分片数据丢失后的恢复。

    注:分片的数量只能在索引创建时指定,索引创建后不能再更改分片数量,但可以改变副本的数量。

    为保证节点发生故障后集群的正常运行,ES不会将某个分片和它的副本存在同一台节点上。

    2 搭建集群

    1. 复制三个elasticsearch服务
      注:复制时要删除data目录。
    2. 修改每个es服务的cong/elasticsearch.yml文件
    #集群名称,保证唯一 
    cluster.name: my_elasticsearch 
    #节点名称,必须不一样 
    node.name: node1 /node2/node3
    #可以访问该节点的ip地址 
    network.host: 0.0.0.0 
    #该节点端口号 
    http.port: 9200 /9201/9202
    #集群间通信端口号 
    transport.tcp.port: 9300 /9301/9302
    #候选主节点的设备地址
    discovery.seed_hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"] 
    #候选主节点的节点名 
    cluster.initial_master_nodes: ["node1", "node2", "node3"]
    
    1. 启动各个节点服务器
      注:搭建集群时一定配置JAVA_HOME环境变量,ES7对应的JDK版本为11。

    2. 测试:访问http://localhost:9202/_cat/nodes查看是否集群搭建成功。
      在这里插入图片描述

    3. head访问集群:访问集群中的任意一个节点即可。
      在这里插入图片描述

    4. kibana访问集群:修改kibana.yml,添加如下配置

    # 该集群的所有节点 
    elasticsearch.hosts: 
    ["http://localhost:9200","http://localhost:9201","http://localhost:9202"]
    

    3 测试集群状态

    1. 在集群中创建一个索引
    PUT /product 
    { 
    	"settings": { 
    		"number_of_shards": 5, // 分片数 
    		"number_of_replicas": 1 // 每个分片的副本数 
    	},
    	"mappings": { 
    		"properties": { 
    			"id": { 
    				"type": "integer", 
    				"store": true, 
    				"index": true 
    			},
    			"productName": { 
    				"type": "text", 
    				"store": true, 
    				"index": true 
    			},
    			"productDesc": { 
    				"type": "text", 
    				"store": true, 
    				"index": true 
    			} 
    		} 
    	} 
    }
    
    1. 查看集群状态
    # 查看集群健康状态 
    GET /_cat/health?v 
    # 查看索引状态 
    GET /_cat/indices?v 
    # 查看分片状态 
    GET /_cat/shards?v
    

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    4 故障应对&水平扩容

    1. 关闭一个节点,可以发现ES集群可以自动进行故障应对。
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    2. 重新打开该节点,可以发现ES集群可以自动进行水平扩容。
      在这里插入图片描述

    3. 分片数不能改变,但是可以改变每个分片的副本数:

    PUT /索引/_settings 
    { 
    	"number_of_replicas": 副本数 
    }
    

    在这里插入图片描述
    在这里插入图片描述

    5 Linux搭建Elasticsearch

    接下载我们在CentOS8系统环境下搭建ES集群:

    5.1 准备工作

    1. 准备一台搭载有CentOS8系统的虚拟机,使用XShell连接虚拟机
    2. 关闭防火墙,方便kibana连接集群:
    #关闭防火墙: 
    systemctl stop firewalld.service 
    #禁止防火墙自启动: 
    systemctl disable firewalld.service
    
    1. 配置最大可创建文件数大小
    #打开系统文件: 
    vim /etc/sysctl.conf 
    #添加以下配置: 
    vm.max_map_count=655360 
    #配置生效: 
    sysctl -p
    
    1. 由于ES不能以root用户运行,我们需要创建一个非root用户,此处创建一个名为es的用户:
    #创建用户: 
    useradd es
    

    5.2 搭建ES集群

    1. 使用rz命令将linux版的ES上传至虚拟机
    2. 解压第一个ES节点:
    #解压:
    tar -zxvf elasticsearch-7.12.1-linux-x86_64.tar.gz 
    #重命名: 
    mv elasticsearch-7.12.1 elasticsearch1 
    #移动文件夹: 
    mv elasticsearch1 /usr/local/ 
    #es用户取得该文件夹权限: 
    chown -R es:es /usr/local/elasticsearch1
    
    1. 解压第二个ES节点:
    #解压: 
    tar -zxvf elasticsearch-7.12.1-linux-x86_64.tar.gz 
    #重命名: 
    mv elasticsearch-7.12.1 elasticsearch2 
    #移动文件夹: 
    mv elasticsearch2 /usr/local/ 
    #es用户取得该文件夹权限: 
    chown -R es:es /usr/local/elasticsearch2
    
    1. 修改两个ES节点的elasticsearch.yml文件:
    #进入节点一配置: 
    vim /usr/local/elasticsearch1/config/elasticsearch.yml 
    #集群名称,保证唯一 
    cluster.name: my_elasticsearch 
    #节点名称,必须不一样 
    node.name: node1 
    #可以访问该节点的ip地址 
    network.host: 0.0.0.0 
    #该节点服务端口号 
    http.port: 9200 
    #集群间通信端口号 
    transport.tcp.port: 9300 
    #候选主节点的设备地址 
    discovery.seed_hosts: ["127.0.0.1:9300","127.0.0.1:9301"] 
    #候选主节点的节点名 
    cluster.initial_master_nodes: ["node1", "node2"]
    
    #进入节点二配置: 
    vim /usr/local/elasticsearch2/config/elasticsearch.yml
    #集群名称,保证唯一 
    cluster.name: my_elasticsearch 
    #节点名称,必须不一样 
    node.name: node2 
    #可以访问该节点的ip地址 
    network.host: 0.0.0.0 
    #该节点服务端口号 
    http.port: 9201 
    #集群间通信端口号 
    transport.tcp.port: 9301 
    #候选主节点的设备地址 
    discovery.seed_hosts: ["127.0.0.1:9300","127.0.0.1:9301"] 
    #候选主节点的节点名 
    cluster.initial_master_nodes: ["node1", "node2"]
    
    1. 启动两个ES节点:
    #切换为es用户: 
    su es 
    #进入第一个节点: 
    cd /usr/local/elasticsearch1/bin/ 
    #后台启动第一个节点: 
    ./elasticsearch -d 
    #进入第二个节点: 
    cd /usr/local/elasticsearch2/bin/ 
    #后台启动第二个节点: 
    ./elasticsearch -d
    

    5.3 连接ES集群

    1. 测试集群:
    http://虚拟机IP:9200/_cat/nodes
    
    1. kibana链接集群:修改kibana.yml,添加如下配置
    elasticsearch.hosts:  ["http://虚拟机IP:9200","http://虚拟机IP:9201"]
    
    1. 启动kibana

    七、原生JAVA操作ES

    原生JAVA可以对ES的索引和文档进行操作,但操作较复杂,我们了解即可。

    1 搭建项目

    1. 创建maven项目
    2. maven项目引入以下依赖:
    <dependencies> 
    	<dependency> 
    		<groupId>org.elasticsearch</groupId> 
    		<artifactId>elasticsearch</artifactId> 
    		<version>7.12.1</version> 
    	</dependency> 
    	<dependency> 
    		<groupId>org.elasticsearch.client</groupId> 
    		<artifactId>elasticsearch-rest-high-level-client</artifactId> 
    		<version>7.12.1</version> 
    	</dependency> 
    	<dependency> 
    		<groupId>junit</groupId> 
    		<artifactId>junit</artifactId> 
    		<version>4.12</version> 
    	</dependency> 
    </dependencies>
    

    2 索引操作

    1. 创建空索引
    // 创建空索引
        @Test
        public void createIndex() throws IOException {
            // 创建客户端对象,链接ES
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(new HttpHost("192.168.1.58",9200,"http")));
    
            // 创建请求对象
            CreateIndexRequest request = new CreateIndexRequest("student");
            request.settings(Settings.builder()
                            .put("index.number_of_shards",5)
                            .put("index.number_of_replicas",1)
            );
    
            // 发送请求
            CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
    
            // 输出返回结果
            System.out.println(response.index());
    
            // 关闭客户端
            client.close();
        }
    

    在这里插入图片描述
    2. 给索引添加结构

     @Test
        public void mappingIndex() throws IOException {
            // 创建客户端对象,链接ES
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(new HttpHost("192.168.1.58",9200,"http")));
    
            // 创建请求对象
            PutMappingRequest request = new PutMappingRequest("student");
            request.source("{\"properties\": {" +
                    "      \"id\": {" +
                    "        \"type\": \"integer\"," +
                    "        \"store\": true," +
                    "        \"index\": true" +
                    "      }," +
                    "      \"name\": {" +
                    "        \"type\": \"text\"," +
                    "        \"store\": true," +
                    "        \"index\": true" +
                    "      }," +
                    "      \"info\": {" +
                    "        \"type\": \"text\"," +
                    "        \"store\": true," +
                    "        \"index\": true" +
                    "      }" +
                    "    }}", XContentType.JSON);
    
            // 发送请求
            AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT);
    
            // 输出返回结果
            System.out.println(response.isAcknowledged());
    
            // 关闭客户端
            client.close();
        }
    

    在这里插入图片描述
    3. 删除索引

     @Test
        public void deleteIndex() throws IOException {
            // 创建客户端对象,链接ES
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(new HttpHost("192.168.1.58",9200,"http")));
    
            // 创建请求对象
            DeleteIndexRequest request = new DeleteIndexRequest("student");
    
            // 发送请求
            AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
    
            // 输出返回结果
            System.out.println(response.isAcknowledged());
    
            // 关闭客户端
            client.close();
        }
    

    3 文档操作

    1. 新增&修改文档
     // 新增/修改文档
        @Test
        public void addDocument() throws IOException {
            // 创建客户端对象,链接ES
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(new HttpHost("192.168.1.58",9200,"http")));
    
            // 创建请求对象
            IndexRequest request = new IndexRequest("student").id("2");
            request.source(XContentFactory.jsonBuilder()
                    .startObject()
                    .field("id",2)
                    .field("name","cat1")
                    .field("info","cat1 is a good girl")
                    .endObject());
    
            // 发送请求
            IndexResponse response = client.index(request, RequestOptions.DEFAULT);
    
            // 输出返回结果
            System.out.println(response.status());
    
            // 关闭客户端
            client.close();
        }
    
    1. 根据id查询文档
     // 根据id查询文档
        @Test
        public void findById() throws IOException {
            // 创建客户端对象,链接ES
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(new HttpHost("192.168.1.58",9200,"http")));
    
            // 创建请求对象
            GetRequest request = new GetRequest("student", "2");
    
            // 发送请求
            GetResponse response = client.get(request, RequestOptions.DEFAULT);
    
            // 输出返回结果
            System.out.println(response.getSourceAsString());
    
            // 关闭客户端
            client.close();
        }
    
    1. 删除文档
    // 删除文档
        @Test
        public void deleteDocument() throws IOException {
            // 创建客户端对象,链接ES
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(new HttpHost("192.168.1.58",9200,"http")));
    
            // 创建请求对象
            DeleteRequest request = new DeleteRequest("student", "2");
    
            // 发送请求
            DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
    
            // 输出返回结果
            System.out.println(response.status());
    
            // 关闭客户端
            client.close();
        }
    

    4 查询操作

    1. 查询所有文档
    // 查询所有文档
        @Test
        public void queryAllDocument() throws IOException {
            // 创建客户端对象,链接ES
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(new HttpHost("192.168.1.58",9200,"http")));
    
            // 创建查询条件的请求体
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    
            // 创建请求对象
            SearchRequest request = new SearchRequest("student").source(searchSourceBuilder);
    
            // 发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    
            // 输出返回结果
            for (SearchHit hit:response.getHits()) {
                System.out.println(hit.getSourceAsString());
            }
    
            // 关闭客户端
            client.close();
        }
    
    1. 根据关键词查询文档
     // 根据关键词查询文档
        @Test
        public void queryTermDocument() throws IOException {
            // 创建客户端对象,链接ES
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(new HttpHost("192.168.1.58",9200,"http")));
    
            // 创建查询条件的请求体
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(QueryBuilders.termQuery("info","boy"));
    
            // 创建请求对象
            SearchRequest request = new SearchRequest("student").source(searchSourceBuilder);
    
            // 发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    
            // 输出返回结果
            for (SearchHit hit:response.getHits()) {
                System.out.println(hit.getSourceAsString());
            }
    
            // 关闭客户端
            client.close();
        }
    

    八、Spring Data Elasticsearch

    Spring Data ElasticSearch是Spring对原生JAVA操作Elasticsearch封装之后的产物。它通过对原生API的封装,使得JAVA程序员可以简单的对Elasticsearch进行操作。

    1 快速入门

    1.1 linux环境配置分词器

    不要使用root用户进行以下操作

    1. 关闭es服务
    #查看es进程号 ps -ef | grep elastic 
    #关闭es进程 
    kill -9 进程号
    
    1. 使用 rz 命令将 ik 和 pinyin 分词器的压缩文件上传至 es 安装路径下的 plugins 目录。
    2. 解压分词器
    unzip elasticsearch-analysis-ik-7.12.1.zip -d analysis-ik-7.12.1 
    unzip elasticsearch-analysis-pinyin-7.12.1.zip -d analysis-pinyin-7.12.1
    
    1. 删除压缩文件
    rm -rf elasticsearch-analysis-ik-7.12.1.zip 
    rm -rf elasticsearch-analysis-pinyin-7.12.1.zip
    
    1. 启动es服务

    1.2 搭建项目

    创建SpringBoot项目,加入Spring Data Elasticsearch起步依赖:

    <dependency> 
    	<groupId>org.springframework.boot</groupId> 
    	<artifactId>spring-boot-starter-data-elasticsearch</artifactId> 
    </dependency>
    

    如果idea版本较低,还需补充以下依赖:

    <dependency> 
    	<groupId>org.junit.platform</groupId> 
    	<artifactId>junit-platform-launcher</artifactId> 
    	<scope>test</scope> 
    </dependency>
    

    写配置文件:

    spring: 
    	elasticsearch: 
    		rest: 
    			uris: http://192.168.1.58:9200, http://192.168.1.58:9201
    

    1.3 创建实体类

    一个实体类的所有对象都会存入ES的一个索引中,所以我们在创建实体类时关联ES索引。如果ES中没有该索引则会自动建索引。

    package com.baizhan.springdataes.product;
    
    import org.springframework.data.annotation.Id;
    import org.springframework.data.elasticsearch.annotations.Document;
    import org.springframework.data.elasticsearch.annotations.Field;
    import org.springframework.data.elasticsearch.annotations.FieldType;
    
    @Document(indexName = "product",shards = 3,replicas = 1,createIndex = true)
    public class Product {
        @Id
        @Field(type = FieldType.Integer,store = true,index = true)
        private Integer id;
        @Field(type = FieldType.Text,store = true,index = true,analyzer = "ik_smart",searchAnalyzer = "ik_smart")
        private String productName;
        @Field(type = FieldType.Text,store = true,index = true,analyzer = "ik_smart",searchAnalyzer = "ik_smart")
        private String productDesc;
    
        public Product() {
        }
    
        public Product(Integer id, String productName, String productDesc) {
            this.id = id;
            this.productName = productName;
            this.productDesc = productDesc;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getProductName() {
            return productName;
        }
    
        public void setProductName(String productName) {
            this.productName = productName;
        }
    
        public String getProductDesc() {
            return productDesc;
        }
    
        public void setProductDesc(String productDesc) {
            this.productDesc = productDesc;
        }
    
        @Override
        public String toString() {
            return "Product{" +
                    "id=" + id +
                    ", productName='" + productName + '\'' +
                    ", productDesc='" + productDesc + '\'' +
                    '}';
        }
    }
    
    @Document:标记在类上,标记实体类为文档对象,一般有如下属性:
    indexName:对应索引的名称
    shards:分片数量
    replicas:副本数量
    createIndex:是否自动创建索引
    
    @Id:标记在成员变量上,标记一个字段为主键,该字段的值会同步到ES该文档的id值。
    
    @Field:标记在成员变量上,标记为文档中的域,一般有如下属性:
    type:域的类型
    index:是否索引,默认是 true
    store:是否单独存储,默认是 false
    analyzer:分词器
    searchAnalyzer:搜索时的分词器
    

    1.4 创建Repository接口

    创建Repository接口继承ElasticsearchRepository,该接口提供了文档的增删改查方法

    public interface ProductRepository extends ElasticsearchRepository<Product,Integer> {
        @Query("{" +
                "    \"match\": {" +
                "      \"productDesc\": \"?0\"" +
                "    }" +
                "  }")
        List<Product> findByProductDescMatch(String keyword);
    
        @Query("{" +
                " \"match\": {" +
                "   \"productDesc\": {" +
                "     \"query\": \"?0\"," +
                "     \"fuzziness\": 1" +
                "   }" +
                " }" +
                "}")
        List<Product> findByProductDescFuzzy(String keyword);
    
        List<Product> findByProductName(String productName);
        List<Product> findByProductNameOrProductDesc(String productName,String productDesc);
        List<Product> findByIdBetween(Integer startId,Integer endId);
    
        Page<Product> findByProductDesc(String productDesc, Pageable pageable);
    }
    

    1.5 测试方法

    编写测试类,注入Repository接口并测试Repository接口的增删改查方法

    @SpringBootTest
    class SpringdataesApplicationTests {
        @Autowired
        private ProductRepository repository;
    
        @Test
        public void addDocument() {
            Product product = new Product(1, "iphone12", "iphone12是苹果最新款手机!");
            repository.save(product);
        }
    
        @Test
        public void addDocument1() {
            // 添加一些数据
            repository.save(new Product(2, "三体1", "三体1是优秀的科幻小说"));
            repository.save(new Product(3, "三体2", "三体2是优秀的科幻小说"));
            repository.save(new Product(4, "三体3", "三体3是优秀的科幻小说"));
            repository.save(new Product(5, "elasticsearch", "elasticsearch是基于lucene开发的优秀的搜索引擎"));
        }
    
        @Test
        public void updateDocument() {
            Product product = new Product(1, "iphone13", "iphone13是苹果最新款手机!");
            repository.save(product);
        }
    
    
    
        @Test
        public void deleteDocument1() {
            repository.deleteById(1);
        }
    
        @Test
        public void findDocument1() {
            Optional<Product> product = repository.findById(1);
            System.out.println(product.get());
        }
    
        @Test
        public void findDocument() {
            Iterable<Product> all = repository.findAll();
            for (Product product : all) {
                System.out.println(product);
            }
        }
    }
    

    2 SpringDataES查询方式

        @Test
        public void addDocument1() {
            // 添加一些数据
            repository.save(new Product(2, "三体1", "三体1是优秀的科幻小说"));
            repository.save(new Product(3, "三体2", "三体2是优秀的科幻小说"));
            repository.save(new Product(4, "三体3", "三体3是优秀的科幻小说"));
            repository.save(new Product(5, "elasticsearch", "elasticsearch是基于lucene开发的优秀的搜索引擎"));
        }
    

    2.1 使用Repository继承的方法查询文档

    2.2 使用DSL语句查询文档

    ES通过json类型的请求体查询文档,方法如下:

    GET /索引/_search 
    { 
    	"query":{ 
    		搜索方式:搜索参数 
    	} 
    }
    

    在这里插入图片描述
    query后的 json对象称为DSL语句,我们可以在接口方法上使用@Query注解自定义DSL语句查询。

       @Query("{" +
                "    \"match\": {" +
                "      \"productDesc\": \"?0\"" +
                "    }" +
                "  }")
        List<Product> findByProductDescMatch(String keyword);
    
        @Query("{" +
                " \"match\": {" +
                "   \"productDesc\": {" +
                "     \"query\": \"?0\"," +
                "     \"fuzziness\": 1" +
                "   }" +
                " }" +
                "}")
        List<Product> findByProductDescFuzzy(String keyword);
    

    2.3 按照规则命名方法进行查询

    • 只需在Repository接口中按照SpringDataES的规则命名方法,该方法就能完成相应的查询。
    • 规则:查询方法以findBy开头,涉及查询条件时,条件的属性用条件关键字连接。
      在这里插入图片描述
     List<Product> findByProductName(String productName);
     List<Product> findByProductNameOrProductDesc(String productName,String productDesc);
     List<Product> findByIdBetween(Integer startId,Integer endId);
    

    测试

        @Autowired
        private ProductRepository repository;
    
        @Test
        public void t1() {
            List<Product> list = repository.findByProductDescMatch("我喜欢用苹果手机写elasticsearch代码");
            System.out.println(list);
        }
    
        @Test
        public void t2() {
            System.out.println(repository.findByProductDescFuzzy("elasticsearc"));
        }
    
        @Test
        public void t3() {
            System.out.println(repository.findByProductName("elasticsearch"));
        }
    
    
        @Test
        public void t4() {
            System.out.println(repository.findByProductNameOrProductDesc("elasticsearch","手机"));
        }
    
        @Test
        public void t5() {
            System.out.println(repository.findByIdBetween(1,3));
        }
    

    2.4 分页查询

    使用继承或自定义的方法时,在方法中添加 Pageable类型的参数,返回值为Page类型即可进行分页查询。

        @Test
        public void t6() {
    	    // 测试继承的方法: 
    	    // 参数1:页数 参数2:每页条数
            Pageable pageable = PageRequest.of(0,3);
            Page<Product> page = repository.findAll(pageable);
            System.out.println(page.getContent());
            System.out.println(page.getTotalElements());
            System.out.println(page.getTotalPages());
        }
    
        @Test
        public void t7() {
        	// 自定义方法
            Pageable pageable = PageRequest.of(1,3);
            // 测试自定义方法
            Page<Product> page = repository.findByProductDesc("优秀",pageable);
            System.out.println(page.getContent());
            System.out.println(page.getTotalElements());
            System.out.println(page.getTotalPages());
        }
    

    2.5 结果排序

    使用继承或自定义的方法时,在方法中添加Sort类型的参数即可进行结果排序。

        @Test
        public void t8(){
        	// 结果排序
            Sort sort = Sort.by(Sort.Direction.DESC,"id");
            Iterable<Product> all = repository.findAll(sort);
            for (Product product : all) {
                System.out.println(product);
            }
        }
    
        @Test
        public void t9(){
       		 // 测试分页加排序
            Sort sort = Sort.by(Sort.Direction.DESC,"id");
            Pageable pageable = PageRequest.of(0,3,sort);
            Page<Product> page = repository.findByProductDesc("优秀", pageable);
            System.out.println(page.getContent());
            System.out.println(page.getTotalElements());
            System.out.println(page.getTotalPages());
        }

    2.6 ElasticsearchRestTemplate

    SpringDataElasticsearch提供了一个工具类ElasticsearchRestTemplate,我们注入该类对象也能对ES
    进行操作。

    2.6.1 操作索引

        @Autowired
        private ElasticsearchRestTemplate template;
    
        @Test
        public void t1(){
        	// 获得操作索引对象
            IndexOperations indexOperations = template.indexOps(Student.class);
            // 创建索引,注:该方法无法设置索引结构,框架遗留bug,不推荐使用。
            indexOperations.create();
        }
    
        @Test
        public void t2(){
            IndexOperations indexOperations = template.indexOps(Student.class);
            // 删除索引
            indexOperations.delete();
        }
    

    2.6.2 增删改文档

    template操作文档的常用方法:

    • save():新增/修改文档
    • delete():删除文档
    @Test 
    public void testDocument() { 
    	Product product = new Product(1004, "Elasticsearch是一个实时的分布式搜索和分析引 擎", "它底层封装了Lucene框架,可以提供分布式全文检索服 务。"); 
    	template.save(product); 
    	template.delete("1004",Product.class); 
    }
    

    2.6.3 查询文档

    template 的 search方法可以查询文档:

    SearchHits<T> search(Query query, Class<T> clazz):查询文档,query是查询条件对象, clazz是结果类型。
    
    1. 普通查询:
        @Autowired
        private ElasticsearchRestTemplate template;
    
        @Test
        public void t1(){
            // 构建查询条件
    //        MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
    //        TermQueryBuilder queryBuilder = QueryBuilders.termQuery("productDesc", "手机");
            MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("productDesc", "我喜欢看科幻小说");
            NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(queryBuilder).build();
    
            // 查询
            SearchHits<Product> search = template.search(query, Product.class);
    
            // 打印查询结果
            for (SearchHit<Product> productSearchHit : search) {
                Product product = productSearchHit.getContent();
                System.out.println(product);
            }
        }
    
    1. 复杂条件查询
        @Test
        public void t2(){
            String productName = "elasticsearch";
            String productDesc = "优秀";
    
            // 构建查询条件
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            if(productName == null && productDesc == null){
                MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
                boolQueryBuilder.must(queryBuilder);
            }else{
                if(productName != null){
                    MatchQueryBuilder queryBuilder1 = QueryBuilders.matchQuery("productName", productName);
                    boolQueryBuilder.must(queryBuilder1);
                }
                if(productDesc != null){
                    MatchQueryBuilder queryBuilder1 = QueryBuilders.matchQuery("productDesc", productDesc);
                    boolQueryBuilder.must(queryBuilder1);
                }
            }
    
            NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).build();
    
            // 查询
            SearchHits<Product> search = template.search(query, Product.class);
    
            // 打印查询结果
            for (SearchHit<Product> productSearchHit : search) {
                Product product = productSearchHit.getContent();
                System.out.println(product);
            }
        }
    
    1. 分页查询
        @Test
        public void t3(){
            // 构建查询条件
            MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
            // 分页条件
            Pageable pageable = PageRequest.of(0,3);
    
            NativeSearchQuery query = new NativeSearchQueryBuilder()
                    .withQuery(queryBuilder)
                    .withPageable(pageable)
                    .build();
    
            // 查询
            SearchHits<Product> search = template.search(query, Product.class);
            // 封装为page对象
            List<Product> content = new ArrayList();
            for (SearchHit<Product> productSearchHit : search) {
                content.add(productSearchHit.getContent());
            }
            Page<Product> page = new PageImpl(content,pageable,search.getTotalHits());
    
            // 打印分页对象
            System.out.println(page.getTotalElements());
            System.out.println(page.getTotalPages());
            System.out.println(page.getContent());
        }
    
    
    1. 结果排序
        @Test
        public void t4(){
            // 构建查询条件
            MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
            // 分页条件
            Pageable pageable = PageRequest.of(0,3);
            // 排序条件
            SortBuilder sortBuilder = SortBuilders.fieldSort("id").order(SortOrder.DESC);
    
            NativeSearchQuery query = new NativeSearchQueryBuilder()
                    .withQuery(queryBuilder)
                    .withPageable(pageable)
                    .withSort(sortBuilder)
                    .build();
    
            // 查询
            SearchHits<Product> search = template.search(query, Product.class);
            // 封装为page对象
            List<Product> content = new ArrayList();
            for (SearchHit<Product> productSearchHit : search) {
                content.add(productSearchHit.getContent());
            }
            Page<Product> page = new PageImpl(content,pageable,search.getTotalHits());
    
            // 打印分页对象
            System.out.println(page.getTotalElements());
            System.out.println(page.getTotalPages());
            System.out.println(page.getContent());
        }
    

    九、Elasticsearch优化

    ES的优化即通过调整参数使得读写性能更快

    1 磁盘选择

    磁盘通常是服务器的瓶颈。Elasticsearch重度使用磁盘,磁盘的效率越高,Elasticsearch的执行效率就越高。这里有一些优化磁盘的技巧:

    • 使用SSD(固态硬盘),它比机械磁盘优秀多了。
    • 使用RAID0模式(将连续的数据分散到多个硬盘存储,这样可以并行进行IO操作),代价是一块硬盘发生故障就会引发系统故障。
    • 不要使用远程挂载的存储。

    2 分片策略

    分片和副本数并不是越多越好。每个分片的底层都是一个Lucene索引,会消耗一定的系统资源。且搜索请求需要命中索引中的所有分片,分片数过多会降低搜索性能。索引的分片数需要架构师和技术人员对业务的增长有预先的判断,一般来说我们遵循以下原则:

    • 每个分片占用的硬盘容量不超过ES的最大JVM的堆空间设置(一般设置不超过32G)。比如:如果索引的总容量在500G左右,那分片数量在16个左右即可。
    • 分片数一般不超过节点数的3倍。比如:如果集群内有10个节点,则分片数不超过30个。
    • 推迟分片分配:节点中断后集群会重新分配分片。但默认集群会等待一分钟来查看节点是否重新加入。我们可以设置等待的时长,减少重新分配的次数:
    PUT /索引/_settings 
    { 
    	"settings":{ 
    		"index.unassianed.node_left.delayed_timeout":"5m" 
    	} 
    }
    
    • 减少副本数量:进行写入操作时,需要把写入的数据都同步到副本,副本越多写入的效率就越慢。我们进行大批量进行写入操作时可以先设置副本数为0,写入完成后再修改回正常的状态。

    3 内存设置

    ES默认占用内存是4GB,我们可以修改config/jvm.option设置ES的堆内存大小,Xms表示堆内存的初始大小,Xmx表示可分配的最大内存。

    • Xmx和Xms的大小设置为相同的,可以减轻伸缩堆大小带来的压力。
    • Xmx和Xms不要超过物理内存的50%,因为ES内部的Lucene也要占据一部分物理内存。
    • Xmx和Xms不要超过 32GB,由于Java语言的特性,堆内存超过32G会浪费大量系统资源,所以在内存足够的情况下,最终我们都会采用设置为31G:
    -Xms 31g 
    -Xmx 31g
    

    例如:在一台128GB内存的机器中,我们可以创建两个节点,每个节点分配31GB内存。

    十、Elasticsearch案例

    1 需求说明

    接下来我们使用ES模仿百度搜索,即自动补全+搜索引擎效果:
    在这里插入图片描述

    2 自动补全

    es为我们提供了关键词的自动补全功能:

    GET /索引/_search 
    { 
    	"suggest": { 
    		"prefix_suggestion": {// 自定义推荐名 
    			"prefix": "elastic",// 被补全的关键字 
    			"completion": { 
    				"field": "productName",// 查询的域 
    				"skip_duplicates": true, // 忽略重复结果 
    				"size": 10 //最多查询到的结果数 
    			} 
    		} 
    	} 
    }
    

    注:自动补全对性能要求极高,ES不是通过倒排索引来实现的,所以需要将对应的查询字段类型设置completion

    PUT /product1 
    { 
    	"mappings":{ 
    		"properties":{ 
    			"id":{
    				"type":"integer", 
    				"store":true, 
    				"index":true 
    			},
    			"productName":{ 
    				"type":"completion" 
    			},
    			"productDesc":{ 
    				"type":"text", 
    				"store":true, 
    				"index":true 
    			} 
    		} 
    	} 
    }
    POST /product1/_doc 
    { 
    	"id":1, 
    	"productName":"elasticsearch1", 
    	"productDesc":"elasticsearch1 is a good search engine" 
    }
    { 
    	"id":2, 
    	"productName":"elasticsearch2", 
    	"productDesc":"elasticsearch2 is a good search engine" 
    }
    { 
    	"id":3, 
    	"productName":"elasticsearch3", 
    	"productDesc":"elasticsearch3 is a good search engine" 
    }
    

    在这里插入图片描述

    3 创建索引

    PUT /news 
    { 
    	"settings": { 
    		"number_of_shards": 3, 
    		"number_of_replicas": 1, 
    		"analysis": { 
    			"analyzer": { 
    				"ik_pinyin": { 
    					"tokenizer": "ik_smart", 
    					"filter": "pinyin_filter" 
    				},
    				"tag_pinyin": { 
    					"tokenizer": "keyword", 
    					"filter": "pinyin_filter" 
    				}
    			},
    			"filter": { 
    			"pinyin_filter": { 
    				"type": "pinyin", 
    				"keep_joined_full_pinyin": true, 
    				"keep_original": true, 
    				"remove_duplicated_term": true 
    			} 
    		} 
    	} 
    },
    "mappings": { 
    	"properties": { 
    		"id": { 
    			"type": "integer", 
    			"index": true 
    		},
    		"title": { 
    			"type": "text", 
    			"index": true, 
    			"analyzer": "ik_pinyin", 
    			"search_analyzer": "ik_smart" 
    		},
    		"content": { 
    			"type": "text", 
    			"index": true, 
    			"analyzer": "ik_pinyin", 
    			"search_analyzer": "ik_smart" 
    		},
    		"url": { 
    			"type": "keyword", 
    			"index": true 
    		},
    		"tags": { 
    			"type": "completion", 
    			"analyzer": "tag_pinyin", 
    			"search_analyzer": "tag_pinyin" 
    	   	} 
      	 } 
      }
    }
    

    4 将mysql表数据复制到索引中

    使用logstash工具可以将mysql数据复制到es中

    1. 解压logstash-7.12.1-windows-x86_64.zip
      logstash要和elastisearch版本一致
    2. 在解压路径下的/config中创建 mysql.conf 文件,文件写入以下脚本内容:
    input { 
    	jdbc {
    		jdbc_driver_library => "E:\logstash-7.12.1\lib\mysql-connector-java- 5.1.37-bin.jar" 
    		jdbc_driver_class => "com.mysql.jdbc.Driver" 
    		jdbc_connection_string => "jdbc:mysql:///news" 
    		jdbc_user => "root" 
    		jdbc_password => "root" 
    		schedule => "* * * * *" 
    		jdbc_default_timezone => "Asia/Shanghai" 
    		statement => "SELECT * FROM news;" 
    	} 
    }
    filter { 
    	mutate { 
    		split => {"tags" => ","} 
    	} 
    }
    output { 
    	elasticsearch { 
    		hosts => ["192.168.1.58:9200","192.168.1.58:9201"] 
    		index => "news" 
    		document_id => "%{id}" 
    	} 
    }
    
    1. 在解压路径下打开cmd黑窗口,运行命令:
    bin\logstash -f config\mysql.conf
    
    1. 测试自动补齐
    GET /news/_search 
    { 
    	"suggest": { 
    		"my_suggest": { 
    			"prefix": "li", 
    			"completion": { 
    				"field": "tags", 
    				"skip_duplicates": true, 
    				"size": 10 
    			} 
    		} 
    	} 
    }
    

    在这里插入图片描述

    5 后端实现

    5.1 项目搭建

    创建springboot项目,加入SpringDataElasticsearch和SpringMVC的起步依赖

    <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>
    

    写配置文件:

    spring: 
    	elasticsearch: 
    		rest: 
    			uris: http://192.168.1.58:9200, http://192.168.1.58:9201
    

    5.2 创建实体类和Repository接口

    实体类:

    
    @Document(indexName = "news")
    public class News {
        @Id
        @Field
        private Integer id;
        @Field
        private String title;
        @Field
        private String content;
        @Field
        private String url;
        @CompletionField
        @Transient
        private Completion tags;
    
        public News() {
        }
    
        public News(Integer id, String title, String content, String url, Completion tags) {
            this.id = id;
            this.title = title;
            this.content = content;
            this.url = url;
            this.tags = tags;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public Completion getTags() {
            return tags;
        }
    
        public void setTags(Completion tags) {
            this.tags = tags;
        }
    
        @Override
        public String toString() {
            return "News{" +
                    "id=" + id +
                    ", title='" + title + '\'' +
                    ", content='" + content + '\'' +
                    ", url='" + url + '\'' +
                    ", tags=" + tags +
                    '}';
        }
    }
    

    Repository接口:

    public interface NewsRepository extends ElasticsearchRepository<News, Integer> {
       
    }
    

    5.3 创建service类

    创建service类,提供自动补齐和搜索关键字功能:
    注入repository 和 template类:

    @Service
    public class NewsService {
        @Autowired
        private ElasticsearchRestTemplate template;
        @Autowired
        private NewsRepository repository;
    }
    

    自动补齐:

      // 自动补齐
        public List<String> autoSuggest(String keyword) {
            // 创建请求
            SuggestBuilder suggestBuilder = new SuggestBuilder();
            // 请求体
            SuggestionBuilder suggestionBuilder = SuggestBuilders
                    .completionSuggestion("tags")
                    .prefix(keyword)
                    .skipDuplicates(true)
                    .size(10);
            suggestBuilder.addSuggestion("prefix_suggestion", suggestionBuilder);
    
            // 发送请求
            SearchResponse response = template.suggest(suggestBuilder, IndexCoordinates.of("news"));
    
            // 处理结果
            List<String> result = response
                    .getSuggest()
                    .getSuggestion("prefix_suggestion")
                    .getEntries()
                    .get(0)
                    .getOptions()
                    .stream()
                    .map(Suggest.Suggestion.Entry.Option::getText)
                    .map(Text::toString)
                    .collect(Collectors.toList());
    
            return result;
        }
    

    搜索关键字(带有高亮显示):
    在repository接口中添加方法:

    public interface NewsRepository extends ElasticsearchRepository<News, Integer> {
        @Highlight(fields = {@HighlightField(name = "title"), @HighlightField(name = "content")})
        List<SearchHit<News>> findByTitleMatchesOrContentMatches(String title, String content);
    }
    

    service类中调用该方法:

        public List<News> highLightSearch(String keyword) {
            List<SearchHit<News>> result = repository.findByTitleMatchesOrContentMatches(keyword, keyword);
            List<News> newsList = new ArrayList();
            for (SearchHit<News> newsSearchHit : result) {
                News news = newsSearchHit.getContent();
                if (newsSearchHit.getHighlightFields().get("title") != null) {
                    news.setTitle(newsSearchHit.getHighlightFields().get("title").get(0));
                }
                if (newsSearchHit.getHighlightFields().get("content") != null) {
                    news.setContent(newsSearchHit.getHighlightFields().get("content").get(0));
                }
                newsList.add(news);
            }
            return newsList;
        }
    

    5.4 创建Controller类

    @RestController
    public class NewController {
        @Autowired
        private NewsService newsService;
    
        @GetMapping("/autoSuggest")
        public List<String> autoSuggest(String term) {
            return newsService.autoSuggest(term);
        }
    
        @GetMapping("/highLightSearch")
        public List<News> highLightSearch(String term) {
            return newsService.highLightSearch(term);
        }
    }
    

    6 前端页面

    我们使用jqueryUI 中的 autocomplete插件完成项目的前端实现。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>结果</title>
        <link rel="stylesheet" type="text/css" href="css/jquery-ui.min.css"/>
        <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
        <script src="js/jquery-2.1.1.min.js"></script>
        <script src="js/jquery-ui.min.js"></script>
        <style>
            body {
                padding-left: 14px;
                padding-top: 14px;
            }
    
            ul, li {
                list-style: none;
                padding: 0;
            }
    
            li {
                padding-bottom: 16px;
            }
    
            a, a:link, a:visited, a:hover, a:active {
                text-decoration: none;
            }
    
            em {
                color: #ff0000;
                font-style: normal;
            }
        </style>
    </head>
    <body>
    <div>
        <input id="newsTag" class="form-control" style="display: inline; width: 50%;" name="keyword">
        <button class="btn btn-primary" onclick="search()">搜索一下</button>
    </div>
    <hr>
    <div>
        <ul id="news"></ul>
    </div>
    </body>
    <script>
        $("#newsTag").autocomplete({
            source: "/autoSuggest", // 请求路径
            delay: 100, //请求延迟
            minLength: 1 //最少输入多少字符向服务器发送请求
        })
    
        function search() {
            var term = $("#newsTag").val();
    
            $.get("/highLightSearch", {term: term}, function (data) {
                var str = "";
                for (var i = 0; i < data.length; i++) {
                    var document = data[i];
                    str += "<li>" +
                        "       <h4>" +
                        "           <a href='" + document.url + "' target='_blank'>" + document.title + "</a>" +
                        "       </h4> " +
                        "       <p>" + document.content + "</p>" +
                        "   </li>";
                }
                $("#news").html(str);
            })
    
        }
    </script>
    </html>
    

    练习源码:https://gitee.com/cutelili/elastic-search
    ElasticStack

    展开全文
  • 主要介绍了discuzx3.1文章进行全文检索的实现方法,需要的朋友可以参考下
  • solr全文检索实现原理

    2021-01-27 12:54:12
    Lucene是apache软件基金会4jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎...
  • 为您提供Sphinx全文检索引擎 for Windows下载,Sphinx是一个基于SQL的全文检索引擎,可以结合MySQL,PostgreSQL做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。...
  • 完整的 全文检索引擎,而是⼀个全文检索引擎的架构,提供了完整的查询引擎、索引引擎和部分文本分析引擎。 Lucene提供了⼀个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境⾥Lucene是⼀个成熟的...

    一、Lucene简介

    Lucene是Apache Jakarta家族中的⼀个开源项目,是⼀个开放源代码的全文检索引擎工具包,但它不是⼀个
    完整的 全文检索引擎,而是⼀个全文检索引擎的架构,提供了完整的查询引擎、索引引擎和部分文本分析引擎。
    Lucene提供了⼀个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境⾥Lucene是⼀个成熟的免费开源工具,是⽬前最为流行的基于 Java 开源全文检索工具包。

    • 数据总体分为两种:
      • 结构化数据:指具有固定格式或有限长度的数据,如数据库、元数据等
      • 非结构化数据:指不定⻓或⽆固定格式的数据,如邮件、word⽂档等磁盘上的⽂件
    • 对于结构化数据的全文搜索很简单,因为数据都是有固定格式的,例如搜索数据库中数据使用SQL语句即可
    • 对于非结构化数据,有以下两种⽅法:
      • 顺序扫描法(Serial Scanning)
      • 全文检索(Full-text Search)

    顺序扫描法:如果要找包含某⼀特定内容的⽂件,对于每⼀个文档,从头到尾扫描内容,如果此文档包含此字符串, 则此文档为我们要找的⽂件,接着看下⼀个⽂件,直到扫描完所有的⽂件,因此速度很慢。

    全文检索:将非结构化数据中的⼀部分信息提取出来,重新组织,使其变得具有⼀定结构,然后对此有⼀定结构的数据进行搜索,从而达到搜索相对较快的⽬的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引

    二、Lucene全文检索流程

    在这里插入图片描述
    在这里插入图片描述

    2.1 创建索引

    • 获得原始文档
      原始⽂档是指要索引和搜索的内容。原始内容包括互联⽹上的网页、数据库中的数据、磁盘上的文件等

    • 创建文档对象
      获取原始内容的⽬的是为了索引,在索引前需要将原始内容创建成文档(Document)文档中包括⼀个⼀个的域 (Field),域中存储内容。 这⾥我们可以将磁盘上的⼀个文件当成⼀个document,Document中包括⼀些 Field(file_name文件名称、file_path文件路径、file_size文件大小、file_content文件内容),如下图:在这里插入图片描述
      注意:

      • 每个Document可以有多个Field,不同的Document可以有不同的Field
      • 每个文档都有⼀个唯⼀的编号,就是文档id。
    • 分析文档
      将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过
      对原始文档提取单词、将字母转为小写、去除标点符号、去除停⽤词等过程⽣成最终的语汇单元,可以将语
      汇单元理解为⼀个⼀个的单词。 比如下边的文档经过分析如下:

    原文档内容

    Lucene is a Java full-text search engine. Lucene is not a complete application, but rather
    a code library and API that can easily be used to add search capabilities to applications
    

    分析后得到的语汇单元:

    lucene、java、full、search、engine
    

    每个单词叫做⼀个Term,不同的域中拆分出来的相同的单词是不同的term。term中包含两部分⼀部分是⽂档
    的域名,另⼀部分是单词的内容。 例如:文件名中包含apache和文件内容中包含的apache是不同的term。

    • 创建索引—倒排索引
      创建索引是对语汇单元索引,通过词语找⽂档,这种索引的结构叫倒排索引结构。 传统方法是根据文件找到该文件的内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描⽅法,数据量大、搜索慢。 倒排索引结构是根 据内容(词语)找文档

    2.1 查询索引

    • 用户查询接口
      全文检索系统提供用户搜索的界⾯供用户提交搜索的关键字,搜索完成展示搜索结果。Lucene不提供制作用
      户搜索界⾯的功能,需要根据自己的需求开发搜索界面。
    • 创建查询
      有户输⼊查询关键字执行搜索之前需要先构建⼀个查询对象,查询对象中可以指定查询要搜索的Field文档
      域、查询关键字等,查询对象会生成具体的查询语法,例如: 语法 “fileName:lucene”表示要搜索Field域的内
      容为“lucene”的⽂档
    • 执行查询
      搜索索引过程: 根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所链接的文档
      链表。 比如搜索语法为“fileName:lucene”表示搜索出fileName域中包含Lucene的文档。 搜索过程就是在索引
      上查找域为 fileName,并且关键字为Lucene的term,并根据term找到文档id列表。
    • 渲染查询结果
      以⼀个友好的界⾯将查询结果展示给有户,⽤户根据搜索结果找自己想要的信息,为了帮助⽤户很快找到自己的结 果,提供了很多展示的效果,比如搜索结果中将关键字高亮显示,百度提供的快照等。

    三、分词器

    分词器(Analyzer)的作⽤是把⼀文本中的词按规则取出所包含的所有词,对应的是Analyzer类,这是⼀个抽象类(public abstract class org.apache.lucene.analysis.Analyzer),切分词的具体规则是由⼦类实现的,所以对于不同的语⾔规则,要有不同的分词器。

    3.1 中文分词器的原理

    中文分词比较复杂,并没有英文分词那么简单,这主要是因为中文的词与词之间并不是像英文那样有空格来隔
    开,因为不是⼀个字就是⼀个词,而且⼀个词在另外⼀个地方就可能不是⼀个词,如:“我们是中国人”,"是中"就
    不是⼀个词,对于中文分词,通常有三种方式:单字分词、⼆分法分词、词典分词。

    • 单字分词:就是按照中文⼀个字⼀个字的进行分词,比如:“我们是中国人”,分词的效果就
      是"我",“们”,“是”,“中”,“国”,“人”,StandardAnalyzer分词法就是单字分词。
    • ⼆分法分词:按照两个字进行切分,比如:“我们是中国人”,分词的效果就是:“我们”,“们是”,“是 中”,“中国”,“国人”,CJKAnalyzer分词法就是⼆分法分词
      词库分词:按照某种算法构造词,然后去匹配已建好的词库集合,如果匹配到就切分出来成为词语,通常词
      库分词被认为是最好的中文分词算法,如:“我们是中国人”,分词的效果就是:“我们”,“中国人”,极易分词
      MMAnalyzer、庖丁分词、IkAnalyzer等分词法就是属于词库分词。

    3.1 常用分词器

    • StopAnalyzer
      去掉了空格,将除了字母以外的符号全部去除,并且将所有字符变为小写,去除了所谓的stop words,比如the, a,this这些。这个也是不支持中文的。
    • IKAnalyzer
      中国人自己开发,对于中文分词比较精准

    3.1 IK 分词器

    Elasticsearch中文分词我们采⽤Ik分词,ik有两种分词模式:ik_max_word和ik_smart模式;
    ik_max_word 和 ik_smart 什么区别?

    • ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华⼈⺠共和国国歌”拆分为“中华人民共和国,中华人民, 中华,华人,人民共和国,人民,人,民,共和国,共和,和国,国歌”,会穷尽各种可能的组合;
    • ik_smart: 会做最粗粒度的拆分,⽐如会将“中华人民共和国国歌”拆分为“中华人民,共和国,国歌”。 索引时,为
      了提供索引的覆盖范围,通常会采⽤ik_max_word分析器,会以最细粒度分词索引,搜索时为了提高搜索准确
      度,会采⽤用k_smart分析器,会以粗粒度分词

    我们可以使用网上的⼀些工具查看分词的效果,比如:https://www.sojson.com/analyzer

    四、 lucene全文检索与数据库查询的比较

    4.1 性能上

    数据库:⽐如我要查找某个商品,根据商品名,⽐如select * from product where doctname like %keywords%,这 样查询的话对于数据量少是可以的,可是⼀旦你的数据量巨大几万几十万的时候,你的性能将会极大的减弱。

    lucene: 全文检索,建⽴⼀个索引库,⼀次建⽴多次使用。在索引库⾥⾯会把所有的商品名根据分词器建⽴索引,就好比新华字典,索引对应document,比如输入衬衫,那么就会根据索引迅速的翻到衬衫对应的商品名,时间迅
    速, 性能很好。

    4.2 相关度排序

    数据库:数据库要实现该功能也是可以的,可是需要改变表的结构,添加⼀个字段,然后该字段用于排名,最后查询的时候order by ⼀下

    lucene: 查询出来的document都有⼀个算法(得分),根据算法可以计算得分,得分越高的排名越靠前,比如百度搜索⼀个东⻄,⼀般排名靠前的得分越高,得分通过算法,可以人工控制,比如百度推广,企业给的钱多得自然高,因此排名靠前。

    4.2 准确性

    数据库:select * from product where doctname like %ant%,搜索出来的可以是plant,aplant,planting等等,准确性不高

    lucene: 通过索引查询的,就好像你查字典⼀样,准确性⽐数据库的模糊查询高许多

    展开全文
  • 为您提供Sphinx全文检索引擎 for Linux下载,Sphinx是一个基于SQL的全文检索引擎,可以结合MySQL,PostgreSQL做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。...
  • PostgreSQL全文检索

    千次阅读 2021-08-10 09:31:51
    PostgreSQL全文检索tsvectortsquery PostgreSQL全文检索 在日常的数据处理中,我们经常会有这样的需求:从一个文本中寻找某个字符串(比如某个单词)。 对这个需求,我们可以用类似这样的SQL完成: SELECT * FROM ...
  • 浅析MongoDB 全文检索

    2020-09-08 20:04:21
    主要介绍了MongoDB 全文检索的相关资料,文中讲解非常细致,帮助大家更好的学习了解MongoDB,感兴趣的朋友可以了解下
  • 这里写目录标题elasticsearch全文检索流程索引过程创建索引获得原始文档创建文档对象分析文档创建索引查询索引 elasticsearch全文检索流程 索引过程 索引过程,对要搜索的原始内容进行索引构建一个索引库,索引...
  • [搜索链接]易搜索 站内全文检索搜索引擎 v1.0_yssfor.zip源码ASP.NET网站源码打包下载[搜索链接]易搜索 站内全文检索搜索引擎 v1.0_yssfor.zip源码ASP.NET网站源码打包下载[搜索链接]易搜索 站内全文检索搜索引擎 v...
  • 全文检索四种技术解决方案

    千次阅读 2022-03-29 16:02:53
    对当前的几种全文检索技术进行分析对比,最后推荐一款基于Elasticsearch的全文检索知识库管理系统。 科亿知识库 kykms是一款基于Elasticsearch的文档型知识库管理系统,提供强大的全文检索与文档分类管理功能。系统...
  • Lucene全文检索框架+Solr搜索引擎(2018版.Java)Lucene实战应用
  • Mysql 全文索引是专门为了解决模糊查询提供的,可以对整篇文章 预先按照词进行索引,搜索效率高,能够支持百万级的数据检索。 如果您使用的是自己的服务器 ,请马上进行设置,不要浪费了这个功能。
  • 前言 为什么要写这个系列的文章呢,基于两个原因,一...我们在网络上的大部分搜索服务都用到了全文检索技术,全文检索的应用场景随处可见,招聘网站的职位搜索,电商网站的商品搜索,百度搜搜等等,这种业务场景往往意

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 189,474
精华内容 75,789
关键字:

全文检索

友情链接: archive.zip