Elasticsearch

文章目录

  • Elasticsearch
    • 1 全文检索
      • 1.1 数据分类
      • 1.2 结构化数据搜索
      • 1.3 非结构化数据搜索
        • 1.3.1 顺序扫描法
        • 1.3.2 全文检索
      • 1.4 全文检索原理
        • 1.4.1 索引里面究竟存些什么
        • 1.4.2 如何创建索引
    • 2 Lucene实现全文检索
      • 2.1 Apache Lucene概述
        • 2.1.1 Lucene介绍
        • 2.1.2 Lucene适用场景
        • 2.1.3 Lucene功能
        • 2.1.4 Lucence架构
      • 2.2 Lucene基本概念
        • 2.2.1 Index(索引)
        • 2.2.2 Document(文档)
        • 2.2.3 Field(字段)
        • 2.2.4 Term和Term Dictionary
        • 2.2.5 Segment(段)
        • 2.2.6 Sequence Number(序列号)
      • 2.3 全文检索案例解析
        • 2.3.1 索引和搜索流程图
        • 2.3.2 全文检索案例分析
      • 2.4 倒排索引
    • 3 Lucene实战
      • 3.1 需求说明
      • 3.2 准备开发环境
        • 3.2.1 准备数据
        • 3.2.2 项目搭建
        • 2.2.3 查询接口开发
      • 3.3 创建索引
        • 3.3.1 创建索引实现
        • 3.3.2 索引解析
      • 3.4 创建索引
      • 3.5 中文分词器的使用
      • 3.5 Lucene小结
    • 4 ELK概述
      • 4.1 ELK简介
      • 4.2 Elasticsearch简介
        • 4.2.1 Elasticsearch概述
        • 4.2.2 Elasticsearch功能
      • 4.3 Elasticsearch安装配置和启动
        • 4.3.1 Elasticsearch版本
        • 4.3.2 Elasticsearch安装配置
        • 4.3.3 Elasticsearch启动
        • 4.3.4 Elasticsearch启动失败
      • 4.4 安装Kibana
        • 4.4.1 什么是Kibana
        • 4.4.2 Kibana下载安装
        • 4.3.3 Kibana配置和启动
        • 4.3.4 Kibana控制台
      • 4.3 安装Head插件
        • 4.5.1 elasticsearch-head简介
        • 4.5.2 elasticsearch-head安装
      • 4.5 配置IK分词器
        • 4.6.1 IK分词器安装
        • 4.6.2 分词器属性
    • 5 使用Kibana对索引库操作
      • 5.1 基本概念
        • 5.1.1 节点(node)
        • 5.1.2 集群(cluster)
        • 5.1.3 分片(shard)
        • 5.1.4 副本(replica)
        • 5.1.5 文档(document)
        • 5.1.6 类型(type)
        • 5.1.7 索引(index)
        • 5.1.8 映射(mapping)
      • 5.2 索引基本操作
        • 5.2.1 创建索引
        • 5.2.2 查看索引库
        • 5.2.3 删除索引
    • 6 使用Kibana对类型及映射操作
      • 6.1 创建字段映射
        • 6.1.1 创建字段映射语法
        • 6.1.2 创建字段映射案例
      • 6.2 查看映射关系
      • 6.3 字段数据类型解析
        • 6.3.1 字段数据类型分类
      • 6.4 一次创建索引库和类型
    • 7 使用Kibana对文档操作
      • 7.1 新增文档
      • 7.2 查询文档
      • 7.3 修改文档数据
      • 7.4 删除文档数据
      • 7.5 智能判断
      • 7.6 动态模板
    • 8 查询
      • 8.1 基本查询操作
        • 8.1.1 查询所有 match_all
        • 8.1.2 匹配查询match
        • 8.1.3 词条匹配term
        • 8.1.4 布尔组合bool
        • 8.1.5 范围查询range
        • 8.1.6 模糊查询fuzzy
      • 8.2 结果过滤
        • 8.2.1 直接指定字段
        • 8.2.2 指定includes和exclude
      • 8.3 过滤filter
        • 8.3.1 条件查询中进行过滤
        • 8.3.2 无查询条件直接过滤
      • 8.4 排序
        • 8.4.1 单个字段排序
        • 8.4.2 多个字段排序
      • 8.5 分页
      • 8.6 高亮
    • 9 聚合aggregations
    • 10 Elasticsearch集群
      • 10.1 单节点的问题
        • 10.2 集群的结构
        • 10.2.1 数据分片
        • 10.2.2 数据备份
      • 10.3 集群搭建
        • 10.3.1 搭建集群设计
        • 10.3.2 搭建集群实现
        • 10.3.3 head访问集群
        • 10.3.4 集群创建索引

学习目标:

  • 全文检索概念
  • Lucene实现全文检索的流程
  • Lucene实战案例
  • Elasticsearch介绍和安装
  • Kibana工具操作ES(对索引库操作、类型、文档)
  • 查询、聚合操作
  • Elasticsearch集群和分布式
  • Spring Data Elasticsearch的Spring数据操作封装依赖

1 全文检索

1.1 数据分类

当今信息爆炸的时代,信息每天都在以惊人的速度增长。我们生活中的数据总体分为两种:结构化数据和非结构化数据。

  • 结构化数据:指具有固定格式或有限长度的数据,如数据库、 元数据等。针对结构化数据的搜索,例如对数据库的搜索、可以使用SQL语包。再如对元数据的搜索,例如Windows中对文件名类型和修改时间进行搜索等。

  • 非结构化数据:指不定长或无固定格式的数据,如邮件、word文档等磁盘上的文件。对非结构化数据的搜索,例如Windows中对文件内容的搜索、Linux中grep命令,以及使用Google或百度来搜索内容都属于对全文数据的搜索。

通过调查发现,在企业存储的海量信息中,结构化数据仅占数据信息总量的15%,而非结构化数据却占数据信息总量的85%。有序地存储、管理并挖掘非结构化数据的利用价值是目前全球一切成功企业提高竞争力和生产力的主要手段。

1.2 结构化数据搜索

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

因为结构的数据它有固定的长度、固定的数据类型、固定的格式…在数据查询的时候方式是固定。

1.3 非结构化数据搜索

1.3.1 顺序扫描法

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

假设你有一个100G硬盘,如果想在上面找到一个内容包含某字符串的文件,不花他几个小时,怕是做不到。Linux系统下的grep命令也是这一种方式。大家可能觉得这种方法比较原始,但对于小数据量的文件,这种方法还是最直接,最方便的。但是对于大量的文件,这种方法就很慢了。

1.3.2 全文检索

1.全文检索发展历史

全文检索是20世纪末产生的一种新的信息检索技术。经过几十年的发展,特别是以计算机技术为代表的新一代信息技术应用,使全文检索从最初的字符串匹配和简单的布尔逻辑检索技术演进到能对超大文本、语音、图像、活动影像等非结构化数据进行综合管理的复合技术。

2.全文检索介绍

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

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

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

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

3.全文检索构成

全文检索大体分两个过程,索引创建(Indexing)和搜索索引(Search)。

索引创建:将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程。

搜索索引:就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程。

于是全文检索就存在三个重要问题:

  • 索引结构?(Index)

  • 如何创建索引?(Indexing)

  • 如何对索引进行搜索?(Search)

下面我们顺序对每个问题进行研究。

1.4 全文检索原理

1.4.1 索引里面究竟存些什么

索引里面究竟需要存些什么呢?首先我们来看为什么顺序扫描的速度慢。其实是由于我们想要搜索的信息和非结构化数据中所存储的信息不一致造成的。

非结构化数据中所存储的信息是每个文件包含哪些字符串,也即已知文件,欲求字符串相对容易,也即是从文件到字符串的映射。而我们想搜索的信息是哪些文件包含此字符串,也即已知字符串,欲求文件,也即从字符串到文件的映射。两者恰恰相反。于是如果索引总能够保存从字符串到文件的映射,则会大大提高搜索速度。由于从字符串到文件的映射是文件到字符串映射的反向过程,于是保存这种信息的索引称为反向索引。

有人可能会说,全文检索的确加快了搜索的速度,但是多了索引的过程,两者加起来不一定比顺序扫描快多少。的确,加上索引的过程,全文检索不一定比顺序扫描快,尤其是在数据量小的时候更是如此。而对一个很大量的数据创建索引也是一个很慢的过程。

然而两者还是有区别的,顺序扫描是每次都要扫描,而创建索引的过程仅仅需要一次,以后便是一劳永逸的了, 每次搜索,创建索引的过程不必经过,仅仅搜索创建好的索引就可以了。这也是全文搜索相对于顺序扫描的优势之一:一次索引,多次使用。

1.4.2 如何创建索引

