Lucene 和 Kibana、ElasticSeach、Spring Data ElasticSearch

什么是全文检索

数据分类

生活中的数据总体分为两种:结构化数据和非结构化数据。

结构化数据 - 行数据,可以用二维表结构来逻辑表达实现的数据;指具有固定格式或有限长度的数据,如数据库,元数据等。

非结构化数据 - 指不定长或无固定格式的数据,如邮件,word 文档等磁盘上的文件。

结构化数据搜索

常见的结构化数据也就是数据库中的数据。

在数据库中搜索很容易实现,通常都是使用 SQL 语句进行查询,而且能很快的得到查询结果。

客户端请求 ----> 连接器

连接器 ----> 缓存区
连接器 ----> 分析器

分析器 ----> 缓存区
分析器 ----> 优化器

优化器 ----> 执行器

Server 层:
- 连接器
- 缓存区
- 分析器
- 优化器
- 执行器

Server 层 ----> 存储引擎(引擎1,引擎2,引擎3)

因为数据库中的数据存储是有规律的,有行有列而且数据格式、数据长度都是固定的,所以数据库搜索很容易。

数据库底层文件存储的物理方式:硬盘是块状存储,其基本单位是 1kb 每块;磁头每次读取数据,至少扫描一个块的大小。

假如表里一个元组,数据加起来一共 100 b,则一个块可以放十个元组,通俗的说是 10 条数据,DBMS 在查询时,就把每一块的数据读取出来,判断其中是否有对应的数据。

常见的关系型数据,其最基本常见的存储方式:

  • 堆 - 随着文件的插入,不停地往尾部上堆;它地访问路径就是顺序扫描,扫完了才能查到数据。
  • Hash - 文件的 hash 值就是其存储地址。
  • 索引 + 堆 - 对堆文件的某一列,建立 b+ 树索引。

非结构化数据查询方法

1)顺序扫描法 (Serial Scanning)

用户搜索 --> 文件

所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用 windows 的搜索也可以搜索文件内容,只是相当的慢。

2)全文检索 (Full-text Search)

用户通过查询索引库 --> 生成索引 --> 文档

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方法。这个过程类似于通过字典的目录查字的过程。

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

例如:字典。字典的拼音表和部首检字表就相当于字典的索引,对每一个字的解释是非结构化的,如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取出来进行结构化处理,比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将读音拿出来按一定的顺序排列,每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音,然后按其指向的页数,便可找到我们的非结构化数据——也即对字的解释。

这种先建立索引,再对索引进行搜索的过程就叫全文检索 (Full-Text Search)。虽然创建索引的过程也是非常耗时的,但是索引一旦创建就可以多次使用,全文检索主要处理的是查询,所以耗时间创建索引是值得的。

建立索引 --> 检索索引

如何实现全文检索

可以使用 Lucene 实现全文检索。Lucene 是 apache 下的一个开放源代码的全文检索引擎工具包。提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene 的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。

Lucene 适用场景:

  • 在应用中为数据库中的数据提供全文检索实现。

  • 开发独立的搜索引擎服务、系统

Lucene 的特性:

1. 稳定、索引性能高。
+ 每小时能够索引 150 GB 以上的数据
+ 对内存的要求小,只需要 1 MB 的堆内存
+ 增量索引和批量索引一样快
+ 索引的大小约为索引文本大小的 20% ~ 30%

2. 高效、准确、高性能的搜索算法。
+ 良好的搜索排序
+ 强大的查询方式支持:短语查询、通配符查询、临近查询、范围查询等
+ 支持字段搜索(如标题、作者、内容)
+ 可根据任意字段排序
+ 支持多个索引查询结果合并
+ 支持更新操作和查询操作同时进行
+ 支持高亮、join、分组结果功能
+ 速度快
+ 可扩展排序模块,内置包含向量空间模型、BM25 模型可选
+ 可配置存储引擎

3. 跨平台。
+ 纯 java 编写
+ 作为 Apache 开源许可下的开源项目,你可以在商业或开源项目中使用
+ Lucene 有多种语言实现版(如 C,C++、Python 等),不仅仅是 JAVA

