① elastic索引最多可以创建多少字段
elastic索引最多可以创建10000个字段,默认1000个。当分片被占满后,创建新索引失败。每个Elasticsearch碎片都是一个Lucene索引。一个Lucene索引中可以包含的文档最多。设置ignore_above后,超过给定长度后的数据将不被索引,无法通过term精确匹配检索返回结果。索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引针对表而建立,每个索引页面中的行都会含有逻辑指针,以便加速检索物理数据。
② 【ES】ElasticSearch 深入分片
@[toc]
分片是 Elasticsearch 在集群中分发数据的关键。
把分片想象成数据的容器。文档存储在分片中,然后分片分配到集群中的节点上。当集群扩容或缩小,Elasticsearch 将会自动在节点间迁移分片,以使集群保持平衡。
一个分片(shard)是一个最小级别“工作单元(worker unit)”,它只是保存了索引中所有数据的一部分。
这类似于 MySql 的分库分表,只不过 Mysql 分库分表需要借助第三方组件而 ES 内部自身实现了此功能。
分片可以是 主分片(primary shard) 或者是 复制分片(replica shard) 。
在集群中唯一一个空节点上创建一个叫做 blogs 的索引。默认情况下,一个索引被分配 5 个主分片,下面只分配 3 个主分片和一个复制分片(每个主分片都有一个复制分片)并码:
在一个多分片的索引中写入数据时,通过路由来确定具体写入哪一个分片中,大致路由过程如下:
routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。routing 通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到余数 。这个在 0 到 number_of_primary_shards 之间的余数,就是所寻求的文档所在分片的位置。
这解释了为什么要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量: 因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了 。
索引中的每个文档属于一个单独的主分片,所以 主分片的数量决定了索引最多能存储多少数据 (实际的数量取决于数据、硬件和应用场景)。
复制分片只是主分片的一个副本,它可以 防止硬件故障导致的数据丢失,同时可以提供读请求,比如搜索或者从别的 shard 取回文档 。
每个主分片都有一个或多个副本分片,当主分片异常绝饥哪时,副本可以提供数据的肢滚查询等操作。主分片和对应的副本分片是不会在同一个节点上的,所以副本分片数的最大值是 n -1(其中 n 为节点数)。
当索引创建完成的时候,主分片的数量就固定了,但是复制分片的数量可以随时调整,根据需求扩大或者缩小规模。如把复制分片的数量从原来的 1 增加到 2 :
分片本身就是一个完整的搜索引擎,它可以使用单一节点的所有资源。 主分片或者复制分片都可以处理读请求——搜索或文档检索,所以数据的冗余越多,能处理的搜索吞吐量就越大。
对文档的新建、索引和删除请求都是写操作,必须在主分片上面完成之后才能被复制到相关的副本分片,ES 为了提高写入的能力这个过程是并发写的,同时为了解决并发写的过程中数据冲突的问题,ES 通过乐观锁的方式控制,每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。一旦所有的副本分片都报告写成功才会向协调节点报告成功,协调节点向客户端报告成功。
ES 集群中每个节点通过路由都知道集群中的文档的存放位置,所以每个节点都有处理读写请求的能力。
在一个写请求被发送到某个节点后,该节点即为协调节点,协调节点会根据路由公式计算出需要写到哪个分片上,再将请求转发到该分片的主分片节点上。假设 shard = hash(routing) % 4 = 0 ,则过程大致如下:
写入磁盘的倒排索引是不可变的,优势主要表现在:
当然,不可变的索引有它的缺点:
在全文检索的早些时候,会为整个文档集合建立一个大索引,并且写入磁盘。只有新的索引准备好了,它就会替代旧的索引,最近的修改才可以被检索。这无疑是低效的。
因为索引的不可变性带来的好处,那如何在保持不可变同时更新倒排索引?
答案是,使用多个索引。 不是重写整个倒排索引,而是增加额外的索引反映最近的变化。 每个倒排索引都可以按顺序查询,从最老的开始,最后把结果聚合。
这就引入了 段 (segment) :
分片下的索引文件被拆分为多个子文件,每个子文件叫作 段 , 每一个段本身都是一个倒排索引,并且段具有不变性,一旦索引的数据被写入硬盘,就不可再修改。
段被 写入到磁盘 后会生成一个 提交点 ,提交点是一个用来记录所有提交后段信息的文件。一个段一旦拥有了提交点,就说明这个段只有读的权限,失去了写的权限。相反,当段在内存中时,就只有写的权限,而不具备读数据的权限,意味着不能被检索。
在 Lucene 中的索引(Lucene 索引是 ES 中的分片,ES 中的索引是分片的集合)指的是段的集合,再加上提交点(commit point),如下图:
在底层采用了分段的存储模式,使它在读写时几乎完全避免了锁的出现,大大提升了读写性能。
索引文件分段存储并且不可修改 ,那么新增、更新和删除如何处理呢?
ES 是怎么做到 近实时 全文搜索?
磁盘是瓶颈。提交一个新的段到磁盘需要 fsync 操作,确保段被物理地写入磁盘,即时电源失效也不会丢失数据。但是 fsync 是昂贵的,严重影响性能,当写数据量大的时候会造成 ES 停顿卡死,查询也无法做到快速响应。
所以 fsync 不能在每个文档被索引的时就触发,需要一种更轻量级的方式使新的文档可以被搜索,这意味移除 fsync 。
为了提升写的性能,ES 没有每新增一条数据就增加一个段到磁盘上,而是采用 延迟写 的策略。
每当有新增的数据时,就将其先写入到内存中,在内存和磁盘之间是文件系统缓存,当达到默认的时间(1秒钟)或者内存的数据达到一定量时,会触发一次刷新(Refresh),将内存中的数据生成到一个新的段上并缓存到文件缓存系统 上,稍后再被刷新到磁盘中并生成提交点 。
这里的内存使用的是ES的JVM内存,而文件缓存系统使用的是操作系统的内存。新的数据会继续的被写入内存,但内存中的数据并不是以段的形式存储的,因此不能提供检索功能。由内存刷新到文件缓存系统的时候会生成了新的段,并将段打开以供搜索使用,而不需要等到被刷新到磁盘。
在 Elasticsearch 中,这种写入和打开一个新段的轻量的过程叫做 refresh (即内存刷新到文件缓存系统)。默认情况下每个分片会每秒自动刷新一次。 这就是为什么说 Elasticsearch 是近实时的搜索了:文档的改动不会立即被搜索,但是会在一秒内可见。
也可以手动触发 refresh。 POST /_refresh 刷新所有索引, POST /index/_refresh 刷新指定的索引:
没用 fsync 同步文件系统缓存到磁盘,不能确保电源失效,甚至正常退出应用后,数据的安全。为了 ES 的可靠性,需要确保变更持久化到磁盘。
虽然通过定时 Refresh 获得近实时的搜索,但是 Refresh 只是将数据挪到文件缓存系统,文件缓存系统也是内存空间,属于操作系统的内存,只要是内存都存在断电或异常情况下丢失数据的危险。
为了避免丢失数据,Elasticsearch添加了 事务日志(Translog) ,事务日志记录了所有还没有持久化到磁盘的数据。
有了事务日志,过程现在如下:
事务日志记录了没有 flush 到硬盘的所有操作。当故障重启后,ES 会用最近一次提交点从硬盘恢复所有已知的段,并且从日志里恢复所有的操作。
在 ES 中,进行一次提交并删除事务日志的操作叫做 flush 。分片每 30 分钟,或事务日志过大会进行一次 flush 操作。 flush API 也可用来进行一次手动 flush , POST/ _flush 针对所有索引有效, POST /index/_flush 则指定的索引:
通常很少需要手动 flush ,通常自动的就够了。
总体的流程大致如下:
由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。每一个段都会消耗文件句柄、内存和 cpu 运行周期。更重要的是,每个搜索请求都必须轮流检查每个段然后合并查询结果,所以段越多,搜索也就越慢。
ES 通过后台合并段解决这个问题。小段被合并成大段,再合并成更大的段。这时旧的文档从文件系统删除的时候,旧的段不会再复制到更大的新段中。合并的过程中不会中断索引和搜索。
段合并在进行索引和搜索时会自动进行,合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中,这些段既可以是未提交的也可以是已提交的。
合并结束后老的段会被删除,新的段被 flush 到磁盘,同时写入一个包含新段且排除旧的和较小的段的新提交点,新的段被打开可以用来搜索。
1. 全文搜索引擎Elasticsearch,这篇文章给讲透了
2.ElasticSearch 权威指南》
③ Es实现百万级数据快速检索
在用户点击一篇采购文章,会匹配到该文章全部相关内容。所有数据是存在ES中的,百万量级。恩~要用python写一个接口。通过查找资料,通过 ES模糊搜索 可以实现。
prefix的匹配一般是处理不分词的场景,将会匹配articleID中以”J”开头的doc。prefix不会计算revelance score,只是作一个过滤的操作,和filter唯一的区别是filter会缓存结果,而prefix不会。前缀越短要处理的doc越多,茄高如性能越差。
?会匹配任意字符,*会匹配0个或多个字符。性能根prefix一样差,必须要扫描整个倒排索引。
[0-9]:指定范围内的数字
[a-z]:指定范围内的字幕
.:一个字符
+:前面的正则表达式可以出现一次或多次
正则的搜索同样会扫描全表,性能也会很差
fuzziness参数调整纠正的次数
通常不会直接用上述搜索,而会用下面的搜索:
在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所有文件的分数被返回。
下面用实验演示一下上述查询的相关语法,
1、首先,我们创建一个索引,并且在索引里添加几条数据,方便后面使用,
我这里直接批量插入数据,也可以通过PUT的语法单条执行插入,
POST /forum/article/_bulk
{ "index": { "_id": 1 }}
{ "articleID" : "XHDK-A-1293-#fJ3", "userID" : 1, "hidden": false, "postDate": "2019-07-01","title":"java contains hadoop and spark","topic":"java" }
{ "index": { "_id": 2 }}
{ "articleID" : "KDKE-B-9947-#kL5", "userID" : 1, "hidden": false, "postDate": "2019-07-02",title":"php contains admin","topic":"java and php" }
{ "index": { "_id": 3 }}
{ "articleID" : "JODL-X-1937-#pV7", "userID" : 2, "hidden": false, "postDate": "2019-07-03" ,title":"spark is new language","topic":"spark may use java"}
{ "index": { "_id": 4 }}
{ "articleID" : "QQPX-R-3956-#aD8", "userID" : 2, "hidden": true, "postDate": "2019-07-04" ,title":"hadoop may involve java","topic":"big data used"}
或者使用put语法
PUT /forum/article/4
{
"articleID": "QQPX-R-3956-#aD8",
"userID": 2,
"hidden": true,
"postDate": "2019-07-04",
"title": "hadoop may involve java",
"topic": "big data used"
}
4条数据插入成功,
2、termQuery,term查询不分词,类似于mysql的where filedName = ? 语法,即精准匹配,比如我们查询articleID = XHDK-A-1293-#fJ3的这条数据,
GET /forum/article/_search
{
"query": {
"term": {
"articleID.keyword":"XHDK-A-1293-#fJ3"
}
}
}
2、must查询,即查询的条件中必须匹配的字段,例如,查询title中必须包含java的数据,
GET /forum/article/_search
{
"query": {
"bool": {
"must": [
{"term":{"title":"hadoop"}}
]
}
}
}
查出两条数据
如果是should呢?如下语法,即查询title中包含hadoop或者topic中包含spark,二者满足其一即可,
GET /forum/article/_search
{
"query": {
"bool": {
"should": [
{"term":{"title":"hadoop"}},
{"term": {"topic": "spark"}}
]
}
}
}
查询出3条数据,
must和should结合使用,
最后再来一个比较复杂的嵌套查询,我们先看一下这条sql语句,
select *
from forum.article
where article_id=‘XHDK-A-1293-#fJ3’
or (article_id=‘JODL-X-1937-#pV7’ and post_date=‘2017-01-01’),
对应着转化为es的复合查询语法是怎样的呢?拆分来看,就是一个should语句的嵌套,
GET /forum/article/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"articleID.keyword": "XHDK-A-1293-#fJ3"
}
},
{
"bool": {
"must": [
{
"term": {
"articleID.keyword":"JODL-X-1937-#pV7"
}
},
{
"term": {
"postDate":"2019-07-01"
}
}
]
}
}
}
}
查询到一条结果,按照这种思路,如果我们对一个复杂的查询不知道如何构建查询语句时,可以考虑先按照sql的语法进行拆分,然后再组织es查询语句是个不错的突破口,
到这里,可能我们会有疑问,复合条件中的term查询和单纯的match区别在哪里呢?既然都是查询,究竟原理有何不同呢?
我们知道match query是需要全文检索的,是进行full text的全文检索,当然如果搜索的字段值做了not_analyzed,match query也相当于是term query了,比如下面这个搜索,由于在插入数据的时候我们没有对title这个字段进行规定,默认就是text类型的,会被自动分词,这样查询的时候只要title中包含了 hadoop,就可以匹配到,
GET /forum/article/_search
{
"query": {
"match": {
"title": "hadoop"
}
}
}
2、有些情况下,假如我们直接使用match进行查询,又希望查出来的结果尽可能是我们期望的包含更多关键词的结果,则在match进行匹配的时候可以添加其他的条件,以便提升结果的匹配精确度,
GET /forum/article/_search
{
"query": {
"match": {
"title": {
"query": "java hadoop",
"operator": "and"
}
}
}
}
这样匹配出来的结果包含了更多我们期望的关键词,即query中可以指定我们查询的结果中包含的关键词,
es还有其他的语法达到上述的效果,minimum_should_match ,通过这个语法,可以指定匹配的百分数,就是查询的关键词至少要达到的百分数,下面这个表示全部匹配,只查询到一条结果,
假如我们将百分数调低点,比如75%,可以看到查到两条结果,
3、当然,我们也可以将bool和match结合起来使用,如下,
GET /forum/article/_search
{
"query": {
"bool": {
"must": [
{"match": {"title": "java"}}
],
"must_not": [
{ "match": { "title": "spark"}}
]
, "should": [
{
"match": {
"title": "php"
}
}
]
}
}
}
通过这种方式,也可以达到更精准的匹配我们期望的查询结果,
简单总结来说,当我们使用match进行查询的时候,如果查询的field包含多个词,比如像下面这个,
{
"match": { "title": "java elasticsearch"}
}
其实es会在底层自动将这个match query转换为bool的语法bool should,指定多个搜索词,同时使用term query,则转化后的语法如下,
{
"bool": {
"should": [
{ "term": { "title": "java" }},
{ "term": { "title": "elasticsearch" }}
]
}
}
而上面所说的match中加and的查询,对应于bool查询,转化后为 term+must 的语法如下,
{
"match": {
"title": {
"query": "java elasticsearch",
"operator": "and"
}
}
}
{
"bool": {
"must": [
{ "term": { "title": "java" }},
{ "term": { "title": "elasticsearch" }}
]
}
}
对于minimum_should_match这种语法来说,道理类似,
{
"match": {
"title": {
"query": "java elasticsearch hadoop spark",
"minimum_should_match": "75%"
}
}
}
{
"bool": {
"should": [
{ "term": { "title": "java" }},
{ "term": { "title": "elasticsearch" }},
{ "term": { "title": "hadoop" }},
{ "term": { "title": "spark" }}
],
"minimum_should_match": 3
}
}
我们来看一个具体的操作实例,也就是说必须至少包含3个关键词的数据才会出现在搜索结果中,
3、在搜索中,我们有这样一种需求,期望搜索的结果中包含java 如果标题中包含hadoop或spark就优先搜索出来,同时呢,如果一个帖子包含java hadoop,一个帖子包含java spark,包含hadoop的帖子要比spark优先搜索出来,
对于这样的需求,通俗来讲,就是需要通过增大某些搜索条件的权重,从而在搜索的结果中,更多符合和满足我们业务场景的数据靠前搜索出来,在es中可以通过boost关键词来增加搜索条件的权重,
GET /forum/article/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "java"
}
}
],
"should": [
{
"match": {
"title": {
"query": "hadoop"
}
}
},
{
"match": {
"title": {
"query": "spark",
"boost":2
}
}
},
{
"match": {
"title": {
"query": "php"
}
}
},
{
"match": {
"title": {
"query": "hadoop",
"boost": 5
}
}
}
]
}
}
}
上面这个例子意思是我们赋予搜索的title中包含hadoop的条件权重更大,hadoop的结果会有限被搜索出来
4、dis_max语法,也叫best_field,在某些情况下,假如我们在bool查询中用多个字段进行查询,但是查询一样,就可能导致说查询出来的结果并不是按照我们期望的那个字段将其排在前面,也就是说,我们只需要包含指定字段的内容展示在前面,如下,
GET /forum/article/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "java solution" }},
{ "match": { "content": "java solution" }}
]
}
}
}
title和content的搜索条件相同,但我们希望的是结果中title 包含java solution的靠前展示,但直接这样查询可能达不到预期的效果,如果使用dis_max进行拼接就可以了,
GET /forum/article/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "java solution" }},
{ "match": { "content": "java solution" }}
]
}
}
}
通过这样的方式,使得查询的结果更符合预期值,
5、但是使用dis_max,只取某一个query最大的分数,完全不考虑其他query的分数,即假如说某个结果中包title含了java,但topic中没有包含java,另一却是相反,还有的结果是两者都包含java,在dis_max语法下,只会拿到相关度得分最高的那一个,而不会考虑其他的结果,这时,如果需要获取其他的title或者topic包含java的结果,可以使用tie_breaker进一步包装,如下,
GET /forum/article/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "spark" }},
{ "match": { "topic": "java"}}
],
"tie_breaker": 0.6
}
}
}
这样查到3条结果,综合来说,最终还是需要结合实际业务场景进行使用,但是在大多数情况相爱,我们还是希望搜索的结果中是按照我们给定的条件包含更多的关键词的内容被优先搜索出来,
④ Elasticsearch 能够存储的数据量一般有多大
单独看ES能玩多大数据意义不大,具体实践中往往因为各种业务要求而无法继续增加数据量。目大的方面考虑有如下几点:
1、查询速度。ES可以支持的查询类型多种多样,单一的term匹配,复杂的historm agg,甚至父子文档模式下bool查询之后继续做文本高亮,数据量越大查询时间越长。如果只是简单的把数据写进去然后按照ID获取数据,那就尽管往里面写数据吧。
2、写入速度。数据量越大,写入速度受影响的可能性越大。业务要求1小时的数据1小时内必须写完,如果做不到就得考虑分索引或者分集群了。
3、更新速度。同上,更新比单纯的写入操作更多,先get再merge再overwrite到es。
4、其他因素。
目前我遇到的ES集群,有1.5T-2T索引量的情况下,需要支持平均查询在500ms以内的高并发高亮查询。在我们的场景下这个量级不算小了。