全文检索的索引创建过程一般有以下几步:

  • 一些要索引的原文档(Document)。
  • 将原文档传给分词组件(Tokenize)。
  • 将得到的词元(Token)传给语言处理组件(Linguistic Processor)。
  • 将得到的词(Term)传给索引组件(Indexer)。

简而言之,先建立索引(Indexing),再根据索引进行搜索(Search)。

2 Lucene实现全文检索

2.1 Apache Lucene概述

Lucene官网地址:https://lucene.apache.org。

Lucene下载地址:http://archive.apache.org/dist/lucene/java。

2.1.1 Lucene介绍

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

2.1.2 Lucene适用场景

这项技术几乎适用于任何需要结构化搜索、全文搜索、分面、跨高维向量的最近邻搜索、拼写纠正或查询建议的应用程序。

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

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

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

2.1.3 Lucene功能

Lucene通过一些简单的API调用来完成强大的搜索功能的实现。

1.可扩展的高性能索引

  • 在现代硬件上超过800GB/小时

  • 小RAM要求——只有1MB堆

  • 增量索引与批量索引一样快

  • 索引大小大约为索引文本大小的20-30%

2.强大、准确、高效的搜索算法

  • 排名搜索——最好的结果首先返回

  • 许多强大的查询类型:短语查询、通配符查询、邻近查询、范围查询等

  • 现场搜索(例如标题、作者、内容)

  • 高维向量的最近邻搜索

  • 按任何字段排序

  • 合并结果的多索引搜索

  • 允许同时更新和搜索

  • 灵活的刻面、突出显示、连接和结果分组

  • 快速、节省内存和容错的建议器

  • 可插拔排名模型,包括向量空间模型和Okapi BM25

  • 可配置的存储引擎(编解码器)

3.跨平台解决方案

  • 可作为Apache许可证下的开源软件,它允许您在商业和开源程序中使用Lucene
  • 100%纯Java
  • 其他可用的与索引兼容的编程语言的实现

2.1.4 Lucence架构

结构化数据的搜索和非结构数据的搜索的对比分析:

搜索的应用程序和Lucene之间的关系,内部的流程:

2.2 Lucene基本概念

Lucene存储架构实现图:

2.2.1 Index(索引)

类似数据库的表的概念,但是与传统表的概念会有很大的不同。传统关系型数据库或者NoSQL数据库的表,在创建时至少要定义表的Scheme,定义表的主键或列等,会有一些明确定义的约束。而Lucene的Index,则完全没有约束。Lucene的Index可以理解为一个文档收纳箱,你可以往内部塞入新的文档,或者从里面拿出文档,但如果你要修改里面的某个文档,则必须先拿出来修改后再塞回去。这个收纳箱可以塞入各种类型的文档,文档里的内容可以任意定义,Lucene都能对其进行索引。

2.2.2 Document(文档)

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

一个Index内会包含多个Document。写入Index的Document会被分配一个唯一的ID,即Sequence Number(序列号,更多被叫做DocId)。

2.2.3 Field(字段)

一个Document会由一个或多个Field组成,Field是Lucene中数据索引的最小定义单位。Lucene提供多种不同类型的Field,例如StringField、TextField、LongFiled或NumericDocValuesField等,Lucene根据Field的类型(FieldType)来判断该数据要采用哪种类型的索引方式(Invert Index、Store Field、DocValues或N-dimensional等)。

例如,一篇文章可以包含“标题”、“正文”、“最后修改时间”等信息域,这些信息域就是通过Field在Document中存储的。

Field有两个属性可选:存储和索引。通过存储属性你可以控制是否对这个Field进行存储;通过索引属性你可以控制是否对该Field进行索引。

如果对标题和正文进行全文搜索,所以我们要把索引属性设置为真,同时我们希望能直接从搜索结果中提取文章标题,所以我们把标题域的存储属性设置为真。但是由于正文域太大了,我们为了缩小索引文件大小,将正文域的存储属性设置为假,当需要时再直接读取文件;我们只是希望能从搜索解果中提取最后修改时间,不需要对它进行搜索,所以我们把最后修改时间域的存储属性设置为真,索引属性设置为假。上面的三个域涵盖了两个属性的三种组合,还有一种全为假的没有用到,事实上Field不允许你那么设置,因为既不存储又不索引的域是没有意义的。

2.2.4 Term和Term Dictionary

Lucene中索引和搜索的最小单位,一个Field会由一个或多个Term组成,Term是由Field经过Analyzer(分词)产生。Term Dictionary即Term词典,是根据条件查找Term的基本索引。

Term由两部分组成:它表示的词语和这个词语所出现的Field的名称。

2.2.5 Segment(段)

一个Index会由一个或多个sub-index构成,sub-index被称为Segment。Lucene的Segment设计思想,与LSM类似但又有些不同,继承了LSM中数据写入的优点,但是在查询上只能提供近实时而非实时查询。

Lucene中的数据写入会先写内存的一个Buffer(类似LSM的MemTable,但是不可读),当Buffer内数据到一定量后会被Flush成一个Segment,每个Segment有自己独立的索引,可独立被查询,但数据永远不能被更改。这种模式避免了随机写,数据写入都是Batch和Append,能达到很高的吞吐量。Segment中写入的文档不可被修改,但可被删除,删除的方式也不是在文件内部原地更改,而是会由另外一个文件保存需要被删除的文档的DocID,保证数据文件不可被修改。Index的查询需要对多个Segment进行查询并对结果进行合并,还需要处理被删除的文档,为了对查询进行优化,Lucene会有策略对多个Segment进行合并,这点与LSM对SSTable的Merge类似。

Segment在被Flush或Commit之前,数据保存在内存中,是不可被搜索的,这也就是为什么Lucene被称为提供近实时而非实时查询的原因。读了它的代码后,发现它并不是不能实现数据写入即可查,只是实现起来比较复杂。原因是Lucene中数据搜索依赖构建的索引(例如倒排依赖Term Dictionary),Lucene中对数据索引的构建会在Segment Flush时,而非实时构建,目的是为了构建最高效索引。当然它可引入另外一套索引机制,在数据实时写入时即构建,但这套索引实现会与当前Segment内索引不同,需要引入额外的写入时索引以及另外一套查询机制,有一定复杂度。

2.2.6 Sequence Number(序列号)

Sequence Number(后面统一叫DocId)是Lucene中一个很重要的概念,数据库内通过主键来唯一标识一行记录,而Lucene的Index通过DocId来唯一标识一个Doc。不过有几点要特别注意:

1.DocId实际上并不在Index内唯一,而是Segment内唯一,Lucene这么做主要是为了做写入和压缩优化。那既然在Segment内才唯一,又是怎么做到在Index级别来唯一标识一个Doc呢?方案很简单,Segment之间是有顺序的,举个简单的例子,一个Index内有两个Segment,每个Segment内分别有100个Doc,在Segment内DocId都是0-100,转换到Index级的DocId,需要将第二个Segment的DocId范围转换为100-200。

2.DocId在Segment内唯一,取值从0开始递增。但不代表DocId取值一定是连续的,如果有Doc被删除,那可能会存在空洞。

3.一个文档对应的DocId可能会发生变化,主要是发生在Segment合并时。

Lucene内最核心的倒排索引,本质上就是Term到所有包含该Term的文档的DocId列表的映射。所以Lucene内部在搜索的时候会是一个两阶段的查询,第一阶段是通过给定的Term的条件找到所有Doc的DocId列表,第二阶段是根据DocId查找Doc。Lucene提供基于Term的搜索功能,也提供基于DocId的查询功能。

DocId采用一个从0开始底层的Int32值,是一个比较大的优化,同时体现在数据压缩和查询效率上。例如数据压缩上的Delta策略、ZigZag编码,以及倒排列表上采用的SkipList等,这些优化后续会详述。

2.3 全文检索案例解析

2.3.1 索引和搜索流程图

索引和搜索流程图见下

1.绿色部分表示索引的过程,对要索引的原始内容进行构建一个索引库的过程(子索引)。

原始文档(内容) => 采集文档数据 => 创建文档 => 分析文档 => 索引文档

2.红色部分表搜索索引的过程,从索引库中搜索内容。

用户通过搜索界面 => 创建查询(创建索引) => 执行搜索 => 从索引库中搜索数据 => 渲染搜索的结果

2.3.2 全文检索案例分析

TODO…

2.4 倒排索引

TODO…

3 Lucene实战

3.1 需求说明

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

3.2 准备开发环境

3.2.1 准备数据

1.通过Navicat Premium软件运行课前资料中的job_info.sql脚本。完成初始化数据的准备工作。

(1) 在Navicat Premium工具中的MySQL数据库【连接名】上右键,选择【运行SQL文件…】选项,然后后运行job_info.sql文件。

(2) 数据库脚本文件job_info.sql中的内容见下。