Lucene 架构:

Application:
+ 从文件系统、数据库、Web、手动输入获取数据,然后生成索引。
+ 获取用户的 Query,然后查询索引,返回查询结果给用户。

用户 
--职位搜索--> 应用服务器
--SQL--> 结构化数据库

Lucene:
+ 生成索引需要与索引库交互。
+ 查询索引需要与索引库交互。

用户 
--职位搜索--> 应用服务器
--特定API--> Lucene索引库

数据源(网络、数据库、文档)
----> Lucene索引库

全文检索的应用场景

对于数据量大、数据结构不固定的数据可采用全文检索方式搜索:

  • 单机软件的搜索:word、markdown。
  • 站内搜索:京东、淘宝、拉勾,索引源是数据库。
  • 搜索引擎:百度、Google,索引源是爬虫程序抓取的数据。

Lucene 实现全文检索的流程说明

索引和搜索流程图

查询索引:
1. 用户查询接口
2. 创建查询
3. 执行查询
4. 渲染结果

用户查询索引 
-----> 索引库

原始文档 
----> 创建索引
--创建索引--> 索引库
--返回结果--> 用户查询索引

创建索引:
1. 获取文档
2. 构建文档对象
3. 分析文档(分词)
4. 创建索引

对要搜索的原始内容进行索引构建一个索引库,索引过程包括:确定原始内容即要搜索的内容 --> 采集文档 --> 创建文档 --> 分析文档 --> 索引文档。

从索引库中搜索内容,搜索过程包括:用户通过搜索界面 --> 创建查询 --> 执行搜索从索引库搜索 --> 渲染搜索结果。

创建索引

核心概念:

Document:

  • 用户提供的源是一条条记录,它们可以是文本文件、字符串或者数据库表的一条记录等等。
  • 一条记录经过索引之后,就是以一个 Document 的形式存储在索引文件中的。
  • 用户进行搜索,也是以 Document 列表的形式返回。

Field:

  • 一个 Document 可以包含多个信息域。例如一篇文章可以包含“标题”、“正文”、“最后修改时间”等信息域,这些信息域就是通过 Field 在 Document 中存储的。
  • Field 有两个属性可选:存储和索引。通过存储属性可以控制是否对这个 Field 进行存储;通过索引属性可以控制是否对该 Field 进行索引。
  • 如果对标题和正文进行全文搜索,要把索引属性设置为真,同时希望能直接从搜索结果中提取文章标题,把标题域的存储属性设置为真;但是由于正文域太大了,为了缩小索引文件大小,可以将正文域的存储属性设置为假,当需要时再直接读取文件;如果只是希望能从搜索结果中提取最后修改时间,不需要对它进行搜索,可以把最后修改时间域的存储属性设置为真,索引属性设置为假。上面的三个域涵盖了两个属性的三种组合,还有一种全为假的没有用到,事实上 Field 不允许那么设置,因为既不存储又不索引的域是没有意义的。

Term:

  • 是搜索的最小单位,它表示文档的一个词语,Term 由两部分组成:它表示的词语和这个词语所出现的 Field 的名称。

以招聘网站的搜索为例,在网站上输入关键字搜索显示的内容不是直接从数据库中来的,而是从索引库中获取的,网站的索引数据需要提前创建的。以下是创建的过程:

  • 获得原始文档 - 就是从 MySQL 数据库中通过 SQL 语句查询需要创建索引的数据。
  • 创建文档对象(Document)- 把查询的内容构建成 lucene 能识别的 Document 对象,获取原始内容的目的是为了索引,在索引前需要将原始内容创建成文档,文档中包括一个一个的域(Field),这个域对应就是表中的列。
  • 注意 - 每个 Document 可以有多个 Field,不同的 Document 可以有不同的 Field,同一个Document 可以有相同的 Field(域名和域值都相同)。每个文档都有一个唯一的编号,就是文档 id。
  • 分析文档 - 将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词、将字母转为小写、去除标点符号、去除停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词。分好的词会组成索引库中最小的单元:term,一个 term 由域名和词组成。
  • 创建索引 - 对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到 Document(文档)。
  • 注意 - 创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构。倒排索引结构是根据内容(词语)找文档。倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。

