1.缘起
为啥想学习es,主要是在工作中会用到,但是因为不了解原理,所以用起来畏手畏脚的,就想了解下es是怎么存储数据,以及es是怎么搜索数据的,和平时的mysql有什么区别,什么情况下用es,什么情况下用mysql。第一次接触es是纠纷有个需求,需要搜索用户组,但是一个用户可能会有多个用户组(需要存储/搜索list),此时mysql虽然可以支持,但是搜索效率很低,就考虑将纠纷接入es。
2.探索
2.1 es的安装与基本使用
2.1.1 es的安装
2.1.2 es的基本使用
存入数据
按照官网现存的 elasticsearch 权威指南,索引数据后,提示:
Deprecation: [types removal] Specifying types in document index requests is deprecated, use the typeless endpoints instead (/{index}/_doc/{id}, /{index}/_doc, or /{index}/_create/{id}).
指定类型在请求中被废弃了,使用括号里的方式替代
# kibana dev tools里发起请求
PUT /yxfirstindex/employee/3 # 不推荐
{
"first_name":"Nancy",
"last_name":"Smith",
"age":27,
"interests":[ "music","coding"]
}
PUT /yxfirstindex/_doc/2 # 推荐 PUT /索引名/_doc(关键词)/id(不指定id es会自己生成id)
{
"first_name":"Jane",
"last_name":"Smith",
"age":32,
"interests":[ "music"]
}
搜索数据
根据id搜索
GET /yxfirstindex/employee/1 # 废弃 不推荐
GET /yxfirstindex/_doc/1 #推荐
GET /yxfirstindex/_search # 搜索全部的文档
GET /yxfirstindex/_search?q=first_name:nancy # 搜索名为nancy的文档
2.1.3 DSL(Domain-specific language)语法
2.1.3.1 一些例子
- 基础规则:将查询语句传递给query参数
GET /yxfirstindex/_search
{
"query": your_query_statement
}
- match_all
等价于空查询{},匹配所有的文档
{
"query": {
"match_all": {}
}
}
- 查询语句的结构
{
QUERT_NAME:{
ARGUEMENT:NAME.
ARGUEMENT:NAME.
}
}
//针对某个字段
{
QUERT_NAME:{
FIELD_NAME:{ //字段名
ARGUEMENT:NAME.
ARGUEMENT:NAME.
}
}
}
//eg:查询last_name匹配smith的所有文档。此处match的含义,是包含,还是精确查询?试了一下是包含这个字段的。但是模糊搜索是不支持的,原因是"smi"在分析时候,不会被分析一个倒排索引。
GET /yxfirstindex/_search
{
"query": {
"match": {
"last_name": "smith"
}
}
}
- 复合查询语句
单条查询语句可以合并成为更复杂的查询语句,复合语句也可以合并其他任何查询语句,包括复合语句。
{
"bool":{
"must":{"match":{"last_name":"smith"}},
"must_not":{"match":{"intrests":"sports"}},
"filter":{"range":{"age":{"gt":26}}}
"should":{"term":{"last_name":"smith"}} //should只会增加满足该语句的文档的得分,只用于修正得分
}
}
2.1.3.2 语法解析
- es的查询语言(dsl)可以用于两种情况:查询和过滤
- 过滤:查询被设置成一个“不评分”/“过滤” 查询,查询的问题是:这篇文章是否匹配。查询结果也只有简单的“是”/否
- 查询:查询情况时候,查询就变成了一个“评分”的查询,和“过滤”相同,需要判断这篇文档是否匹配,以及匹配度(分数)有多高
- 举例:
- last_name字段是否包含smitch 【过滤】
- age字段是否在26到30之间 【过滤】
- 包含 “smith”、“sport”这几个词,词之间离得越近,相关性越高。【查询】
- 性能差异
- 过滤:简单的检查包含,或者不包含,计算起来速度较快。结果会被缓存到内存中
- 查询:需要计算得分,计算速度不如过滤,结果不会被缓存到内存中
- 选择
需要关注搜索结果中的相关性得分的查询,都需要使用评分查询,其余情况用过滤查询。可以从我们的项目中看到,对列表的筛选过滤,都是用的filter查询,只关注是否包含,不关注相关性。 - 常用查询语句
//1.match_all查询,匹配所有的文档
{"match_all":{}}
//2.match查询,分为全文查询和对字段的匹配
//全文查询,没有找到全文查询的语法关键词是啥子
{"match":{"full_text_name":"query content"}}
//字段的精确匹配
{"match":{"field_name":"query content"}}
//eg
{"match":{"last_name":"smith"}}
//3.muti_match查询:在多字段上执行相同的match查询
"multi_match": {
"query":"smith"
, "fields": ["last_name", "first_name"]
}
//4.range查询:查询在指定区间的数字或时间,gte(greater than or equal):大于等于,lte(less than or equal):小于等于,gt:大于,lt:小于
"range": {
"age":{
"gte":26,
"lte": 30
}
}
//5. term查询,对于某个字段的精确查询
"term": {
"age":26
}
//6.terms查询,多值匹配,该字段包含了其中任意一个值,都会命中
"terms": {
"age":[26,30,32]
}
//7.exisits(有值)和missing(无值),和sql里的is null和not is null类似,应用于某个字段有值或者缺失
"exists": {
"field": "last_name"
}
//插入一条某个字段无值的文档可以看出来,最终查询结果没有"last_name"为null的这条
PUT /yxfirstindex/_doc/5
{
"first_name":"Jason",
"last_name":null,
"age":27,
"interests":[ "music","coding"]
}
- 查询语法验证
GET /yxfirstindex/_validate/query
{
"query":{
"field": {
"exists": "last_name"
}
}
}
//结果
{
"valid" : false
}
//通过explain字段给出更详细的解析
GET /yxfirstindex/_validate/query?explain
//结果
{
"valid" : false,
"error" : "ParsingException[unknown query [field]]; nested: NamedObjectNotFoundException[[3:14] unknown field [field]];; org.elasticsearch.common.xcontent.NamedObjectNotFoundException: [3:14] unknown field [field]"
}
//8.指定字段排序
GET /yxfirstindex/_search
{
"query":{
"bool": {
"must": {
"match": {
"last_name": "smith"
}
}
}
},
"sort":{
"age":{
"order":"asc"
}
}
}
//返回结果多了 sort字段,值为指定排序字段的值,且_score字段为null。
//9在公司的可视化页面上查询
index_name get方法
空query
可以查到mapping信息等,查到index所包含的字段
2.2 ES的存储&搜索过程
2.2.1 倒排索引(inverted index)
倒排索引,其实从字面意义上很容易理解错,但是看英文就会好理解一些,inverted index,反向的索引。倒排索引,指的是,索引词(关键词)和文档之间的对应关系,即通过一个关键词,可以得到包含这个关键词的所有文档的文档id。有反向索引,就应该有正向的索引,我们可以先来了解下正向索引的数据结构及其使用。
- 正向索引(forward index)
正向索引是搜索过程中也不可缺少的一步,当通过倒排索引查找到相关的文档后,通过正向索引,找到该关键词在文档中的具体位置。
正向索引的几个关键词
- LocalId:指文档id.简称(Lid)
- WordId:索引词的id
- NHits:索引词在该文档中命中的次数
- HitList:索引词在文档中的位置,是一个变长的列表
下面通过一个例子来讲解,正向索引的建立
文档1:“谷爱凌又夺冠了,我们都爱谷爱凌”,通过分词后:“谷爱凌/又/夺冠/了,我们/都/爱/谷爱凌”,分词后得到这些词语,谷爱凌、又、夺冠、了、我们、都、爱,其中,又、都、了,这些连接词含义不大,我们可以去掉。去掉后得到关键词:谷爱凌、夺冠、我们、爱,分别给这些词加上编号w1、w2、w3、w4。其中“谷爱凌”一词出现了2次,NHits为2;在文档中的位置是1、12。以此类推可以得到另外三个词的NHits和HitList,于是我们可以得到以下这张表
LocalId | WordId | NHits | HitList |
---|---|---|---|
s1 | w1 | 2 | 1,12 |
w2 | 1 | 5 | |
w3 | 1 | 8 | |
w4 | 1 | 10 | |
null |
最后通过null来表示结束。
从上面的文档可以得出,正向索引提供的是在一篇文档里,索引词出现的次数、位置等信息,不能满足我们通过一个关键词得到所包含该关键词文档的需求。
- 倒排索引
倒排索引分为两个部分,第一部分是词典,包含索引词、统计信息(文档数量等)以及对应的文档在记录文件中的偏移量(地址),第二部分是记录文件,包含文档id、NHits、HitList等信息。
下图是第一部分词典和第二部分记录文件等对应关系。
比如我们要搜索关键词w1,通过词典我们可以查到有三个文档包含这个关键词,再通过偏移量找到第一个对应的文档Doc1,通过记录表可以得到该关键词命中的次数NHits,以及在Doc1中出现的位置。同理也可以得到该关键词在Doc2、Doc3中的信息。
关于记录表中,文档的排名有几种方式
- 通过文档id排序
- 通过NHits降序
- 记录表分块,块内按照文档id正序,块间按照pageRank降序
对于方案一,可以对DocId进行压缩,降低存储成本
对于方案二,文档中关键词出现的次数越多,可以一定程度上反映该文档和关键词的相关性越高,但是会被有些网页的作弊者利用
对于方案三,兼顾了方案一的DocId压缩带来的有点,也能够体现出文档相关性的顺序,使得重要的文档被优先检索到(此处存疑,问题在于块是如何划分的)
总结:本质上,存在两个空间:文档空间和索引词空间,正向索引是文档空间到索引词空间的映射关系,可以通过文档id得到该文档里的一组索引词的信息。而反向索引是索引词空间到文档空间的映射关系,可以通过索引词id得到包含该索引词的所有文档的id。
2.2.2 索引(建立)过程
2.2.2.1分析
分析模块能够把一个string类型的域转化为单独的项目。这些项目是被加入到倒排索引中以便使这个文档能被搜索到,二是被使用在高级的搜索中如match query中来产生搜索项。
- 索引分片分配:这个模块提供每一个索引的设置来控制节点中分片的分配。
- 分片分配过滤:控制哪个分片被分配到哪个节点上
- 延迟分配:延迟分配由节点的离开导致未分配分片的分配
- 每个节点的所有分片:相同索引每个节点上的分片数量会有严格的控制
- 数据层粉看:控制数据层的索引分配
2.1 索引级别的分片分配过滤
可以用分片过来控制es为每一个特定的索引分配分片。索引的生存周期
2.2 当一个节点离开时的延迟分配:
当一个集群中的节点不管因为什么原因离开时,故意的或者其他,集群的主节点会进行如下操作: - 把一个副本分片提升为主要的分片来代替(离开的)节点上的任意主分片
- 分配副本分片来代替消失的副本分片(假设有足够的节点)
- 重新平衡剩下的分片通过剩下的节点
通过尽快地复制副本分片来保护集群数据的以免丢失
即使我们在节点级别和集群级别限制了并发恢复,这种分片洗牌仍然会带来一些额外的不必要的负载,如果这个消失的节点可能很快🉐️回来。现象这种假设 - node5失去了网络连接
- 主节点将副本分片提升为主要分片,为node5上的所有的主要分片
- 主节点将分配新的副本给集群中的其他节点
- 每一个副本分片会复制一整份主要分片的数据通过网络
- 更多的分片被移到不同的节点来平衡集群
- node5在几分钟后回归了
- master重新平衡集群,通过重新分配节点5的分片
如果主分片等一会,消失的分片能够被重新分配到node5上以更小的网络拥挤。
副本分片的分配可以被延迟,通过设置index.unassigned.node_left.delayed_timeout,默认是一分钟。
index.unassigned.node_left.delayed_timeout
## 设置
PUT _all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}
当延迟被设置后,假设就会变成下面这样
- node5失去网络连接
- 主节点将为node5中的主分片,提升对应的其他节点中的副本分片为主分片
- 主节点记录一个消息,未被分配的节点被延迟分配了,延迟时间是多久
- cluster状态为黄色,因为存在未被分配的副本分片
- node5不一会回来了,在设置的timeout时间内
- 缺失的副本被重新分配给node5
这个设置不会影响副本分片到主分片提升,也不会影响之前未分配的副本分片的分配。
分片重定位的取消
如果延迟超时了,主节点分配缺失的分片到另一个开始恢复的节点上,如果这个消失的节点重新加入了集群,且它的分片还和主分片有同样的id,分片重定位会被取消,而且这个同步的分片会被用来恢复。
还有两节都是将分片的,和主题关系不大(分片是为了保证分布式高可用)而我想知道为什么快。搜索的原理。
索引块(跳过)
2.2.2.2 映射器
映射器模块的功能:充当创建索引/使用更新api更新索引时的 类型映射定义 的注册表。还处理对没有预定义显式映射类型的动态映射支持。
是定义文档及其包含的字段如果存储和索引的过程。每个文档都是字段的集合,每个字段都有其数据类型,映射数据时候,会创建一个映射定义,其中包含与文档相关的字段列表。
可以使用动态映射和显示映射来定义数据。
显示的映射允许你精确地选择如何让定义映射的定义。如
- 哪些string字段应该被当作全文字段对待
- 哪些字段包含数字、日期或者地理位置
- 日期的格式
映射太多/太大会导致内存爆炸,可以通过设置来限制
动态映射
一个es重要的特征,为了索引一个文档,你不需要首先创建一个索引,定义一个映射类型、定义你的字段。你能够只索引一个文档,而这个索引、类型和字段都会自动的展示。这段话我理解,就是不需要先定义一个索引、字段类型,就可以直接创建,而且能够正常的显示。
put data/_doc/1
{
"count":5
}
动态的字段映射
当es检测到文档中有一个新增的字段,会动态地为这个字段添加类型的映射。动态的参数控制他的行为(可以通过动态参数选择是否需要动态映射)
json data type
null:对应的就不会有字段被添加
true or false 被映射成boolean
double float
long long
object object
array 取决于array中的第一个非空的值
date类型和数字类型的string,在date detection和numeric detection开启时,被影射成对应的date、float
未被检测为date/数组 会被影射成text
2.3 es的搜索过程
- 不同的查询语句,对应的搜索过程有什么区别:match、filter、term。
- 模糊查询的实现,性能。
- 分词后多term条件如何处理
其他同事给的一些链接:主要原因是wildcard查询会构建一个状态机,这个状态机如果state过多,超过10000就会报错,即使没有报错,性能也相当的差。特别是query为长query时
https://www.cnblogs.com/johnvwan/p/15645014.html
http://xiaobaoqiu.github.io/blog/2015/07/16/cratefu-wu-loadbiao-gao/