CREATE DATABASE `es_db` CHARACTER SET utf8mb4;
USE `es_db`;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 DEFAULT NULL COMMENT '公司信息',`job_name` varchar(100) DEFAULT NULL COMMENT '职位名称',`job_addr` varchar(50) DEFAULT NULL COMMENT '工作地点',`job_info` mediumtext DEFAULT NULL 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=utf8mb3 COMMENT='招聘信息';INSERT INTO `job_info` (`id`, `company_name`, `company_addr`, `company_info`, `job_name`, `job_addr`, `job_info`, `salary_min`, `salary_max`, `url`, `time`)
VALUES (1397, '北京中认环宇信息安全技术有限公司', '北京市丰台区南四环西路188号9区8号楼', '北京中认环宇信息安全技术有限公司(简称CQCCA)是由中国质量认证中心投资...', ' JAVA软件开发程师 (职位编号:002)', '北京-丰台区', '参与产品需求分析、系统设计工作...', 120000, 180000, 'https://jobs.51job.com/beijing-ftq/101555054.html?s=01&t=5', '2022-02-26');# 后面省略10000+条INSERT插入语句

2.如果在运行job_info.sql脚本时提示1153 - Got a packet bigger than ‘max_allowed_packet’ bytes出错,解决方案见下:

(1) 查看max_allowed_packet变量默认值。原因是我安装的MySQL的默认配置为16MB,而导入的文件数据大于默认配置所以出错。

# 16777216 / 1024 / 1024 = 16M
SHOW VARIABLES LIKE '%max_allowed_packet%';

(2) 修改max_allowed_packet变量的默认值。需要重启Navicat Premium软件。

# 设置成512M
SET GLOBAL max_allowed_packet = 524288000;

(3) 再次使用Navicat Premium软件运行job_info.sql脚本。便可执行成功。

3.2.2 项目搭建

1.SpringBoot项目,项目的名称:lucene-demo。

2.使用Lucene技术,需要导入对应的依赖文件。

3.连接数据,添加数据的配置文件后缀改成yml。

4.搭建项目的mvc接口。

2.2.3 查询接口开发

1.创建一个JobInfo的实体类。

package com.yx.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;@Data
@TableName("job_info")
public class JobInfo implements Serializable {// 表示将属性id映射到数据表中的主键上,并且指定该数据的取值为自增@TableId(type = IdType.AUTO)private Long id; // id属性建议使用包装类定义private String companyName;private String companyAddr;private String companyInfo;private String jobName;private String jobAddr;private String jobInfo;private int salaryMin;private int salaryMax;private String url;private String time;
}

2.创建一个JobInfoMapper接口,实现MyBatis-Plus的BaseMapper。

package com.yx.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yx.pojo.JobInfo;public interface JobInfoMapper extends BaseMapper<JobInfo> {}

3.添加该接口的扫描。

@MapperScan("com.yx.mapper")

4.JobInfoService接口,更具id查询数据、查询所有数据。

package com.yx.service;
import com.yx.pojo.JobInfo;
import java.util.List;public interface JobInfoService {// 根据查询公司职位信息JobInfo selectById(Long id);// 查询所有的职位信息List<JobInfo> selectAll();
}

5.JobInfoServiceImpl实现类。实现以上两个抽象方法。

6.controller层开发。

package com.yx.controller;import com.yx.pojo.JobInfo;
import com.yx.service.JobInfoService;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
@RequestMapping("job-info")
public class JobInfoController {@Autowiredprivate JobInfoService jobInfoService;@RequestMapping("query/{id}")public JobInfo getJobInfoById(@PathVariable Long id) {return jobInfoService.selectById(id);}@RequestMapping("query")public List<JobInfo> getJobInfos() {return jobInfoService.selectAll();}
}

3.3 创建索引

3.3.1 创建索引实现

LuceneTests测试类。通过Lucene技术来创建源数据的索引。

package com.yx.lucene;import com.yx.pojo.JobInfo;
import com.yx.service.JobInfoService;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.LiveIndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.io.File;
import java.io.IOException;
import java.util.List;@SpringBootTest
public class LuceneTests {@Autowiredprivate JobInfoService jobInfoService;// 创建索引@Testpublic void createIndex() throws IOException {// 1.指定将来创建的索引存储的磁盘位置// FSDirectory.open(new File("D:\\lucen\\index"));Directory directory = FSDirectory.open(new File("/Users/yuanxin/Documents/lucene/index"));// 2.配置版本和分词器Analyzer analyzer = new StandardAnalyzer(); // 创建一个标准分词器(将一个字符串进行拆分,形成一个一个词<term>)// 指定索引的写入版本/** 参数1:表示指定索引写出的版本,最新* 参数2:表示建立索引所使用的分词器*/IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);// 3.创建一个用来创建索引的对象/** 参数1:表示目标索引数据写出的位置* 参数2:表示索引的配置信息(索引分词器、索引的版本)*/IndexWriter indexWriter = new IndexWriter(directory, config);// 删除所有的原来被创建出来的索引indexWriter.deleteAll();// 4.获取目标数据(获取源数据)List<JobInfo> jobInfoList = jobInfoService.selectAll();// 在集合中有多少条数据就应该构建多少个Document(Lucene中的概念)for (JobInfo jobInfo : jobInfoList) {// 创建一个文档对象,映射数据库一行记录Document document = new Document();// 字段名(Field: 域名)、字段值(value: 数据源),要不要存储(Store)// add()方法表示向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 IntField("salaryMin", jobInfo.getSalaryMin(), Field.Store.YES));document.add(new IntField("salaryMax", jobInfo.getSalaryMax(), Field.Store.YES));document.add(new StringField("url", jobInfo.getUrl(), Field.Store.YES));document.add(new StringField("time", jobInfo.getTime(), Field.Store.YES));  // ES技术 - 底层就是Lucene// 保存到索引库中(写出到的索引文件中)indexWriter.addDocument(document);}// 关闭资源indexWriter.close();}
}

3.3.2 索引解析

1.Index索引

在Lucene中一个索引是存放在一个文件夹中的。如下图所示。同一文件夹中的所有的文件构成一个Lucene索引。

2.Segment段

按层次保存了索引到词的包含关系:索引(Index) => 段(segment) => 文档(Document) => 域(Field) => 词(Term)。

即此索引包含了哪些段,每个段包含了哪些文档,每个文档包含了哪些域,每个域包含了哪些词。

一个索引可以包含多个段,段与段之间是独立的,添加新文档可以生成新的段,不同的段可以合并。

如上图中,具有相同前缀文件的属同一个段,图中共一个段(以"_0"开头的文件)。

segments_1和segments.gen是段的元数据文件,也即它们保存了段的属性信息。

3.Filed的特性

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

是否分词(tokenized):

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

  • 否:不做分词处理。如:订单编号、身份证号,是一个整体,分词以后就失去了意义,故不需要分词。

是否索引(indexed):

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

  • 否:不索引。如:商品图片路径, 不会作为查询条件,不需要建立索引。

是否存储(stored):

  • 是:将Field值保存到Document中。如:商品名称,商品价格。凡是将来在搜索结果页面展现给用户的内容,都需要存储。

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

4.Filed类型

常用的Field类型见下表:

3.4 创建索引

将所创建出来的索引进行查询,获取到索引中数据。

// 查询索引
@Test
public void queryIndex() throws IOException {// 1.指定索引文件存储的位置Directory directory = FSDirectory.open(new File("/Users/yuanxin/Documents/lucene/index"));// 2.创建一个用来读取索引的对象IndexReaderIndexReader indexReader = DirectoryReader.open(directory);// 3.创建一个用来查询索引的对象IndexSearch。接收一个IndexReader类型的参数,才能去指定的路径下读取索引文件的数据IndexSearcher indexSearcher = new IndexSearcher(indexReader);// 4.执行索引的查询操作// 4.1 通过字段名来获取字段的取值// 创建一个Term表示要查询的词条Term term = new Term("companyName", "北京");// 根据指定的term词条进行查询Query query = new TermQuery(term);// 4.2 通过indexSearcher对象来完成词条的查询操作TopDocs topDocs = indexSearcher.search(query, 100); // 表示最多返回100条指定term的数据// 获取topDocs文档中查询出了多少条数据System.out.println("查询的总记录是:" + topDocs.totalHits); // 查询的总数据量// 5.获取命中的文档。再去获取文档中的细节信息(公司的名称、公司的地址、公司提供的职位....)// 获取所有命中的文档并转发带个ScoreDocs[]数组中// ScoreDoc对象描述了Document对象的信息ScoreDoc[] scoreDocs = topDocs.scoreDocs; // 数组中的每一个元素表示一个ScoreDoc对象for (ScoreDoc scoreDoc : scoreDocs) {// 获取文档的idint docID = scoreDoc.doc;// 在索引库中通过文档的id查询文档Document document = indexSearcher.doc(docID);// 获取Document文档对象中的数据(Filed字段的数据内容)System.out.println("id: " + document.get("id"));System.out.println("companyName: " + document.get("companyName"));System.out.println("companyAddr: " + document.get("companyAddr"));System.out.println("-----------------------------------------------------------");}
}