倒排索引

倒排索引 Inverted List 记录每个词条出现在哪些文档,及在文档中的位置,可以根据词条快速定位到包含这个词条的文档及出现的位置。

文档:索引库中的每一条原始数据,例如一个商品信息、一个职位信息。

词条:原始数据按照分词算法进行分词,得到的每一个词。

创建倒排索引,分为以下几步:

1)创建文档列表 - Lucene 首先对原始文档数据进行编号 DocID,形成列表,就是一个文档列表。

2)创建倒排索引列表 - 对文档中数据进行分词,得到词条(分词后的一个又一个词)。对词条进行编号,以词条创建索引。然后记录下包含该词条的所有文档编号(及其它信息)。

搜索的过程:当用户输入任意的词条时,首先对用户输入的数据进行分词,得到用户要搜索的所有词条,然后拿着这些词条去倒排索引列表中进行匹配。找到这些词条就能找到包含这些词条的所有文档的编号。然后根据这些编号去文档列表中找到文档

查询索引

查询索引也是搜索的过程。搜索就是用户输入关键字,从索引(index)中进行搜索的过程。根据关键字搜索索引,根据索引找到对应的文档

第一步:创建用户接口 - 用户输入关键字的地方。

第二步:创建查询 - 指定查询的域名和关键字。

第三步:执行查询。

第四步:渲染结果(结果内容显示到页面上 关键字需要高亮)。

Lucene 实战

需求说明

生成职位信息索引库,从索引库检索数据。

创建数据库 es,将 sql 脚本导入数据库执行。

/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`es` /*!40100 DEFAULT CHARACTER SET latin1 */;

USE `es`;

/*Table structure for table `job_info` */

DROP TABLE IF EXISTS `job_info`;

