目录
概述
Elasticsearch的底层是开源库Lucene。
Lucene是一个基于Java的全文搜索工具包。
Lucene中包含了四种基本数据类型:
- Index: 索引,由很多的Document组成
- Document: 由很多的Field组成
- Field: 由很多的Term组成
- Term:分词后的最小单位
Lucene中存的就是一系列的二进制压缩文件和一些控制文件,位于计算机的硬盘上。
索引库的组成:
- 原始记录:原始文本
- 词汇表:按照一定的拆分策略将原始记录中的每个字符拆开后,存入一个供将来搜索的表
为什么我们需要用ES来做搜索,不能直接用Mysql做查询?
- mysql没有相关度排名
- mysql不能针对硬盘上的文本搜索
- Lucene不耗内存,访问速度快
- Lucene可以关键词高亮
- ……
Elasticsearch就是对Lucene进行一个封装,提供REST API的操作接口。
Elasticsearch是面向文档的,它存储整个对象或文档。
Elasticsearch不仅存储文档,而且索引每个文档的内容,使之可以被检索。
ES使用JSON作为文档的序列化格式。
名词
索引
-
索引(名词):
一个存放关系型文档的地方 -
索引(动词):
存储一个文档到一个索引(名词)中以便被检索和查询
ES数据管理的顶层单位就是Index,每个Index的名字必须是小写,不能以下划线开头,不能包含逗号。
每个索引都可以定义自己的Mappings和Settings:
- Mappings:用于设置文档字段的类型
- Settings:用于设置不同的数据分布
ES索引原理
mysql的索引是基于B+Tree,B+数将搜索时间复杂度变成了logN。
搜索优化的思路无非是 存储数据结构优化、提高硬件环境存取效率。
ES的索引结构是倒排索引,将文档的content域拆分为——一组Term,创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。
首先,文档都会有一个id,姑且称为doc_id。
然后大概像这样对应起来:
ohh [doc1_id, doc2_id,……]
hhhh [doc3_id,doc4_id]
可以理解为列出这个Term出现在哪些文档里。
当然Terms可以按序排列,再激进一点,可以用树来记录。
由于索引放在内存中,所以可以使用FST进行进一步压缩。(有点像编译器里词法分析的感觉)
doc_id列表,Posting List也可以压缩,比如Roaring bitmaps (压缩位图)。
文档
索引里的单条记录称为Document(文档)
Document使用JSON格式表示。
同一个索引里Document,不要求有相同的结构,但是最好保持相同,这样有利于提高搜索效率。
文档中的数据类型可以指定,也可以由ES自动推断。
每个文档中都有一个Unique ID,用于唯一标识一个文档。
ID可以由用户指定,也可以由ES自动生成。
文档元数据
元数据——文档的信息:
_index
文档在哪存放
一个索引应该是因共同的特性被分组到一起的文档集合。
es中我们的数据是被存储和索引在 分片 中,而一个索引仅仅是逻辑上的命名空间,这个命名空间由一个或者多个分片组合在一起。
_type
文档表示的对象类别
数据可能在索引中只是松散的组合在一起,但是通常明确定义一些数据中的子分区是很有用的。
_id
文档唯一标识
_source
文档的原始Json数据
_version
文档更新的次数
更新文档
ES中文档是不可改变的,不能修改它们。
更新操作会将旧的文档标记为删除,同时增加一个新的字段,并且文档的version 加 1。
ES 中文档的删除操作不会马上将其删除,而是将其标记到del文件中,在后期合适的时候 (比如 Merge 阶段) 会真正的删除。
如果想要更新现有的文档,需要重建索引或者进行替换,可以使用相同的index API 进行实现。
映射和分析
ES中的数据可以概括的分为两类:精确值和全文。
精确值很容易查询,结果是二进制的:要么匹配查询,要么不匹配。
倒排索引
一个倒排索引由文档中所有不重复词多列表构成,对于其中每个词,有一个包含它的文档列表
为了创建倒排索引,将每个文档的content
域拆分成单独的词tokens,创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。
Term | Doc1 | Doc2 |
---|---|---|
Quick | X | |
The | X | |
brown | X | X |
dog | X | |
dogs | X | |
fox | X | |
foxes | X | |
in | X | |
jumped | X |
如果将词条规范为标准模式,可以找到和用户搜索的词条不完全一致,但具有足够相关性的文档。
- Quick可以小写化为quick
- foxes 可以 词干提取–变为词根的格式-- 为 fox 。类似的,dogs可以提取为dog
- jumped 和 leap 是同义词,可以索引为相同的单词jump
Term | Doc1 | Doc2 |
---|---|---|
brown | X | X |
dog | X | X |
fox | X | X |
in | X | |
jump | X | X |
lazy | X | X |
over | X | X |
quick | X | X |
summer | X |
doc_values
- 在搜索时,通过搜索关键词快速得到结果集
- 当排序时,需要倒排索引里某个字段的集和–> 需要转置倒排索引
转置:列存储
将所有单字段的值存储在单数据列中–排序高效
在ES中,Doc Values --> 列式存储结构
默认情况下每个字段的Doc Values都是激活的
Doc Values是在索引时创建的,当字段索引时,ES为了能够快速检索,会把字段的值加入倒排索引,同时也存储该字段的Doc Values。
Doc Values : 排序、聚合、过滤、计算
分析器
分析
包含下面的过程:
- 首先,将一块文本分成适合于倒排索引的独立的
词条
- 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者recall
分析器这些上面的工作,分析器实际上是将三个功能封装到了一个包里
-
字符过滤器
字符串按顺序通过每个字符过滤器
。他们的任务是在分词前整理字符串。 -
分词器
字符串被分词器
分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条 -
Token过滤器
词条按顺序通过每个token过滤器,这个过程可能会改变词条、删除词条、增加词条
ES提供了开箱即用的字符过滤器、分词器和token过滤器。这些可以组合起来形成自定义的分析器以用于不同的目的。
内置分析器
ES附带了可以直接使用的预包装的分析器。
标准分析器
标准分析器是ES默认使用的分析器,它是分析各种语言文本最常用的选择。它根据Unicode联盟定义的 单词边界 划分文本。删除绝大部分标点,然后将词条小写
简单分析器
简单分析器在任何不是字母的地方分隔文本,将词条小写。
空格分析器
空格分析器在空格的地方划分文本
语言分析器
特定语言分析器可以用于 很多语言。它们可以考虑指定语言的特点。例如,英语分析器附带了一组英语无用词 (常用单词,例如and或者the),它们对相关性没有多少影响,它们会被删除。由于理解英语语法的规则,这个分词器可以提取词干。
当ES在你的文档中检测到一个新的字符串域,它会自动设置其为一个全文字符串
域,使用 标准
分析器对它进行分析。
有时候会希望一个字符串域就是一个字符串域不使用分析,直接索引传入精确值,就需要指定这些域的映射。
映射
和传统数据库不同,传统的数据库我们尝试向表中插入数据的前提是这个表已经存在数据结构的定义,且插入数据的字段要在表结构中被定义。
而ES的映射和创建支持主动和被动创建。
选择被动创建只需要存在文档的索引,当遇到不存在的映射字段会根据内容自动添加映射字段定义。
为了能够将时间域视为时间,数字域视为数字,字符串域视为全文或者精确值字符串,es需要知道每个域中数据的类型。这个信息包含在映射中。
核心简单域类型
ES支持如下简单域类型:
- 字符串:string
- 整数:byte, short, integer, long
- 浮点数 :float, double
- 布尔型 :boolean
- 日期 :date
JSON type | 域 type |
---|---|
布尔型:true 或者 false | boolean |
整数:123 | long |
浮点数:123.22 | double |
字符串,有效日期:2022-2-2 | date |
字符串:hhhhh h | string |
object | Object |
array | 取决于数组中的第一个非空值的类型 |
null | 不添加任何字段 |
自定义域映射 (主动创建)
尽管在很多情况下基本域数据类型已经够用,但你经常需要为单独域自定义映射,特别是字符串域。
自定义映射允许你执行下面的操作:
- 全文字符串域和精确值字符串域的区别
- 使用特定语言分析器
- 优化域以适应部分匹配
- 指定自定义数据格式
- more
域最重要的属性是type,对于不是string的域,一般只需要设置type。
默认string类型域会被认为包含全文,就是说,它们的值在索引前,会通过一个分析器,针对于这个域的查询在搜索前也会经过一个分析器。
string域映射的两个最重要的属性是index和analyzer。
index
index 属性控制怎样索引字符串,它可以是下面三个值:
- analyzer:首先分析字符串,然后索引它,以全文索引这个域
- not_analyzed:索引这个域,它能够被搜索,但是索引的是精确值,不会对它进行分析
- no: 不索引这个域。这个域不会被搜索到
string域index属性默认是analyzed,如果我们想映射这个字段为一个精确值,我们需要设置它的not_analyzed
其他简单类型 (例如 long , double,date 等)也接受index参数,但有意义的值只有no 和 not_analyzed,因为它们永远不会被分析。
analyzer
对于analyzer字符串域,用analyzer属性指定在搜索和索引时使用的分析器。
默认,Elasticsearch使用standard分析器。但可以指定,比如:whitespace simple english
更新映射
首次创建一个索引的时候,可以指定类型的映射。也可以使用/_mapping为新类型增加映射。
尽管可以增加一个存在的映射,不能修改存在的域映射。
如果一个域的映射已经存在,那么该域的数据可能已经被索引。
我们可以更新一个映射来添加一个新域,但不能将一个存在的域从analyzed改为not_analyzed。
复杂核心域类型
除了简单标量数据类型,JSON还有null、数组、对象。
多值域
希望tag 域包含多个标签
{"tag": ["search", "nosql"]}
对于数组没有特殊的映射需求,数组中所有的值必须是相同数据类型的。
通过索引数组来创建新的域,ES会用数组中第一个值的数据类型作为这个域的 类型。
当你从es得到一个文档,每个数组的顺序和你当初索引文档时一样。
你得到的 _source域,包含与你的索引的一模一样的json文档。
但是,数组是以多值域索引的,可以搜索但是无序。
在搜索的时候不能指定第一个或者最后一个。
空域
数组可以为空,相当于存在零值。
在Lucene中说不能存储null值的,所以我们认为存在null值的域为空域。
下面这三种域被认为是空的,它们将不会被索引
"null_value": null
"empty_array": []
"array_with_null_value": [ null ]
多层级对象
哈希 哈希map 字典或者关联数组
内部对象 经常用于嵌入一个实体或对象到其他对象中,ES会动态监测新的对象域并映射它们为 对象,
Type 映射只是一种特殊的 对象 映射 —— 根对象。除了它有一些文档元数据的特殊顶级域,例如_source和_all域
内部对象是如何索引的
Lucene不理解内部对象。Lucene文档是由一组键值对列表组成的。为了能让ES有效地索引内部类,
它把我们的文档转化成这样:
{
"tweet": [elasticsearch, flexible, very],
"user.id": [@johnsmith],
"user.gender": [male],
"user.age": [26],
"user.name.full": [john, smith],
"user.name.first": [john],
"user.name.last": [smith]
}
内部域 可以通过名称引用(例如, first
)。为了区分同名的两个域,我们可以使用全 路径 (例如, user.name.first
) 或 type
名加路径( tweet.user.name.first
)