3.5 中文分词器的使用

1.导入IK分词器的依赖。

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

2.删除原来index文件夹。

3.重新常见一个IK分词器对象。

// 创建一个标准分词器(将一个字符串进行拆分,形成一个一个词<term>)
// Analyzer analyzer = new StandardAnalyzer();
Analyzer analyzer = new IKAnalyzer();

4.重新运行创建索引方法,然后再去查看“北京”。

3.5 Lucene小结

如果使用原生Lucene技术来完成索引的实现,需要编写原生的全文检索相关的大量冗余的代码。市面上就有很多非常成熟的基于Lucene的项目或者软件,目前市面上主流搜索引擎技术:

  • ES:市场占用比最大的。学习的是ES。
  • Solr:市场占比较小。

4 ELK概述

4.1 ELK简介

Elastic Stack核心产品包括Elasticsearch、Kibana、Beats和Logstash(也称为ELK)等等。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。

4.2 Elasticsearch简介

Elasticsearch官网地址:https://www.elastic.co/cn/elasticsearch。

4.2.1 Elasticsearch概述

Elasticsearch是一个分布式、RESTful风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。 作为Elastic Stack的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。

1.查询和分析

通过Elasticsearch,您能够执行及合并多种类型的搜索(结构化数据、非结构化数据、地理位置、指标),搜索方式随心而变。

找到与查询最匹配的10个文档并不困难。但如果面对的是十亿行日志,又该如何解读呢?Elasticsearch聚合让您能够从大处着眼,探索数据的趋势和规律。

2.速度

快速获得结果。如果您能够立即获得答案,您与数据的关系就会发生变化。这样您就有条件进行迭代并涵盖更大的范围。

强大的设计。但是要达到这样的速度并非易事。我们通过有限状态转换器实现了用于全文检索的倒排索引,实现了用于存储数值数据和地理位置数据的BKD树,以及用于分析的列存储。

无所不包。而且由于每个数据都被编入了索引,因此您再也不用因为某些数据没有索引而烦心。您可以用快到令人惊叹的速度使用和访问您的所有数据。

3.可扩展性

原型环境和生产环境可无缝切换;无论Elasticsearch是在一个节点上运行,还是在一个包含300个节点的集群上运行,您都能够以相同的方式与Elasticsearch进行通信。

它能够水平扩展,每秒钟可处理海量事件,同时能够自动管理索引和查询在集群中的分布方式,以实现极其流畅的操作。

4.相关度

基于各项元素(从词频或新近度到热门度等)对搜索结果进行排序。将这些内容与功能进行混搭,以优化向用户显示结果的方式。

而且,由于我们的大部分用户都是真实的人,Elasticsearch具备齐全功能,可以处理包括各种复杂情况(例如拼写错误)在内的人为错误。

5.弹性

硬件故障。网络分割。Elasticsearch为您检测这些故障并确保您的集群(和数据)的安全性和可用性。通过跨集群复制功能,辅助集群可以作为热备份随时投入使用。Elasticsearch运行在一个分布式的环境中,从设计之初就考虑到了这一点,目的只有一个,让您永远高枕无忧。

4.2.2 Elasticsearch功能

Elasticsearch是一个分布式的RestFul搜索和分析的引擎,可以来集中的存储数据,以便对形形色色、一定规模的数据进行搜索、索引和分析。

4.3 Elasticsearch安装配置和启动

4.3.1 Elasticsearch版本

目前Elasticsearch版本已经跟新到了8.x系列。企业中使用比较多的是6.x系列版本,所以我们的课程是基于Elasticsearch 6.2.4版本进行讲解。JDK版本要求是1.8及以上的版本。

安装在真机上(Windows、MAC上)。

4.3.2 Elasticsearch安装配置

1.软件安装在一个非中文没有空格的目录下。

2.在安装ES的时候使用会产生数据。Elasticsearch默认端口是9000。

  • 数据:data文件夹下
  • 日志:logs文件夹下
es-9000- data  # 放ES的数据- logs  # 放ES日志数据

3.配置ES的数据目录存放位置。ES安装目录config/elasticsearch.yml文件,维护的就是当前ES的的配置信息。

# Mac系统下配置方式
path.data: /Users/yuanxin/Documents/ProgramSoftware/es-config/es-9000/data
path.logs: /Users/yuanxin/Documents/ProgramSoftware/es-config/es-9000/logs# Windows系统下配置方式
path.data: D:\es-config\es-9000\data
path.logs: D:\es-config\es-9000\logs

4.3.3 Elasticsearch启动

1.Elasticsearch安装目录bin/elasticsearch文件,就是启动的脚本文件。

# MAC
./elasticsearch# Windows(双击)
elasticsearch.bat

2.elasticsearch端口:

  • 9300:集群节点间通信接口,接收的是TCP协议数据。
  • 9200:客户端访问ES的端口,接收的协议是HTTP。

3.在浏览器访问localhost:9200。

4.3.4 Elasticsearch启动失败

elasticsearch启动闪退:

1.data和logs目录是否配置正确
2.内存不足导致

1.config目录:jvm.options文件。

  • Xms表示程序启动时占用内存的大小。

  • Xmx表示运行期间最大的内存占用大小。

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

2.调整参数大小

-Xms256m
-Xmx256m

4.4 安装Kibana

4.4.1 什么是Kibana

Kibana是一个基于Node.js的Elasticsearch索引库数据统计工具,可以利用Elasticsearch的聚合功能, 生成各种图表,如柱形图、线状图及饼图等。

而且还提供了操作Elasticsearch索引数据的控制台,并且提供了一定的API提示,非常有利于我们学习Elasticsearch的语法。

4.4.2 Kibana下载安装

1.Node.js安装

因为Kibana依赖于Node.js(Vue-CLI.pdf),需要在系统上先安装Node.js。具体Node.js的安装步骤在这里不展开讲解。

2.Kibana安装

1.访问https://www.elastic.co/cn/downloads/past-releases/kibana-6-2-4网址,根据操作系统下载对应的Kibana安装包。注意下载的Kibana版本和Elasticsearch版本保持一致。

  • Mac版:kibana-6.2.4-darwin-x86_64.tar.gz
  • Windows版:kibana-6.2.4-windows-x86_64.zip

2.将下载的Kibana压缩包解压到任意一个没有中文没有空格的目录下。

4.3.3 Kibana配置和启动

1.在Kibana中配置ES的连接地址。config/kibana.yml文件中进行配置。

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

2.bin/kibana文件。

注意:必须要保证ES先启动,然后再启动Kibana。

# Win
kibana.bat# Mac
./kibana

3.http://localhost:5601访问Kibana。

4.3.4 Kibana控制台

Kibana控制台用来输入ES的语法,来远程操作ES。

4.3 安装Head插件

4.5.1 elasticsearch-head简介

elasticsearch-head是一个界面化的集群操作和管理工具,可以对集群进行傻瓜式操作。你可以通过插件把它集成到Elasticsearch(首选方式)也可以安装成一个独立webapp。

elasticsearch-head主要有三个方面的操作:

  • 显示集群的拓扑,并且能够执行索引和节点级别操作。

  • 搜索接口能够查询集群中原始JSON或表格格式的检索数据。

  • 能够快速访问并显示集群的状态。

4.5.2 elasticsearch-head安装

elasticsearch-head的安装基于谷歌浏览器进行介绍。

1.通过https://fifiles.cnblogs.com/fifiles/sanduzxcvbnm/elasticsearch-head.7z网址下载elasticsearch-head.7z压缩包。

2.将elasticsearch-head.7z解压到任意一个没有中文没有空格的目录下。

3.在谷歌浏览器中点击【扩展程序】-【加载已解压的压缩程序】选项,找到elasticsearch-head文件夹,点击打开即可进行安装。

4.访问chrome-extension://ffmkiejjmecolpfloofpjologoblkegm/elasticsearch-head/index.html地址将看到以下窗口表示安装成功。

4.5 配置IK分词器

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

IK分词器下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases。

4.6.1 IK分词器安装

1.访问https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v6.2.4地址下载IK分词器zip安装包。

2.将下载的elasticsearch-analysis-ik-6.2.4.zip的压缩包解压到elasticsearch-6.2.4/plugins/目录下,并将解压后的目录重命名成analysis-ik。

3.重新启动Elasticsearch服务即可加载IK分词器,然后再重启Kibana服务。