CREATE TABLE `job_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键 id',
  `company_name` varchar(100) DEFAULT NULL COMMENT '公司名称',
  `company_addr` varchar(200) DEFAULT NULL COMMENT '公司联系方式',
  `company_info` mediumtext COMMENT '公司信息',
  `job_name` varchar(100) DEFAULT NULL COMMENT '职位名称',
  `job_addr` varchar(50) DEFAULT NULL COMMENT '工作地点',
  `job_info` mediumtext COMMENT '职位信息',
  `salary_min` int(10) DEFAULT NULL COMMENT '薪资范围,最小',
  `salary_max` int(10) DEFAULT NULL COMMENT '薪资范围,最大',
  `url` varchar(150) DEFAULT NULL COMMENT '招聘信息详情页',
  `time` varchar(10) DEFAULT NULL COMMENT '职位最近发布时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7656 DEFAULT CHARSET=utf8 COMMENT='招聘信息';

中文分词器使用 IK。

准备开发环境

第一步:创建一个 maven 工程,创建一个 Spring Boot 项目
第二步:导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.renda</groupId>
    <artifactId>lucene-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>lucene-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <!-- web 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- lombok 工具 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!-- pojo 持久化使用 -->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>javax.persistence-api</artifactId>
            <version>2.2</version>
        </dependency>
        <!-- mysql 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- 引入 Lucene 核心包及分词器包 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>4.10.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>4.10.3</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!-- IK 中文分词器 -->
        <dependency>
            <groupId>com.janeluo</groupId>
            <artifactId>ikanalyzer</artifactId>
            <version>2012_u6</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
            <!-- 打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
第三步:创建引导类 com.renda.LuceneDemoApplication
package com.renda;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.renda.mapper")
public class LuceneDemoApplication {
   

    public static void main(String[] args) {
   
        SpringApplication.run(LuceneDemoApplication.class, args);
    }

}
第四步:配置 application.yml 文件
server:
  port: 9000
Spring:
  application:
    name: lagou-lucene
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/es?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: password

# 开启驼峰命名匹配映射
mybatis:
  configuration:
    map-underscore-to-camel-case: true
第五步:创建实体类、Mapper、Service、Controller 等等

实体类 com.renda.pojo.JobInfo

package com.renda.pojo;

import lombok.Data;
import lombok.ToString;

import javax.persistence.Id;
import javax.persistence.Table;

@Data
@ToString
@Table(name = "job_info")
public class JobInfo {
   

    @Id
    private long id;
    private String companyName;
    private String companyAddr;
    private String companyInfo;
    private String jobName;
    private String jobAddr;
    private String jobInfo;
    private long salaryMin;
    private long salaryMax;
    private String url;
    private String time;

}

数据层 com.renda.mapper.JobInfoMapper

public interface JobInfoMapper extends BaseMapper<JobInfo> {
   
}

服务层

com.renda.service.JobInfoService

public interface JobInfoService {
   

    /**
     * 通过id查询
     */
    public JobInfo selectById(long id);

    /**
     * 查询所有
     */
    public List<JobInfo> selectAll();

}

com.renda.service.impl.JobInfoServiceImpl

@Service
public class JobInfoServiceImpl implements JobInfoService {
   

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    private JobInfoMapper jobInfoMapper;

    @Override
    public JobInfo selectById(long id) {
   
        return jobInfoMapper.selectById(id);
    }

    @Override
    public List<JobInfo> selectAll() {
   
        return jobInfoMapper.selectList(new QueryWrapper<JobInfo>());
    }

}

控制层 com.renda.controller.JobInfoController

@RestController
@RequestMapping("/jobInfo")
public class JobInfoController {
   

    @Autowired
    private JobInfoService jobInfoService;

    @RequestMapping("/query/{id}")
    public JobInfo selectById(@PathVariable Long id){
   
        return jobInfoService.selectById(id);
    }

    @RequestMapping("/query")
    public List<JobInfo> select(){
   
        return jobInfoService.selectAll();
    }

}

测试类 com.renda.LuceneDemoApplicationTests

@RunWith(SpringRunner.class)
@SpringBootTest
public class LuceneDemoApplicationTests {
   
    ...
}

整体结构:

+ src/main/java
  + com.renda.controller
    + JobInfoController
  + com.renda.mapper
    + JobInfoMapper
  + com.renda.pojo
    + JobInfo
  + com.renda.service
    + impl.JobInfoServiceImpl
    + JobInfoService
  + com.renda
    + LuceneDemoApplication
+ src/main/resources
  + static
  + templates
  + application.yml
+ src/test/java
  + com.renda
    + LuceneDemoApplicationTests

创建索引

测试类 com.renda.LuceneDemoApplicationTests

@RunWith(SpringRunner.class)
@SpringBootTest
public class LuceneDemoApplicationTests {
   

    @Autowired
    private JobInfoService jobInfoService;

    /**
     * 创建索引
     */
    @Test
    public void createIndex() throws Exception {
   
        // 1.指定索引文件的存储位置,索引具体的表现形式就是一组有规则的文件
        Directory directory = FSDirectory.open(new File("E:/class/index"));
        // 2.配置版本及其分词器
        Analyzer analyzer = new IKAnalyzer();
        IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
        // 3.创建 IndexWriter 对象,作用就是创建索引
        IndexWriter indexWriter = new IndexWriter(directory, config);
        // 先删除已经存在的索引库
        indexWriter.deleteAll();
        // 4.获得索引源 / 原始数据
        List<JobInfo> jobInfoList = jobInfoService.selectAll();
        // 5. 遍历 jobInfoList,每次遍历创建一个 Document 对象
        for (JobInfo jobInfo : jobInfoList) {
   
            // 创建 Document 对象
            Document document = new Document();
            // 创建 Field 对象,添加到 document 中
            document.add(new LongField("id", jobInfo.getId(), Field.Store.YES));
            // 切分词、索引、存储
            document.add(new TextField("companyName", jobInfo.getCompanyName(), Field.Store.YES));
            document.add(new TextField("companyAddr", jobInfo.getCompanyAddr(), Field.Store.YES));
            document.add(new TextField("companyInfo", jobInfo.getCompanyInfo(), Field.Store.YES));
            document.add(new TextField("jobName", jobInfo.getJobName(), Field.Store.YES));
            document.add(new TextField("jobAddr", jobInfo.getJobAddr(), Field.Store.YES));
            document.add(new TextField("jobInfo", jobInfo.getJobInfo(), Field.Store.YES));
            document.add(new LongField("salaryMin", jobInfo.getSalaryMin(), Field.Store.YES));
            document.add(new LongField("salaryMax", jobInfo.getSalaryMax(), Field.Store.YES));
            document.add(new StringField("url", jobInfo.getUrl(), Field.Store.YES));
            // 将文档追加到索引库中
            indexWriter.addDocument(document);
        }
        // 关闭资源
        indexWriter.close();
        System.out.println("Index was created successfully!");
    }
    
}

生成的索引目录:D:\class\index

索引 Index:

  • 在 Lucene 中一个索引是放在一个文件夹中的。
  • 同一文件夹中的所有的文件构成一个 Lucene 索引。

段 Segment:

  • 按层次保存了从索引,一直到词的包含关系:索引 (Index) –-> 段 (segment) –-> 文档 (Document) –-> 域 (Field) –-> 词 (Term)。
  • 也即此索引包含了那些段,每个段包含了那些文档,每个文档包含了哪些域,每个域包含了哪些词。
  • 一个索引可以包含多个段,段与段之间是独立的,添加新文档可以生成新的段,不同的段可以合并。
  • 具有相同前缀文件的属同一个段,如 _0
  • segments.gensegments_1 是段的元数据文件,也即它们保存了段的属性信息。
D:\class\index

_0.cfe
_0.cfs
_0.si
_1.cfe
_1.cfs
_1.si
segments.gen
segments_1
write.lock

Field 的特性:Document (文档) 是 Field (域) 的承载体,一个 Document 由多个 Field 组成,Field 由名称和值两部分组成,Field 的值是要索引的内容,是要搜索的内容。

  • 是否分词 (tokenized) :
是: 将 Field 的值进行分词处理,分词的目的是为了索引。如: 商品名称、商品描述。这些内容用户会通过输入关键词进行查询,由于内容多样,需要进行分词处理建立索引。

否: 不做分词处理,如: 订单编号、身份证号, 是一个整体,分词以后就失去了意义,故不需要分词。
  • 是否索引 (indexed)
是: 将 Field 内容进行分词处理后得到的词 (或整体 Field 内容) 建立索引,存储到索引域。索引的目的是为了搜索。如: 商品名称, 商品描述需要分词建立索引。订单编号、身份证号作为整体建立索引。只要可能作为用户查询条件的词, 都需要索引。

否: 不索引。如: 商品图片路径,不会作为查询条件,不需要建立索引。
  • 是否存储 (stored)
是: 将 Field 值保存到 Document 中。如: 商品名称, 商品价格。凡是将来在搜索结果页面展现给用户的内容,都需要存储。

否: 不存储。如: 商品描述。内容多格式大,不需要直接在搜索结果页面展现,不做存储。需要的时候可以从关系数据库取。

常用的 Field 类型:

  • StringField(FieldName, FieldValue, Store.YES) - 字符串 - 不可分词 - 默认索引 - 可以开启存储 - 字符串类型 Field,不可分词,作为一个整体进行索引,如身份证号、订单编号;是否需要存储由 Store.YES 或 Store.No 决定。
  • LongField(FieldName, FieldValue, Store.YES) - 数值型代表 - 默认分词 - 默认索引 - 可以开启存储 - Long 数值类型 Field 代表,默认分词并且索引,如价格;是否需要存储由 Store.YES 或 Store.No 决定。
  • StoredField(FieldName, FieldValue) - 重载方法支持多种类型 - 不可分词 - 不可索引 - 默认开启存储 - 构建不同类型的 Field,不分词,不索引,要存储,如商品图片路径。
  • TextField(FieldName, FieldValue, Store.NO) - 文本类型 - 默认分词 - 默认索引 - 可以开启存储 - 文本类型 Field,默认分词并且索引,是否需要存储由 Store.YES 或 Store.No 决定。

查询索引

com.renda.LuceneDemoApplicationTests

@RunWith(SpringRunner.class)
@SpringBootTest
public class LuceneDemoApplicationTests {
   

    @Autowired
    private JobInfoService jobInfoService;

    ...

    @Test
    public void query() throws Exception {
   
        // 1.指定索引文件的存储位置,索引具体的表现形式就是一组有规则的文件
        Directory directory = FSDirectory.open(new File("E:/class/index"));
        // 2.IndexReader 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 3.创建查询对象,IndexSearcher
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 使用 term, 查询公司名称中包含"北京"的所有的文档对象
        Query query = new TermQuery(new Term("companyName", "北京"));
        TopDocs topDocs = indexSearcher.search(query, 100);
        // 获得符合查询条件的文档数
        int totalHits = topDocs.totalHits;
        System.out.println("符合条件的文档数:" + totalHits);
        // 获得命中的文档  ScoreDoc封装了文档id信息
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
   
            // 文档 id
            int docId = scoreDoc.doc;
            // 通过文档 id 获得文档对象
            Document doc = indexSearcher.doc(docId);
            System.out.println("id:" + doc.get("id"));
            System.out.println("companyName:" + doc.get("companyName"));
            System.out.println("companyAddr:" + doc.get("companyAddr"));
            System.out.println("companyInfo:" + doc.get("companyInfo"));
            System.out.println("jobName:" + doc.get("jobName"));
            System.out.println("jobInfo:" + doc.get("jobInfo"));
            System.out.println("*******************************************");
        }
        // 资源释放
        indexReader.close();
    }

}

这里需要使用可以合理分词的分词器,否则中文会一个字一个字地分词,其中最有名的中文分词器是 IKAnalyzer 分词器。

中文分词器的使用

第一步:导入依赖。

<!-- IK 中文分词器 -->
<dependency>
    <groupId>com.janeluo</groupId>
    <artifactId>ikanalyzer</artifactId>
    <version>2012_u6</version>
</dependency>

第二步:根据需求,可以添加配置文件放入到 resources 文件夹中(可以在配置文件额外加扩展词典和停止词典)。

IKAnalyzer.cfg.xml
stopword.dic

第三步:创建索引时使用 IKanalyzer。

Analyzer analyzer = new IKAnalyzer();

考虑一个问题:一个大型网站中的索引数据会很庞大的,所以使用 Lucene 这种原生的写代码的方式就不合适了,所以需要借助一个成熟的项目或软件来实现,目前比较有名是 solr 和 ElasticSearch。

Elastic Search 介绍和安装

Elasticsearch 是一个需要安装配置的软件。

ELK 技术栈说明:

Elastic 有一条完整的产品线 ELK - Elasticsearch、Logstash、Kibana,前面说的三个就是常说的 ELK 技术栈(开源实时日志分析平台)。

MySQL ---Logstash数据同步---> ElasticSearch索引库

MySQL ---- MySQL可视化软件

ElasticSearch索引库 ---- Kibana可视化软件

Logstash 的作用就是一个数据收集器,将各种格式各种渠道的数据通过它收集解析之后格式化输出到 Elastic Search ,最后再由 Kibana 提供的比较友好的 Web 界面进行汇总、分析、搜索。

ELK 内部实际就是个管道结构,数据从 Logstash 到 Elastic Search 再到 Kibana 做可视化展示。这三个组件各自也可以单独使用,比如 Logstash 不仅可以将数据输出到 Elastic Search ,也可以到数据库、缓存等。

简介

Elastic

Elastic 官网:https://www.elastic.co/cn/

Elastic 有一条完整的产品线:Elasticsearch、Logstash、Kibana 等,前面说的三个就是常说的 ELK 技术栈。

Elasticsearch

Elasticsearch 官网:https://www.elastic.co/cn/products/elasticsearch

功能:

  • 分布式的搜索引擎 - 百度、Google、站内搜索。
  • 全文检索 = 提供模糊搜索等自动度很高的查询方式,并进行相关性排名,高亮等功能。
  • 数据分析引擎(分组聚合)- 电商网站一周内手机销量 Top 10。
  • 对海量数据进行近乎实时处理 - 水平扩展,每秒钟可处理海量事件,同时能够自动管理索引和查询在集群中的分布方式,以实现极其流畅的操作。

Elastic Search 具备以下特点:

  • 高速、扩展性、最相关的搜索结果。
  • 分布式 - 节点对外表现对等,每个节点都可以作为入门,加入节点自动负载均衡。
  • JSON - 输入输出格式是 JSON。
  • Restful 风格,一切 API 都遵循 Rest 原则,容易上手。
  • 近实时搜索,数据更新在 Elasticsearch 中几乎是完全同步的,数据检索近乎实时。
  • 安装方便 - 没有其它依赖,下载后安装很方便,简单修改几个参数就可以搭建集群。
  • 支持超大数据:可以扩展到 PB 级别的结构化和非结构化数据。
版本

目前 Elasticsearch 最新的版本是 7.x,企业内目前用的比较多是 6.x,以 6.2.4 为例子,需要 JDK 1.8 及以上。

安装和配置

为了快速看到效果可以直接在本地 window 下安装 Elasticsearch,实际开发是在 Linux 中使用,但使用方式是一样的;环境要求:JDK 8 及以上版本。

第一步:解压安装包

把压缩包 elasticsearch-6.2.4.zip 放到一个没有中文没有空格的位置,解压即可。

\bin            命令,启动,关闭
\config         配置文件
\lib            ES 的 jar 包依赖
\logs           日志
\modules        ES 工作所依赖的一些组件,启动时加载
\plugins        放置一些第三方插件,如 IK 分词器
LICENSE.txt
NOTICE.txt
README.textile
第二步:修改配置文件

1、修改索引数据和日志数据存储的路径 \config\elasticsearch.yml

第 33 行和 37 行,修改完记得把注释打开:

# ----------------------------------- Paths ------------------------------------
#
# Path to directory where to store the data (separate multiple locations by comma):
#
path.data: e:\class\es\data
#
# Path to log files:
#
path.logs: e:\class\es\logs
#
第三步:启动

进入 bin 目录中直接双击 elasticsearch.bat

如果启动失败,需要修改虚拟机内存的大小,默认为 1 G,可以调小。

在 config 目录下找到 jvm.options 文件 ,修改后如下:

# Xms represents the initial size of total heap space
# Xmx represents the maximum size of total heap space

-Xms256m
-Xmx256m
  • Xms 是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。
  • Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出 OutOfMemory 异常。

访问

启动后台部分输出如下:

[2020-11-08T21:52:33,482][INFO ][o.e.n.Node               ] [G9aQRl_] starting ...
[2020-11-08T21:52:34,307][INFO ][o.e.t.TransportService   ] [G9aQRl_] publish_address {127.0.0.1:9300}, bound_addresses {127.0.0.1:9300}, {[::1]:9300}
[2020-11-08T21:52:37,363][INFO ][o.e.c.s.MasterService    ] [G9aQRl_] zen-disco-elected-as-master ([0] nodes joined), reason: new_master {G9aQRl_}{G9aQRl_qR2KeysskhZY0xQ}{87fblLs1Rci_HVjZVzVlOQ}{127.0.0.1}{127.0.0.1:9300}
[2020-11-08T21:52:37,363][INFO ][o.e.c.s.ClusterApplierService] [G9aQRl_] new_master {G9aQRl_}{G9aQRl_qR2KeysskhZY0xQ}{87fblLs1Rci_HVjZVzVlOQ}{127.0.0.1}{127.0.0.1:9300}, reason: apply cluster state (from master [master {G9aQRl_}{G9aQRl_qR2KeysskhZY0xQ}{87fblLs1Rci_HVjZVzVlOQ}{127.0.0.1}{127.0.0.1:9300} committed version [1] source [zen-disco-elected-as-master ([0] nodes joined)]])
[2020-11-08T21:52:37,426][INFO ][o.e.g.GatewayService     ] [G9aQRl_] recovered [0] indices into cluster_state
[2020-11-08T21:52:37,762][INFO ][o.e.h.n.Netty4HttpServerTransport] [G9aQRl_] publish_address {127.0.0.1:9200}, bound_addresses {127.0.0.1:9200}, {[::1]:9200}
[2020-11-08T21:52:37,762][INFO ][o.e.n.Node               ] [G9aQRl_] started

可以看到绑定了两个端口:

9300 - 集群节点间通讯接口,接收 tcp 协议。

9200 - 客户端访问接口,接收 Http 协议。

在浏览器中访问:http://127.0.0.1:9200

{
   
  "name" : "G9aQRl_",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "ezpbwfUtTqubgqUGPvJWAg",
  "version" : {
   
    "number" : "6.2.4",
    "build_hash" : "ccec39f",
    "build_date" : "2018-04-12T20:37:28.497551Z",
    "build_snapshot" : false,
    "lucene_version" : "7.2.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

安装 kibana

什么是 Kibana

Kibana 是一个基于 Node.js 的 Elasticsearch 索引库数据统计工具,可以利用 Elasticsearch 的聚合功能,生成各种图表,如柱形图,线状图,饼;而且还提供了操作 Elasticsearch 索引数据的控制台,并且提供了一定的 API 提示。

安装

因为 Kibana 依赖于 node,需要在 windows 下先安装 Node.js,直接双击运行 node.js 的安装包:node-v10.15.0-x64.msi

安装成功后在任意 DOS 窗口输入:node -v,即可查看到 node 版本。

然后安装 kibana,版本与 Elasticsearch 保持一致,也是 6.2.4。

直接解压安装包即可:kibana-6.2.4-windows-x86_64.zip

配置运行
配置

进入安装目录下的 config 目录,修改 kibana.yml 文件的第 21 行(注释放开)。

确保 elasticsearch 服务器的地址如下:

elasticsearch.url: "http://localhost:9200"
运行

进入安装目录下的 bin 目录,双击 kibana.bat 启动。

  log   [14:05:55.974] [info][listening] Server running at http://localhost:5601
  log   [14:05:56.031] [info][status][plugin:[email protected]] Status changed from yellow to green - Ready

可以看到 Kibana 的监听端口是 5601,于是直接访问:http://127.0.0.1:5601

控制台

成功访问 Kibana 后,选择左侧的 DevTools 菜单,即可进入控制台页面。

在页面右侧,就可以输入请求,访问 Elasticsearch 了。

编写 Restful 请求;这里类似于 POST 或者浏览器,可以向 ES 发送请求,但是不用写 ES 的地址,因为在 config/kibana.yml 文件中已经定义了 ES 的地址,剩下的只需要填写对应的 uri 和参数即可:

GET _search
{
   
  "query": {
   
    "match_all": {
   }
  }
}

点击按钮执行请求,返回执行结果,显示的格式是 JSON,请求格式和响应格式一样都是 JSON 格式:

{
   
  "took": 6,
  "timed_out": false,
  "_shards": {
   
    "total": 0,
    "successful": 0,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
   
    "total": 0,
    "max_score": 0,
    "hits": []
  }
}

安装 ik 分词器

Lucene 的 IK 分词器早在 2012 年已经没有维护了,现在要使用的是在其基础上维护升级的版本,并且开发为 Elasticsearch 的集成插件了,与 Elasticsearch 一起维护升级,版本也保持一致。

https://github.com/medcl/elasticsearch-analysis-ik

安装

1、 解压 elasticsearch-analysis-ik-6.2.4.zip 后,将解压后的文件夹拷贝到 elasticsearch-6.2.4\plugins 下,并重命名文件夹为 ik

2、重新启动 ElasticSearch,即可加载 IK 分词器。

测试

在 kibana 控制台输入下面的请求:

GET /_analyze
{
   
  "analyzer": "ik_max_word",
  "text": "我是中国人"
}

运行得到结果:

{
   
  "tokens": [
    {
   
      "token": "我",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_CHAR",
      "position": 0
    },
    {
   
      "token": "是",
      "start_offset": 1,
      "end_offset": 2,
      "type": "CN_CHAR",
      "position": 1
    },
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值