Elasticsearch(一)架构及一般性应用
首先,当我们对记录进行修改时,es会把数据同时写到内存缓存区和translog中。而这个时候数据是不能被搜索到的,只有数据形成了segmentFile,才会被搜索到。默认情况下,es每隔一秒钟执行一次refresh,可以通过参数index.refresh_interval来修改这个刷新间隔或者搜索时加上?refresh=wait_for强制刷新,但是会造成刷新频次过高会造成性能下降,表示如果1秒内有请求立即更新并可见,执行refresh主要做三件事:
1、所有在内存缓冲区中的文档被写入到一个新的segment中,但是没有调用fsync,因此内存中的数据可能丢失;
2、segment被打开使得里面的文档能够被搜索到;
3、清空内存缓冲区;
translog的相当于事务日志,记录着所有对Elasticsearch的操作记录,也是对Elasticsearch的一种备份。因为并不是写到segment就表示数据落到磁盘了,实际上segment是存储在系统缓存(page cache)中的,只有达到一个周期或者数据量达到一定值,才会flush到磁盘上。这个时候如果系统内存中的segment丢失,是可以通过translog来恢复的。这个flush过程主要做了三件事:
1、往磁盘里写入commit point信息。
2、文件系统中的segment,fsync到磁盘。
3、清空translog文件。
translog可以保证缓存中的segment的恢复,但translog也不是实时也磁盘的,也就是说,内存中的translog丢了的话,也会有丢失数据的可能。所以translog也要进行flush。translog的flush主要有三个条件:
1、可以设置是否在某些操作之后进行强制flush,比如索引的删除或批量请求之后。
2、translog大小超过512mb或者超过三十分钟会强制对segment进行flush,随后会强制对translog进行flush,这种情况缓存中的translog在flush之后会被清空。
3、默认5s,会强制对translog进行flush。最小值可配置100ms。
6.3版本显示保留translog文件的最长持续时间。默认为12h。
参考官网:Translog | Elasticsearch Guide [6.3] | Elastic
refresh,flush 和fsync的区别
1.refresh是将缓冲队列buffer里数据刷入文件缓冲系统生成索引文件segement,该segement数据才能被查询到,保证查询属性可见
2.flush是将索引文件segement数据持久化到硬盘(触发机制是translog文件超过512mb或者30分钟强强制刷新segement)
3.fsyncd是将translog 持久化到硬盘(每5秒执行一次) 写入translog其实也是在内存中。translog 和segement持久化到硬盘是两回事。
持久化的translog文件中存的是所有索引成segement的数据但还未持久化到硬盘的内容,一旦segement持久化到硬盘translog会清空。
参考:https://elasticsearch.cn/question/3847
索引存储方式
Elasticsearch是一个建立在全文搜索引擎库Apache Lucene 基础上的分布式搜索引擎,Lucene最早的版本是2000年发布的,距今已经18年,是当今最先进,最高效的全功能开源搜索引擎框架。
Lucene
Lucene中包含了四种基本数据类型,分别是:
- Index:索引,由很多的Document组成。
- Document:由很多的Field组成,是Index和Search的最小单位。
- Field:由很多的term组成,包括field_name和field_value。
- Term:由很多的字节组成,可以分词,分词之后每个词即为一个term。term是索引的最小单元。
上述四种类型在Elasticsearch中同样存在,意思也一样。
Lucene中存储的索引主要分为三种类型:
- Invert Index,即倒排索引。通过term可以快速查找到包含该term的doc_id。如果Field配置分词,则分词后的每个term都会进入倒排索引,如果Field不指定分词,那该Field的value值则会作为一个term进入倒排。(这里需要注意的是term的长度是有限制的,如果对一个Field不采取分词,那么不建议该Field存储过长的值。关于term超长处理)
- DocValues,即正排索引。采用的是类似数据库的列式存储。对于一些特殊需求的字段可以选择这种索引方式。
- Store,即原文。存储整个完整Document的原始信息。
倒排索引是lucene的核心索引类型,采用链表的数据结构,倒排索引中的key就是一个term,value就是以doc_id形成的链表结构。
Term Doc_1 Doc_2
-------------------------
Quick | | X
The | X |
brown | X | X
dog | X |
dogs | | X
fox | X |
foxes | | X
in | | X
jumped | X |
lazy | X | X
leap | | X
over | X | X
quick | X |
summer | | X
the | X |
------------------------
现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:
Term Doc_1 Doc_2
-------------------------
brown | X | X
quick | X |
------------------------
Total | 2 | 1
这里分别匹配到了doc1和doc2,但是doc1匹配度要高于doc2。
倒排索引中的value有四种存储类型:
- DOCS:只存储doc_id。
- DOCS_AND_FREQS:存储doc_id和词频(Term Freq)。
- DOCS_AND_FREQS_AND_POSITIONS:存储doc_id、词频(Term Freq)和位置。
- DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS:存储doc_id、词频(Term Freq)、位置和偏移(offset)。
DocValues
正排索引类似关系型数据库的存储模式,作用是通过doc_id和field_name可以快速定位到指定doc的特定字段值。DocValues的key是doc_id+field_name,value是field_value。
ES默认会对所有字段进行正排索引,但并不是所有字段都需要DocValues。所以合理配置DocValues可以节省存储空间。DocValues的使用场景一般是:需要针对某个Field排序、聚合、过滤和script。
Store
存储的是Document的完整信息,包括所有field_name和field_value。Store的key是doc_id,value是field_name+field_value。对于上诉中需要聚合和排序的Field并没有开启DocValues的情况,依然可以实现排序和聚合,会从Store中获取要排序聚合的字段值。
Elasticsearch在Lucene基础上的改变
Lucene本身不支持分布式,Elasticsearch通过_routing实现分布式的架构。我们可以通过_routing来实现不同doc分布在不同的Shard上,我们可以自己定义也可以系统自动分配。对于指定_routing的插入和查询,性能上会更好。
Lucene中没有主键索引,并且id是在Segment中唯一。那么Elasticsearch是如何实现doc_id唯一?Elasticsearch中有个系统字段_id,来决定doc唯一。_id是在用户可见层度上的id值,实际上Elasticsearch内部会把_id存储成_uid(_uid =index_type + '#' + _id)。_uid只会存储倒排和原文,目的就是为了通过id可以快速索引到doc。这里需要注意的是,在Elasticsearch6.x版本以后,一个index只支持一个type,这也就意味着_id和_uid概念几乎相同。以后的版本中,Elasticsearch会取消type。
Elasticsearch通过_version字段来保证文档的一致性。更多关于文档的一致性和锁机制的参考:ElasticSearch干货(一):锁机制
Elasticsearch通过_source字段来存储doc原文。这个字段非常重要。Lucene的update是覆盖,是不支持针对doc中特定字段进行修改的。但Elasticsearch支持对特定字段的修改,就是基于_source字段实现的。关于Elasticsearch的update详细内容,参考:ElasticSearch干货(二):index、create、update区别
Elasticsearch通过_field_names字段来判断doc中是否存在某个字段。_field_names的存储形式为倒排,可以快速判断出是否包含某个field_name。
Lucene中Segment一旦创建不可修改。那么Elasticsearch如何实现实时修改并索引数据的呢?详细参考:ElasticSearch原理(三):写入流程
关于原理和索引先介绍到这里,主要这里还是聚焦与如何实现几个关键需求?
ES延时问题
默认情况下,es每隔一秒钟执行一次refresh,可以通过参数index.refresh_interval来修改这个刷新间隔或者搜索时加上?refresh=wait_for强制刷新,但是会造成刷新频次过高会造成性能下降,表示如果1秒内有请求立即更新并可见。另外由于没有生成segment,也就是说不能通过索引来获取,但是可以通过直接get by id来获取单条记录。
搜索(同时实现精确查询和模糊查询和组合查询)
ES的搜索是分2个阶段进行的,即Query阶段和Fetch阶段。 Query阶段比较轻量级,通过查询倒排索引,获取满足查询结果的文档ID列表。 而Fetch阶段比较重,需要将每个shard的结果取回,在协调结点进行全局排序。 通过From+size这种方式分批获取数据的时候,随着from加大,需要全局排序并丢弃的结果数量随之上升,性能越来越差。
1、精确查询
在elasticsearch 中输入查询条件,一般会匹配到很多结果,是因为analyzer的存在:
Each element in the result represents a single term:
{"tokens": [{"token": "text","start_offset": 0,"end_offset": 4,"type": "<ALPHANUM>","position": 1},{"token": "to","start_offset": 5,"end_offset": 7,"type": "<ALPHANUM>","position": 2},{"token": "analyze","start_offset": 8,"end_offset": 15,"type": "<ALPHANUM>","position": 3}] }
必须要将字段设置为not_analyzed 才可以,如下:
PUT /my_store { "mappings" : { "products" : { "properties" : { "productID" : { "type" : "string", "index" : "not_analyzed" } } } } }
2、es组合多个条件进行查询
1、must、should
GET /test_index/_search
{
"query": {
"bool": {
"must": { "match": { "name": "tom" }},
"should": [
{ "match": { "hired": true }},
{ "bool": {
"must": { "match": { "personality": "good" }},
"must_not": { "match": { "rude": true }}
}}
],
"minimum_should_match": 1
}
}
}
在es中,使用组合条件查询是其作为搜索引擎检索数据的一个强大之处,在前几篇中,简单演示了es的查询语法,但基本的增删改查功能并不能很好的满足复杂的查询场景,比如说我们期望像mysql那样做到拼接复杂的条件进行查询该如何做呢?es中有一种语法叫bool,通过在bool里面拼接es特定的语法可以做到大部分场景下复杂条件的拼接查询,也叫复合查询
首先简单介绍es中常用的组合查询用到的关键词,
filter:过滤,不参与打分
must:如果有多个条件,这些条件都必须满足 and与
should:如果有多个条件,满足一个或多个即可 or或
must_not:和must相反,必须都不满足条件才可以匹配到 !非
发生 描述
must
该条款(查询)必须出现在匹配的文件,并将有助于得分。
filter
子句(查询)必须出现在匹配的文档中。然而不像 must查询的分数将被忽略。Filter子句在过滤器上下文中执行,这意味着评分被忽略,子句被考虑用于高速缓存。
should
子句(查询)应该出现在匹配的文档中。如果 bool查询位于查询上下文中并且具有mustor filter子句,则bool即使没有should查询匹配,文档也将匹配该查询 。在这种情况下,这些条款仅用于影响分数。如果bool查询是过滤器上下文 或者两者都不存在,must或者filter至少有一个should查询必须与文档相匹配才能与bool查询匹配。这种行为可以通过设置minimum_should_match参数来显式控制 。
must_not
子句(查询)不能出现在匹配的文档中。子句在过滤器上下文中执行,意味着评分被忽略,子句被考虑用于高速缓存。因为计分被忽略,0所有文件的分数被返回。
3、模糊查询
前缀查询:匹配包含具有指定前缀的项(not analyzed)的字段的文档。前缀查询对应 Lucene 的 PrefixQuery 。
案例
GET /_search
{ "query": {"prefix" : { "user" : { "value" : "ki", "boost" : 2.0 } }}
}
正则表达式查询:egexp (正则表达式)查询允许您使用正则表达式进行项查询。有关支持的正则表达式语言的详细信息,请参阅正则表达式语法。第一个句子中的 “项查询” 意味着 Elasticsearch 会将正则表达式应用于由该字段生成的项,而不是字段的原始文本。注意: regexp (正则表达式)查询的性能很大程度上取决于所选的正则表达式。匹配一切像 “.*” ,是非常慢的,使用回顾正则表达式也是如此。如果可能,您应该尝试在正则表达式开始之前使用长前缀。通配符匹配器 “.*?+” 将主要降低性能。
案例
GET /_search
{"query": {"regexp":{"name.first":{"value":"s.*y","boost":1.2}}}
}
‘
通配符查询:匹配与通配符表达式具有匹配字段的文档(not analyzed)。支持的通配符是 “*”,它匹配任何字符序列(包括空字符);还有 “?”,它匹配任何单个字符。请注意,此查询可能很慢,因为它需要迭代多个项。为了防止极慢的通配符查询,通配符项不应以通配符 “*” 或 “?” 开头。通配符查询对应 Lucene 的 WildcardQuery 。
案例
GET /_search
{"query": {"wildcard" : { "user" : { "value" : "ki*y", "boost" : 2.0 } }}
}
###模糊查询数据量越大效率越低,当查询内容较多,数据量较大时建议将该字段设置成text进行分词,然后通过match进行匹配。
排序与相关性
默认情况下,返回的结果是按照 相关性 进行排序的——最相关的文档排在最前。 在本章的后面部分,我们会解释 相关性 意味着什么以及它是如何计算的, 不过让我们首先看看 sort 参数以及如何使用它。
1、排序
为了按照相关性来排序,需要将相关性表示为一个数值。在 Elasticsearch 中, 相关性得分 由一个浮点数进行表示,并在搜索结果中通过 _score 参数返回, 默认排序是 _score 降序。
有时,相关性评分对你来说并没有意义。例如,下面的查询返回所有 user_id 字段包含 1 的结果:
GET /_search
{"query" : {"bool" : {"filter" : {"term" : {"user_id" : 1}}}}
}
里没有一个有意义的分数:因为我们使用的是 filter (过滤),这表明我们只希望获取匹配 user_id: 1 的文档,并没有试图确定这些文档的相关性。 实际上文档将按照随机顺序返回,并且每个文档都会评为零分。
1.1、按照字段的值排序
在这个案例中,通过时间来对 tweets 进行排序是有意义的,最新的 tweets 排在最前。 我们可以使用 sort 参数进行实现:
GET /_search
{"query" : {"bool" : {"filter" : { "term" : { "user_id" : 1 }}}},"sort": { "date": { "order": "desc" }}
}
1.2、多级排序
假定我们想要结合使用 date 和 _score 进行查询,并且匹配的结果首先按照日期排序,然后按照相关性排序:
GET /_search
{"query" : {"bool" : {"must": { "match": { "tweet": "manage text search" }},"filter" : { "term" : { "user_id" : 2 }}}},"sort": [{ "date": { "order": "desc" }},{ "_score": { "order": "desc" }}]
}
排序条件的顺序是很重要的。结果首先按第一个条件排序,仅当结果集的第一个 sort 值完全相同时才会按照第二个条件进行排序,以此类推。
多级排序并不一定包含 _score 。你可以根据一些不同的字段进行排序, 如地理距离或是脚本计算的特定值。
1.3、字段多值的排序
一种情形是字段有多个值的排序, 需要记住这些值并没有固有的顺序;一个多值的字段仅仅是多个值的包装,这时应该选择哪个进行排序呢?
对于数字或日期,你可以将多值字段减为单值,这可以通过使用 min 、 max 、 avg 或是 sum 排序模式 。 例如你可以按照每个 date 字段中的最早日期进行排序,通过以下方法:
"sort": {"dates": {"order": "asc","mode": "min"}
}
- 更多详情清参考此文;
如何在elasticsearch里面使用分页功能
from + size 浅分页
"浅"分页可以理解为简单意义上的分页。它的原理很简单,就是查询前20条数据,然后截断前10条,只返回10-20的数据。这样其实白白浪费了前10条的查询。
GET test_dev/_search
{"query": {"bool": {"filter": [{"term": {"age": 28}}]}},"size": 10,"from": 20,"sort": [{"timestamp": {"order": "desc"},"_id": {"order": "desc"}}]
}
其中,from定义了目标数据的偏移值,size定义当前返回的数目。默认from为0,size为10,即所有的查询默认仅仅返回前10条数据。
在这里有必要了解一下from/size的原理:
因为es是基于分片的,假设有5个分片,from=100,size=10。则会根据排序规则从5个分片中各取回100条数据数据,然后汇总成500条数据后选择最后面的10条数据。
做过测试,越往后的分页,执行的效率越低。总体上会随着from的增加,消耗时间也会增加。而且数据量越大,就越明显!
es默认的from+size的分页方式返回的结果数据集不能超过1万点,超过之后返回的数据越多性能就越低;
这是因为es要计算相似度排名,需要排序整个整个结果集,假设我们有一个index它有5个shard,现在要读取1000到1010之间的这10条数据,es内部会在每个shard上读取1010条数据,然后返回给计算节点,这里有朋友可能问为啥不是10条数据而是1010条呢?这是因为某个shard上的10条数据,可能还没有另一个shard上top10之后的数据相似度高,所以必须全部返回,然后在计算节点上,重新对5050条数据进行全局排序,最后在选取top 10出来,这里面排序是非常耗时的,所以这个数量其实是指数级增长的,到后面分页数量越多性能就越下降的厉害,而且大量的数据排序会占用jvm的内存,很有可能就OOM了,这也是为什么es默认不允许读取超过1万条数据的原因。
scroll 深分页
from+size查询在10000-50000条数据(1000到5000页)以内的时候还是可以的,但是如果数据过多的话,就会出现深分页问题。
为了解决上面的问题,elasticsearch提出了一个scroll滚动的方式。
scroll 类似于sql中的cursor,使用scroll,每次只能获取一页的内容,然后会返回一个scroll_id。根据返回的这个scroll_id可以不断地获取下一页的内容,所以scroll并不适用于有跳页的情景。
GET test_dev/_search?scroll=5m
{"query": {"bool": {"filter": [{"term": {"age": 28}}]}},"size": 10,"from": 0,"sort": [{"timestamp": {"order": "desc"},"_id": {"order": "desc"}}]
}
- scroll=5m表示设置scroll_id保留5分钟可用。
- 使用scroll必须要将from设置为0。
- size决定后面每次调用_search搜索返回的数量
然后我们可以通过数据返回的_scroll_id读取下一页内容,每次请求将会读取下10条数据,直到数据读取完毕或者scroll_id保留时间截止:
GET _search/scroll
{"scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAJZ9Fnk1d......","scroll": "5m"
}
注意:请求的接口不再使用索引名了,而是 _search/scroll,其中GET和POST方法都可以使用。
scroll删除
根据官方文档的说法,scroll的搜索上下文会在scroll的保留时间截止后自动清除,但是我们知道scroll是非常消耗资源的,所以一个建议就是当不需要了scroll数据的时候,尽可能快的把scroll_id显式删除掉。
清除指定的scroll_id:
DELETE _search/scroll/DnF1ZXJ5VGhlbkZldGNo.....
清除所有的scroll:
DELETE _search/scroll/_all
search_after 深分页
scroll 的方式,官方的建议不用于实时的请求(一般用于数据导出),因为每一个 scroll_id 不仅会占用大量的资源,而且会生成历史快照,对于数据的变更不会反映到快照上。
为了找到每一页最后一条数据,每个文档必须有一个全局唯一值,官方推荐使用 _uid 作为全局唯一值,其实使用业务层的 id 也可以。
GET test_dev/_search
{"query": {"bool": {"filter": [{"term": {"age": 28}}]}},"size": 20,"from": 0,"sort": [{"timestamp": {"order": "desc"},"_id": {"order": "desc"}}]
}
GET test_dev/_search
{"query": {"bool": {"filter": [{"term": {"age": 28}}]}},"size": 10,"from": 0,"search_after": [1541495312521,"d0xH6GYBBtbwbQSP0j1A"],"sort": [{"timestamp": {"order": "desc"},"_id": {"order": "desc"}}]
}
</div>
Elasticsearch(一)架构及一般性应用相关推荐
- Elasticsearch 分布式架构原理
前言 前面介绍了很多ES使用过程中的具体实战知识点,本文主要是谈谈ES分布式架构原理. 一.Elasticsearch特点 elasticsearch是近实时的分布式搜索分析引擎,底层实现基于Luce ...
- ElasticSearch部署架构和容量规划
一.前言 前面介绍了ElasticSearch原理和使用相关的内容,在生产环境如何比较科学的进行容量规划.部署.调优.排查问题呢,业界和官方也对相关的问题进行总结,我这边也结合自己的经验对这些使用El ...
- Elasticsearch的架构
为什么要学习架构? Elasticsearch的一些架构设计,对我们做性能调优.故障处理,具有非常重要的影响.下面将从Elasticsearch的准实时索引的实现.自动发现.rounting和repl ...
- ES:Elasticsearch的架构(二)
Gateway层 es用来存储索引文件的一个文件系统且它支持很多类型,例如:本地磁盘.共享存储(做snapshot的时候需要用到).hadoop的hdfs分布式存储.亚马逊的S3.它的主要职责是用来对 ...
- 从Elasticsearch来看分布式系统架构设计,真是666~
欢迎关注方志朋的博客,回复"666"获面试宝典 分布式系统类型多,涉及面非常广,不同类型的系统有不同的特点,批量计算和实时计算就差别非常大.这篇文章中,重点会讨论下分布式数据系统的 ...
- ElasticSearch是否有架构?
1.ElasticSearch可以有一个架构.架构是描述文档类型以及如何处理文档的不同字段的一个或多个字段的描述.Elasticsearch中的架构是一种映射,它描述了JSON文档中的字段及其数据类型 ...
- 从 Elasticsearch 来看分布式系统架构设计
云栖君导读: 分布式系统类型多,涉及面非常广,不同类型的系统有不同的特点,批量计算和实时计算就差别非常大.这篇文章中,重点会讨论下分布式数据系统的设计,比如分布式存储系统,分布式搜索系统,分布式分析系 ...
- Elasticsearch 架构以及源码概览
https://mp.weixin.qq.com/s/k_FjSOqZmTaCknBRwaiw7Q Elasticsearch 是最近两年异军突起的一个兼有搜索引擎和NoSQL数据库功能的开源系统,基 ...
- 58同城 Elasticsearch 应用及平台建设实践
分享嘉宾:于伯伟 58同城 高级架构师 编辑整理:陈树昌 内容来源:DataFunTalk 导读:Elasticsearch是一个分布式的搜索和分析引擎,可以用于全文检索.结构化检索和分析,并能将这三 ...
最新文章
- 第三代测序单分子荧光测序之Pacbio 测序原理
- linux开发板加快开机速度,readahead加速Linux开机速度
- 发现一个病毒文件你删了他又自动创建怎么解决
- 程序员必须要掌握的十大经典算法
- 微软e5服务器,OFFICE365 E5调用api使E5开发者续订(不使用服务器)
- java的幂运算_java数组五种运算符
- Android开机动画过程
- 总之就是不太可爱(思维严谨性的考验)
- 【多传感器融合理论】01自动驾驶中常用传感器硬件介绍
- linux 的截屏软件下载,Linux 截屏软件 Shutter
- apk与服务器的ip在哪个文件夹,手机中apk文件存放目录在哪
- 2011最犀利语录大全
- gtk之G_LIKELY(expr)和G_UNLIKELY(expr)
- 什么是html超文本语言,什么是超文本?HTML超文本标记语言怎么学?
- P2P 之 UDP穿透NAT的原理与实现
- 网络丢包产生的几个原因,总结!
- 马尔萨斯人口论与数学模型
- 小梅哥Xilinx FPGA学习笔记1——二选一多路器
- 判断piv_str1是否包含在piv_str2中,成功返回值大于1,失败返回0
- SolidWorks如何更改二维图纸中的标注样式
热门文章
- svn里ignore不需要提交的用户文档
- 笔记本应用/测试软件大全(个人使用心得)
- python连接oracle进行监控_使用Python脚本zabbix自定义key监控oracle连接状态
- python定义一个圆_Python-矩形和圆形
- 如何处理高并发写入mysql_如何处理高并发情况下的DB插入
- 开发商微信选房后不退认筹金_【震惊】胶州恒大文化旅游城项目爆丑闻!2000余人缴纳认筹金后竟有1800余户因对房子不满意提出退款...
- python文件操作解码_python基础3之文件操作、字符编码解码、函数介绍
- mysql8.0.17压缩包安装教程_超详细的MySQL8.0.17版本安装教程
- 传递function_boost库function与bind
- 怎么获取layer中的表单值_layer获取弹出frame层数据