4.重启ES、Kibana重启。

4.6.2 分词器属性

ik_max_word和ik_smart有什么区别?

ik_max_word:会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、人、民、共和国、共和、和、国国、国歌”,会穷尽各种可能的组合,适合词条(Term)查询。

ik_smart:会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国、国歌”,适合词组(Phrase)查询。

问题:

  • Node.js没有安装导致Kibana启动异常
  • 分词器使用如果不能正常的进行分词,IK插件解压不能重现多层目录的嵌套

5 使用Kibana对索引库操作

5.1 基本概念

5.1.1 节点(node)

一个节点是一个Elasticsearch的实例。

在服务器上启动Elasticsearch之后,就拥有了一个节点。如果在另一台服务器上启动Elasticsearch,这就是另一个节点。甚至可以通过启动多个Elasticsearch进程,在同一台服务器上拥有多个节点。

5.1.2 集群(cluster)

多个协同工作的Elasticsearch节点的集合被称为集群。

在多节点的集群上,同样的数据可以在多台服务器上传播,这有助于性能,这同样有助于稳定性,如果每个分片至少有一个副本分片,那么任何一个节点宕机后,Elasticsearch依然可以进行服务,返回所有数据。

但是它也有缺点:必须确定节点之间能够足够快速地通信,并且不会产生脑裂效应(集群的2个部分不能彼此交流,都认为对方宕机了)。

5.1.3 分片(shard)

索引可能会存储大量数据,这些数据可能超过单个节点的硬件限制。例如,十亿个文档的单个索引占用了1TB的磁盘空间,可能不适合单个节点的磁盘,或者可能太慢而无法单独满足来自单个节点的搜索请求。

为了解决此问题,Elasticsearch提供了将索引细分为多个碎片的功能。创建索引时,只需定义所需的分片数量即可。每个分片本身就是一个功能齐全且独立的“索引”,可以托管在群集中的任何节点上。

分片很重要,主要有两个原因:

  • 它允许您水平分割/缩放内容量。

  • 它允许您跨碎片(可能在多个节点上)分布和并行化操作,从而提高性能/吞吐量。

分片如何分布以及其文档如何聚合回到搜索请求中的机制完全由Elasticsearch管理,并且对您作为用户是透明的。

在随时可能发生故障的网络/云环境中非常有用,强烈建议您使用故障转移机制,以防碎片/节点因某种原因脱机或消失。为此,Elasticsearch允许您将索引分片的一个或多个副本制作为所谓的副本分片(简称副本)。

5.1.4 副本(replica)

分片处理允许用户推送超过单机容量的数据至Elasticsearch集群。副本则解决了访问压力过大时单机无法处理所有请求的问题。

分片可以是主分片,也可以是副本分片,其中副本分片是主分片的完整副本。副本分片用于搜索,或者是在原有的主分片丢失后成为新的主分片。

注意:可以在任何时候改变每个分片的副本分片的数量,因为副本分片总是可以被创建和移除的。这并不适用于索引划分为主分片的数量,在创建索引之前,必须决定主分片的数量。过少的分片将限制可扩展性,但是过多的分片会影响性能。默认设置的5份是一个不错的开始。

5.1.5 文档(document)

Elasticsearch是面向文档的,这意味着索引和搜索数据的最小单位是文档。在Elasticsearch中文档有几个重要的属性。

  • 它是自我包含的。一篇文档同时包含字段和它们的取值。

  • 它可以是层次的。文档中还包含新的文档,字段还可以包含其他字段和取值。例如,“location”字段可以同时包含“city”和“street“两个字段。

  • 它拥有灵活的结构。文档不依赖于预先定义的模式。并非所有的文档都需要拥有相同的字段,它们不受限于同一个模式。

5.1.6 类型(type)

类型是文档的逻辑容器,类似于表格是行的容器。在不同的类型中,最好放入不同结构的文档。例如,可以用一个类型定义聚会时的分组,而另一个类型定义人们参加的活动。

5.1.7 索引(index)

索引是映射类型的容器。一个Elasticsearch索引是独立的大量的文档集合。每个索引存储在磁盘上的同组文件中,索引存储了所有映射类型的字段,还有一些设置。

5.1.8 映射(mapping)

所有文档在写入索引前都将被分析,用户可以设置一些参数,决定如何将输入文本分割为词条,哪些词条应该被过滤掉,或哪些附加处理有必要被调用(比如移除HTML标签)。这就是映射扮演的角色,存储分析链所需的所有信息。

Elasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与MySQL类似的。对比关系:

索引库(Indices) => 数据库(Database)类型(Type) => 数据表(Table)文档(Document) => 行(Row)域字段(Field) => 列(Columns)映射配置(Mappings) => 每个列的约束(类型、长度)

详细说明:

概念 说明
类型(Type) 类型是模拟MySQL中的Table概念,一个索引库下可以有不同类型的索引(目前6.X以后的版本只能有一个类型),类似数据库中的表概念。数据库表中有表结构,也就是表中每个字段的约束信息;索引库的类型中对应表结构的叫做映射(Mapping),用来定义每个字段的约束
文档(Document) 存入索引库原始的数据。比如每一条商品信息,就是一个文档
字段(Field) 文档中的属性
映射配置(Mappings) 字段的数据类型、属性、是否索引、是否存储等特性

5.2 索引基本操作

ES采用RestFul风格的API,发送一次HTTP请求。可以使用任何支持HTTP请求的工具。

5.2.1 创建索引

1.创建索引语法:

  • 请求方式:PUT

  • 请求路径:/索引名称

  • 请求参数:JSON格式

  • 响应结果:ES响应(不关注)

2.请求参数:

{"setttings": {"属性名": "属性值","属性名": "属性值",....}
}

setttings: 就是索引库的设置,其中可以定义索引库的各种属性,先不设置,走默认。

3.创建一个/yx索引。

PUT /yx
{...
}

3.结果:

{"acknowledged": true,"shards_acknowledged": true,"index": "yx"
}

kibana封装了HTTP请求的结构,进行了简化处理。

5.2.2 查看索引库

1.语法:

GET /索引库名称

2.案例:

GET /yx

3.结果:

{"yx": { # 索引名称"aliases": {}, # 索引别名"mappings": {}, # 索引映射(term - Filed)"settings": { # 索引的相关配置信息"index": { "creation_date": "1670479801027","number_of_shards": "5","number_of_replicas": "1","uuid": "0btSwsk8QU-XuF2L46Ga5w", # 当前索引的ID"version": {"created": "6020499" # 创建索引},"provided_name": "yx" # 索引名称}}}
}

5.2.3 删除索引

1.语法:

DELETE /索引名称

2.案例:

DELETE /yx

3.结果:

{"acknowledged": true # 执行成功
}

6 使用Kibana对类型及映射操作

有了索引库 ,等于有了数据库中的Database。接下来就需要索引库中的类型了,也就是数据库中的表。创建数据库表需要设置字段约束,索引库也一样,在创建索引库的类型时,需要知道这个类型下有哪些字段,每个字段有哪些约束信息,这就叫做字段映射(Mapping)。

注意:Elasticsearch7.x取消了索引type类型的设置,不允许指定类型,默认为_doc,但字段仍然是有的,我们需要设置字段的约束信息,叫做字段映射(Mapping)。

字段的约束我们在学习Lucene中我们都见到过,包括到不限于:

  • 字段的数据类型

  • 是否要存储

  • 是否要索引

  • 是否分词

  • 分词器是什么

我们一起来看下创建的语法。

6.1 创建字段映射

6.1.1 创建字段映射语法

语法:请求PUT请求。

PUT /索引名称/_mapping/typeName
{"properties": {"字段名": {"type": "类型","index": true或false,"store": true或false,"analyzer": "分词器"}}
}

类型名称:就是前面讲的type的概念,类似于数据库中的表。

字段名:字段名称由开发者自定义,每个字段名下可指定多个属性。具体属性见下表:

属性 描述
type 类型,可以是text、keyword、long、short、date、integer、object等
index 是否索引,默认为true
store 是否存储,默认为false
analyzer 分词器,例如设置为ik_max_word值,即使用IK分词器

6.1.2 创建字段映射案例

需求:创建一个yx索引,在该索引中创建一个goods类型。包含字段:

  • title: 表示商品名称
  • images: 表示商品图片地址
  • price:表示商品价格
PUT /yx/_mapping/goods
{"properties": {"title": {"type": "text","store": true,"analyzer": "ik_max_word"},"images": {"type": "keyword","index": false,"store": true},"price": {"type": "float"}}
}

结果:

{"acknowledged": true
}

6.2 查看映射关系

1.语法:

# 表示查询所有的类型的映射
GET /索引库名称/_mapping# 表示查询某一个指定的类型映射, 可以在路径后面跟上类型的名称
GET /索引库名称/_mapping/类型名

2.案例

{"yx": {"mappings": {"goods": {"properties": {"images": {"type": "keyword","index": false,"store": true},"price": {"type": "float"},"title": {"type": "text","store": true,"analyzer": "ik_max_word"}}}}}
}

6.3 字段数据类型解析

在ES中字段有一个类型(字段数据类型、字段类型),这个类型通过指定来决定此字段可以包含什么样的数据。

6.3.1 字段数据类型分类

1.常用类型

2.string类型

text: 会被分词,如:文章标题、正文。

keyword: 关键字数据类型,用于索引结构化内容的数据。例如:邮箱、身份号、银行卡号、手机号。

3.Numerical数值类型

基本包括:long、integer、short、byte、double、float…

  • float: 32
  • double: 62
  • half_float: 16

4.Date日志类型

可以将格式化的日期字符串进行存储(2022-12-10 12:23:10)。

5.Array数组类型

1.字符串数组:[“one”, “two”]

2.整数数组:[1, 2]

3.数组的数组:[1, [3, 4, 5]]

4.对象数组:

[{"name": "tom","age": 20},{"name": "Marry","age": 22}
]

6.Object对象

{"name": "tom","age": 20
}{"region": "CN","manager.name": "Tom","manager.age": 20
}{"region": "CN","manager": {"name": "tom","age": 20}
}

7.IP地址

可以支持IPV4、IPV6。

PUT /my_ip_addr
{"mappings": {"_doc": {"properties": {"ip_addr": {"type": "ip"}}}}
}

向索引库中插入一条IP地址数据。

PUT /my_ip_addr/_doc/1
{"ip_addr": "192.168.230.131"
}

获取数据:

GET /my_ip_addr/_search

8.多领域

出于不同的目的,以不同的方式索引同一字段通常很有用。例如,一个string字段可以映射为一个text用于全文搜索的字段,也可以映射为一个keyword用于排序或聚合的字段。或者,您可以使用标准分析器(standard analyzer)、英语分析器(english analyzer)和法语分析器(french analyzer)对文本字段进行索引。

这就是多领域的目的。fields大多数字段类型通过fields参数支持多字段。

6.4 一次创建索引库和类型

1.传统写法

在传统的方式下,先创建索引库,然后在给索引库添加映射,分为两个步骤来完成。

1.创建索引库

PUT /ytx

2.添加类型

PUT /ytx/_mapping/goods
{"properties": {"title": {"type": "text","index": true,"store": true,"analyzer": "ik_max_word"},"images": {"type": "keyword","index": false},"price": {"type": "float","index": true,"store": true}}
}

2.简便写法

1.语法:

PUT /索引库名
{"setttings": {"索引库属性名": "索引库属性值"....},"mappings": { # 表示添加映射"类型名": {"properties": {"字段名称": {"属性名": "属性类型"...},...}}}
}

2.案例:

PUT /ytx2
{"settings": {},"mappings": {"goods": {"properties": {"title": {"type": "text","index": true,"store": true,"analyzer": "ik_max_word"},"images": {"type": "keyword","index": false},"price": {"type": "float","index": true,"store": true}}}}
}

7 使用Kibana对文档操作

文档:即索引库中某个类型下的所有的数据(goods)。会根据规则创建索引,然后用户搜索。

7.1 新增文档

1.新增文档并随机生成id

1.语法:

POST /索引库名称/类型名称
{"属性名": "属性值"
}

2.案例:

POST /ytx/goods
{"title": "小米手机","images": "http://www.yuanxindev.com","price": 2888.0
}

3.结果:

{"_index": "ytx","_type": "goods","_id": "k-zb8IQBuyJnhZgsYv8p", # 表示当前文档的id(UUID序列化)"_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 0,"_primary_term": 1
}

2.新增文档并手动设置id

1.语法:

POST /索引库名称/类型名称/手动分配id
{"属性名": "属性值"
}

2.案例:

POST /ytx/goods/1
{"title": "大米手机","images": "http://www.yuanxindev.com","price": 4888.0
}

7.2 查询文档

1.语法结构:

GET /索引库名称/类型名称/id值

2.案例:

GET /ytx/goods/1

3.结果:

{"_index": "ytx","_type": "goods","_id": "1","_version": 1,"found": true,"_source": {"title": "大米手机","images": "http://www.yuanxindev.com","price": 4888}
}

4.语法解析:

  • _source: 表示存储文档的源数据信息,所有的数据都在该属性中保存。
  • _id: 表示这个文档的唯一标识。

7.3 修改文档数据

PUT表示修改,不会修改id的值。分为两种情况:

  • id不存在:则会新增这条数据。
  • id存在:则修改这条数据。

1.修改一个id存在的数据。

PUT /yx/goods/1
{"title": "红米手机","price": 1288.00
}

2.如果id不存在则创建对应的数据。

PUT /yx/goods/5
{"title": "玉米手机","images": "http://www.yuanxindev.com","price": 2999.00,"stock": 100
}

7.4 删除文档数据

删除使用DELETE,根据id删除文档数据。

DELETE /索引库名称/类型名称/id值

7.5 智能判断

在使用ES的时候,不需要指定具体的映射,输入具体的数据(字段名称、字段的取值),智能的进行判断,最终进行动态的添加。

原有字段:titile、price、images。新字段:stock、subTitle。

POST /yx/goods/4
{"title": "IPhone 15","images": "http://www.iphone.com","price": 6999,"stock": 300,"subTitle": "IPhone 15 pro","saleable": true
}

查看:

GET /yx/goods/4

7.6 动态模板

案例

PUT /ytr
{"mappings": {"goods": {"properties": {"title": {"type": "text","analyzer": "ik_max_word"}},"dynamic_templates": [ # 定义动态模板{"strings": {"match_mapping_type": "string", # 对string类型进行动态模板的定义"mapping": {"type": "keyword","index": false,"store": true}}}]}}
}

解析:

  • title:统一映射为text类型,支持分词。

  • match_mapping_type属性:定义了处理string类型的字段,凡事string类型的字段都表现出下面的特性:

    "mapping": {"type": "keyword","index": false,"store": true
    }
    

插入数据:

POST /ytr/goods/1
{"title": "Iphone","images": "http:www.phone.com","price": 3999.0
}

查看一个类型的所有映射(Field具体定义):

GET /ytr/_mapping

结果:

{"ytr": {"mappings": {"goods": {"dynamic_templates": [{"strings": {"match_mapping_type": "string","mapping": {"index": false,"store": true,"type": "keyword"}}}],"properties": {"images": { # 动态模板:指定了如果是string字符串类型的数据,则按照mapping的定义作为字段约束"type": "keyword","index": false,"store": true},"price": { # 智能判断"type": "float"},"title": {"type": "text","analyzer": "ik_max_word"}}}}}
}

8 查询

ES最终的内容就是搜索(查询数据),在ES搜索查询数据介绍以下模块:

  • 基本的查询操作
  • 结果过滤
  • 高级查询操作
  • 排序

8.1 基本查询操作

语法:

GET /索引名称/_search
{"query": {"查询类型": {"查询条件": "查询条件的取值"}}
}

_search指定查询的相关条件的声明。

查询类型:表示取值为:match_all、match、term、range等。

查询条件:查询条件会根据类型来做数据的获取操作。

8.1.1 查询所有 match_all

案例:

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

解析:

  • query:表示指定查询操作
  • match_all:表示查询所有

结果:

{"took": 87, # 查询所有数据耗费总时间,单位是毫秒"timed_out": false, # 查询是否超时"_shards": { # 分片信息"total": 5,"successful": 5,"skipped": 0,"failed": 0},"hits": { # 搜索的结果总揽"total": 2, # 搜索的数据总数"max_score": 1, # 结果文档中得分最高"hits": [ # 存储查询的结果集{"_index": "yx","_type": "goods","_id": "4","_score": 1,"_source": {"title": "IPhone 15","images": "http://www.iphone.com","price": 6999,"stock": 300,"subTitle": "IPhone 15 pro","saleable": true}},{"_index": "yx","_type": "goods","_id": "1","_score": 1,"_source": {"title": "红米手机","price": 1288}}]}
}

8.1.2 匹配查询match

通过指定的关键词,如果查询的内容中包含指定的关键字,则返回对应的数据。

PUT /yx/goods/5
{"title": "小米电视4A","images": "http://images.com","price": 3999.0
}

1.OR

math类型的查询中,将指定的字段进行分词,将满足任意一个分词条件的数据都会返回,索引OR是分词的或者的关系。

GET /yx/_search
{"query": {"match": {"title": "小米手机"}}
}

2.AND

将查询条件中的多个分词作为并且的关系来连接,都必须满足的情况下,才会返回对应的数据。

GET /yx/_search
{"query": {"match": {"title": {"query": "红米手机", "operator": "and"}}}
}

8.1.3 词条匹配term

term查询被用于精确匹配查询,可以匹配:数字、时间、字符串、布尔…

SQL语句:

select * from tb_name where colName = "val";

案例:

GET /yx/_search
{"query": {"term": {"price": 1288}}
}

8.1.4 布尔组合bool

bool可以把多个条件进行组合。

  • 与:must
  • 或:should
  • 非:must_not

案例:

GET /yx/_search
{"query": {"bool": { # 声明组合条件"must": { # 与"match": { # 表示声明条件"title": "红米" # 声明一组比较条件}},"must_not": { # 非"match": {"title": "电脑"}},"should": { # 或"match": {"title": "手机"}}}}
}

8.1.5 范围查询range

range查询在指定区间中的数据(时间、数字)。

GET /yx/_search
{"query": {"range": {"price": {"gte": 1000,"lte": 4000}}}
}

参数:

  • gte: 表示大于等于
  • lte:表示等于
  • gt: 大于
  • lt: 小于

8.1.6 模糊查询fuzzy

fuzzy查询是term模糊进行匹配。

POST /yx/goods/4
{"title": "Apple手机", # 分词:Apple、手机"images": "http://image.yx.com/12479122.jpg","price": 6899.00
}

查询:

GET /yx/_search
{"query": {"fuzzy": { # 声明模糊查询条件"title": "Apple" # 根据title字段进行模糊查询}}
}

8.2 结果过滤

默认情况下ES将结果保存在_source属性中。

通过编程方式来对_source中的数据进行过滤。

8.2.1 直接指定字段

在查询的时候,记录有5个字段,但是我只想看其中指定的两个字段(title、price)。

GET /yx/_search
{"_source": ["title", "price"], # 用来指定被查询结果展示的数据字段"query": {"term": {"title": "手机"} }
}

8.2.2 指定includes和exclude

含义:

  • includes:来指定想要显示那一些字段
  • exclude: 来指定不想显示那一些字段
GET /yx/_search
{"_source": {"includes": ["title", "price"] # 来指定最终要展示的字段列表},"query": {"term": {"title": "手机"} }
}

不需要展示对应的字段exclude来声明

GET /yx/_search
{"_source": {"excludes": ["title", "price"] # 来指定不想显示的字段列表},"query": {"term": {"title": "手机"} }
}

8.3 过滤filter

ES使用查询获取一组查询的结果,然后通过filter可以实现结果的过滤。

8.3.1 条件查询中进行过滤

通过bool属性来指定查询的条件,还可以在该字段中指定结果的过滤,通过filter属性来声明。

GET /yx/_search
{"query": {"bool": {"must": {"match": {"title": "小米手机"}},"filter": { # 结果过滤"range": { # 表示根据取值的返回进行过滤"price": { # 根据价格的范围"gte": 2000,"lte": 6000}}}}}
}

8.3.2 无查询条件直接过滤

如果一次全数据查询获取的到的结果,期望对这些结果进行一个指定条件的过滤操作,可以直接使用filter。

GET /yx/_search
{"query": {"constant_score": { # 需要将filter声明在constant_score属性中"filter": { # 声明过滤条件"range": { # 根据范围取值进行过滤"price": {"gte": 2000,"lte": 6000}}}}}
}

8.4 排序

8.4.1 单个字段排序

sort可以根据指定的字段进行排序的声明,类似于MySQL数据库中学习order by关键字。

  • asc: 升序排序
  • desc: 降序排序

案例

GET /yx/_search
{"query": {"match": {"title": "小米手机"}},"sort": [ # 表示声明排序{"price": {"order": "desc" # 表示具体排序的类型,desc表示降序排序}}]
}

8.4.2 多个字段排序

案例

GET /yx/_search
{"query": {"bool": {"must": {"match": {"title": "红米手机"}},"filter": {"range": {"price": {"gte": 0,"lte": 10000}}}}},"sort": [{"price": {"order": "desc"}},{"_score": { # 表示当前选项被查询评分(就是根据用户查询搜索频率,计算出了一个命中率)"order": "asc"}}]
}

8.5 分页

SE分页查询需要指定两个数值:

  • from: 目标数据的偏移值(当前也第一条数据的索引编号),默认从0开始取值
  • size:每一页显示的数据条数(表示每页大小)

语法:

GET /索引名称/_search {"query": {....},"from": current_index,"size": page_size
}

案例:

GET /yx/_search
{"query": {"match_all": {}},"sort": [{"price": {"order": "desc"}}],"from": 0, # 当前页起始第一条数据的编号(从0开始)"size": 3 # 当前页的大小
}

8.6 高亮

高亮处理:

  • 服务器端搜索数据,得到搜索的结果。
  • 把搜索结果中,包含用户输入的关键字内容添加约定的标签(前端人员来确定:span)。
  • 再把这个添加了标签后的结果返回给前端,前端会通过CSS样式来控制关键展示的效果,比如效果进行标红(高亮处理)
后台:华为智能门锁 AI指纹锁 星际黑 电子锁 华为手机钱包钥匙开锁 HarmonyOS分布式后端处理:<span>华为</span>智能门锁 AI指纹锁 星际黑 电子锁 <span>华为</span>手机钱包钥匙开锁 HarmonyOS分布式CSS样式:
<div id="title"><span>华为</span>智能门锁 AI指纹锁 星际黑 电子锁 <span>华为</span>手机钱包钥匙开锁 HarmonyOS分布式
</div>#title span {color: red;
}

处理语法:

  • pre_tags: 表示前缀标签的名称
  • post_tags:表示后缀标签的名称
  • field: 声明将开始标签和结束标签作用在那一个字段上

案例:

GET /yx/_search
{"query": {"match": {"title": "手机"}},"highlight": { # 表示声明定义高亮处理"pre_tags": "<span>", # 表示声前缀标签"post_tags": "</span>", # 表示声明后缀标签"fields": { # 将该高亮处理作用在那一个字段上"title": {} # 表示作用在title字段}}
}

9 聚合aggregations

自己探索。类似MySQL的group by内容。

TODO

10 Elasticsearch集群

10.1 单节点的问题

单节点的ES面临的问题:

1.存储容量有限,无法实现高存储。

2.单节点服务器出现故障,无法实现高可用。

3.单节点服务器处理能力有限,无法现实高并发。

10.2 集群的结构

10.2.1 数据分片

首先,我们面临的第一个问题就是数据量太大,单点存储量有限的问题。大家觉得应该如何解决?

没错,我们可以把数据拆分成多份,每一份存储到不同机器节点(Node),从而实现减少每个节点数据量的目的。这就是数据的分布式存储,也叫做数据分片(Shard)。

10.2.2 数据备份

数据分片解决了海量数据存储的问题,但是如果出现单点故障,那么分片数据就不再完整,这又该如何解决呢?

没错,就像大家为了备份手机数据,会额外存储一份到移动硬盘一样。我们可以给每个分片数据进行备份,存储到其它节点,防止数据丢失,这就是数据备份,也叫数据副本(replica)。

数据备份可以保证高可用,但是每个分片备份一份,所需要的节点数量就会翻一倍,成本实在是太高了。为了在高可用和成本间寻求平衡,我们可以这样做:

  • 首先对数据分片,存储到不同节点。

  • 然后对每个分片进行备份,放到对方节点,完成互相备份。

这样可以大大减少所需要的服务节点数量。如下图我们以3分片,每个分片备份一份为例。

10.3 集群搭建

10.3.1 搭建集群设计

设计集群名称:yx-elastic。部署3台ES集群的环境。分别:

  • node-01:HTTP端口9201,TCP端口9301。
  • node-02:HTTP端口9202,TCP端口9302。
  • node-03:HTTP端口9203,TCP端口9303。

在默认情况下,集群HTTP的端口是9200,TCP端口默认是9300。

10.3.2 搭建集群实现

1.集群节点的安装

1.解压ES压缩包,解压出3份。

  • elasticsearch-9201
  • lasticsearch-9202
  • lasticsearch-9203

2.每一个节点要配置data和logs。创建三台节点的数据存放目录

  • es-9201
  • es-9202
  • es-9203

2.配置集群环境

在节点上的config目录找到elasticsearch.yml文件中进行集群的配置。

1.配置node-01节点。

# 允许跨域名访问
http.cors.enabled: true
# 当设置允许跨域,默认为*,表示支持所有域名
http.cors.allow-origin: "*"
# 允许所有节点访问
network.host: 0.0.0.0
# 集群的名称,同一个集群下所有节点的集群名称应该一致
cluster.name: yx-elastic
# 当前节点名称 每个节点不一样
node.name: node-01
# 数据的存放路径,每个节点不一样,不同es服务器对应的data和log存储的路径不能一样
path.data: /Users/yuanxin/Documents/elasticsearch/es-9201/data
# 日志的存放路径 每个节点不一样
path.logs: /Users/yuanxin/Documents/elasticsearch/es-9201/logs
# HTTP协议的对外端口,每个节点不一样,默认:9200
http.port: 9201
# TCP协议对外端口 每个节点不一样,默认:9300
transport.tcp.port: 9301
# 三个节点相互发现,包含自己,使用TCP协议的端口号
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
# 声明大于几个的投票主节点有效,请设置为(nodes / 2) + 1
discovery.zen.minimum_master_nodes: 2
# 是否为主节点
node.master: true

2.配置node-02节点。

# 允许跨域名访问
http.cors.enabled: true
# 当设置允许跨域,默认为*,表示支持所有域名
http.cors.allow-origin: "*"
# 允许所有节点访问
network.host: 0.0.0.0
# 集群的名称,同一个集群下所有节点的集群名称应该一致
cluster.name: yx-elastic
# 当前节点名称 每个节点不一样
node.name: node-02
# 数据的存放路径,每个节点不一样,不同es服务器对应的data和log存储的路径不能一样
path.data: /Users/yuanxin/Documents/elasticsearch/es-9202/data
# 日志的存放路径 每个节点不一样
path.logs: /Users/yuanxin/Documents/elasticsearch/es-9202/logs
# HTTP协议的对外端口,每个节点不一样,默认:9200
http.port: 9202
# TCP协议对外端口 每个节点不一样,默认:9300
transport.tcp.port: 9302
# 三个节点相互发现,包含自己,使用TCP协议的端口号
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
# 声明大于几个的投票主节点有效,请设置为(nodes / 2) + 1
discovery.zen.minimum_master_nodes: 2
# 是否为主节点
node.master: true

3.配置node-03节点。

# 允许跨域名访问
http.cors.enabled: true
# 当设置允许跨域,默认为*,表示支持所有域名
http.cors.allow-origin: "*"
# 允许所有节点访问
network.host: 0.0.0.0
# 集群的名称,同一个集群下所有节点的集群名称应该一致
cluster.name: yx-elastic
# 当前节点名称 每个节点不一样
node.name: node-03
# 数据的存放路径,每个节点不一样,不同es服务器对应的data和log存储的路径不能一样
path.data: /Users/yuanxin/Documents/elasticsearch/es-9203/data
# 日志的存放路径 每个节点不一样
path.logs: /Users/yuanxin/Documents/elasticsearch/es-9203/logs
# HTTP协议的对外端口,每个节点不一样,默认:9200
http.port: 9203
# TCP协议对外端口 每个节点不一样,默认:9300
transport.tcp.port: 9303
# 三个节点相互发现,包含自己,使用TCP协议的端口号
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
# 声明大于几个的投票主节点有效,请设置为(nodes / 2) + 1
discovery.zen.minimum_master_nodes: 2
# 是否为主节点
node.master: true

4.配置分词器:在三台节点下分别配置分词器插件。

5.Kibana工具。

6.直接启动集群。每一个ES都要运行elasticsearch文件。

# win
elasticsearch.bat# Linux/Mac
./elasticsearch

10.3.3 head访问集群

1.打开head工具。

2.http://localhost:9201/访问集群中任意一台节点。

10.3.4 集群创建索引

1.创建索引:

PUT /yx
{"settings": {"number_of_shards":3,"number_of_replicas": 1 }
}

2.使用head工具查看集群索引创建情况。

Elasticsearch(未完)相关推荐

  1. 《今日简史》读书笔记(未完待续)

    <今日简史>读书笔记(未完待续) 这本书是尤瓦尔·赫拉利的简史三部曲的最后一本,前2本书是<未来简史>和<人类简史>.根据豆瓣上网友的评价,这本书是尤瓦尔·赫拉利写 ...

  2. [每周软件]:Cucumber:未完待续的原因

    2019独角兽企业重金招聘Python工程师标准>>> 本来这个计划是一周的 剩下未完的三篇才是核心 两篇源码分析,一篇总结+BDD分析,但是因为目前水平有限 源码追了一部分之后追丢 ...

  3. ExtJs之Ext.grid.GridPanel(部分未完)

    今天在家休息,年假不用就作费啊. 看了几部香港老电影,陪爸爸看了勇士占奇才, 然后,测试了一下EXTJS未完的内容, 在京东上订了七本历史普及书,近两百块..:) 搞定. ? 1 2 3 4 5 6 ...

  4. 创建型模式——Factory Method(未完)

    当对某个对象的实例化代码散布在整个项目中的时候,似乎你已经可以嗅到坏味道了,我们叫做"创建蔓延".除非你肯定这个对象的实例化方法永远不会改变,否则最后将"创建的知识搬迁到 ...

  5. linux引数列项目过长,Linux 命令个人总结====== 未完待续 个人认为比较重要

    Linux 命令个人总结====== 未完待续 man [功能说明]: 查看帮助 [语法格式]: man [123456789]命令.文件. [选项参数]: 数字"1"表示用户命令 ...

  6. CC2530学习路线-基础实验-串口通讯发送字符串(4 未完待续)

    目录 1. 前期预备知识 1.1 串口通讯电路图 1.2 实验相关寄存器 1.2 常用波特率设置 本章未完待续..... 原来写的文章已经丢失了,只能找到这一小部分,看什么时候有时间再补上. 1. 前 ...

  7. [教程] [承風雅傳HSU]用ES4封裝Win7---ES4 Win7封裝教程(未完待續)

    [教程] [承風雅傳HSU]用ES4封裝Win7---ES4 Win7封裝教程(未完待續) a10036it 发表于 2015-7-27 21:11:19 https://www.itsk.com/t ...

  8. Codeforces Round #395 (Div. 2)(未完)

    2.2.2017 9:35~11:35 A - Taymyr is calling you 直接模拟 #include <iostream> #include <cstdio> ...

  9. 计算机病毒洛,蓝狐动漫《百变机兽》中未完的战争,蓝毒兽原来是电脑病毒?...

    原标题:蓝狐动漫<百变机兽>中未完的战争,蓝毒兽原来是电脑病毒? 最近,好久没有更新了,因为最近找到一部非常好看的童年动漫<百变机兽>,然后就一直追剧.今天和大家一起聊聊< ...

  10. Paper之BigGAN:ICLR 2019最新论文《LARGE SCALE GAN TRAINING FOR HIGH FIDELITY NATURAL IMAGE SYNTHESIS》(未完待续)

    Paper之BigGAN:ICLR 2019最新论文<LARGE SCALE GAN TRAINING FOR HIGH FIDELITY NATURAL IMAGE SYNTHESIS> ...

最新文章

  1. framework7使用笔记
  2. 使用docker安装Mongodb
  3. RxPermissions 源码解析之举一反三
  4. python基础入门学习笔记 (2)
  5. Android开发杂谈更新中
  6. 让Jackson JSON生成的数据包含的中文以unicode方式编码
  7. [Linux] ubuntu server sudo出现sudo:must be setuid root 完美解决办法
  8. 手把手教你使用R语言做评分卡模型
  9. nest.js 使用express需要提供多个静态目录的操作
  10. livereload_LiveReload
  11. 安工大matlab实验报告王朋飞,计算机仿真实验
  12. 使用ps 制作gif 动图
  13. 五个美观好用的全能性IDE推荐(更新)
  14. Promise优缺点
  15. Python游戏嗷大喵快跑设计
  16. python产生一个1到10的列表_python-列表生成式(一)
  17. 汇编 eax test jnz jz 等组合连用的总结
  18. Leetcode PHP题解--D38 463. Island Perimeter
  19. 基于Lanczos方法的矩阵双对角化或三对角化
  20. C语言%p与%x的区别

热门文章

  1. 20/03/07 机器学习---基础算法 (2)
  2. 小鼠脑立体定位图谱_探索从未止步!瑞沃德首款国产电动脑立体定位仪发布
  3. KVM 虚拟化环境搭建 - ProxmoxVE
  4. 阿里云服务器Centos/tomcat6 配置http/https证书访问
  5. 京东准点秒杀脚本,准点自动加入购物车,准点自动秒杀
  6. 调用libcurl获取https的url文件的大小
  7. 在html语言中段落标签是,HTML的基本结构、段落标签、空格标签、标题标签、图片标签详解...
  8. 股票软件开发技术之个股的选择功能设计思路101
  9. Unity3dRPG 相机跟随player旋转_「Facebook」应用程序Bug会悄悄访问相机,发紧急声明会修复且更新...
  10. oracle触发器更新库存,oracle 触发器,当一个表更新或插入时将数据同步至另个库中的某个表中...