ElasticSearch 笔记
CRUD
Bulk Api
批量查询
数据迁移
创建新的索引,reindex 数据
POST _reindex
{
"source": {
"index": "旧索引"
},
"dest": {
"index": "新索引"
}
}
倒排索引
倒排索引包含两个核心组成部分
单词词典,记录所有文档的单词,记录单词到倒排列表的关联关系
单词词典一般比较大,可以通过 B+或者哈希拉链法实现,以满足高性能查询
倒排列表(Posting List),记录了单词对应的文档组合,由倒排索引项组成
倒排索引项
文档 ID
词频 TF - 该单词在文档中出现的次数,用于相关性评分
位置(Position)- 单词在文档中出现的位置,用于语句搜索
偏移(Offset)- 记录单词的开始结束位置,实现高亮显示
Analyzer
文本分析是把全文本转换成一系列单词的过程,也叫分词
Analyis 是将文本转换成倒排索引中 Terms 的过程
除了在数据写入时转换词条,匹配 Query 语句时也需要用相同分析器对查询语句进行分析
分词器是专门处理分词的组件,Analyzer 由三部分组成
character filters - 针对原始文本进行处理,例如去除 Html
tokenizer - 按照规则切分单词,例如空格
token filter - 将切分的单词进行加工,小写,删除 stop words(in/on/is/the),增加同义词
ElasticSearch 内置分词器
URI Search
能否更新 Mapping 的字段类型
Index Options
Null Value
Copy_to
这种情况 source 中并没有 fullName,但可以指定该字段进行查询:
Index Templates
用于指定自动生成的 mapping 规则
工作方式
Term 精确查询
包含前缀查询、范围查询、通配符查询
ES 中,term 查询不管是 keyword 还是 text 对输入都不做分词,会将输入作为一个整体,在倒排索引中查找准确词项,并且使用相关度算分公示为每个包含该词项的文档进行相关度算分
ES 在插入数据默认会将,大写英文转成小写,如需完全匹配:"字段.keyword" : "查询值"
大部分情况 term 查询需要忽略 TF-IDF 计算,避免相关性算分的开销,可以将 query 转成 filter,并且 filter 可以有效利用缓存
日期区间
term 查询其实是包含关系,而不是精确匹配。比如,["A", "B"] 此时搜索 "A",依然可以匹配到
Match 全文本查询
索引和搜索时都会分词,查询字符串先传递到一个合适的分词器,然后生成共查询的词项列表
查询的时候会对输入的查询进行分词,然后每个词项逐个进行底层查询,最终将结果进行合并,并为每个文档进行算分。 - 例如查询"Matrix reloaded",会查到 matrix 或 reload 的所有结果
如果需要结果两者都存在,则需要添加 AND 条件
相关性算分
TF-IDF
相关性 - relevance
搜索的相关性算分,描述了一个文档和查询语句的匹配程度。ES 会对每个匹配查询条件的结果进行算分_score
打分的本质是排序,需要把最符合用户需求的文档排在前面。ES 5 之前默认的相关性算分采用 TF-IDF,现在采用 BM25
词频
Term Frequency:检索词在一篇文档中的出现频率(检索词出现的次数 / 文档总字数)
度量一条查询和结果文档相关性的简单方法:简单将搜索中每个词的 TF 进行相加,如"区块链的应用":TF(区块链) + TF(的) + TF(应用)
stop word:"的"在文档中出现了很多次,但对相关度没有贡献,不应该考虑它的 TF
BM25
从 ES 5 开始,默认打分算法改为 BM25
和经典的 TF-IDF 相比,当 TF 无限增加时,BM25 算分会趋于一个数值
Boosting Relevance
Bool Query 组合查询
filter,must_not 属于 Filter Context 不会影响算分,should,must 属于 Query Context 查询会影响算分
通过指定 boost 字段,可以影响算分
通过指定 negative 字段,可以减少算分,将不相关值排到末尾
should 算分过程
查询满足 should 条件的所有 doc
加和满足条件查询的评分
乘以匹配语句总数
除以所有语句总数
这种简单将分数加和的算分机制,有可能导致最佳匹配的 doc 分数反而偏低
Disjunction max query
将任何与任一查询匹配的文档作为结果返回。采用字段上最匹配的评分作为最终评分返回
Multi Match 单字符串多字段查询
三种场景
最佳字段(Best Fields)
当字段之间相互竞争,又相互关联。例如 title 和 body 这样的字段。评分来自最匹配字段
Best Fields 是默认类型,可以不用指定
多数字段(Most Fields)
处理英文内容时:一种常见的手段是,在主字段(English Analyzer),抽取词干,加入同义词,以匹配更多的文档。相同文本,加入子字段(Standard Analyzer),以提供更加精确的匹配。其他字段作为匹配文档提高相关度信号。匹配字段越多分数越高
用 English 分词器分词,用 Standard 分词器保证搜索精度
跨字段搜索(Cross Field)
对于某些实体,例如图书,人名,地址信息等,需要在多个字段中确定信息,单个字段只能作为整体的一部分,希望在列出的的字段中找到尽可能多的词
支持使用 Operator
可以在搜索时为单个字段提升权重
Function Score Query
按照受欢迎度提升权重
希望将 skuId 大的数据,放在搜索列表相对靠前的位置。同时搜索的评分还是要作为主要依据
新的算分 = 老的算分 * skuId
使用 modifier 平滑曲线,新的算分 = 老的算分 * log(1 + skuId)
使用 factor,新的算分 = 老的算分 * log(1 + factor * skuId)
seed 不变,随机返回的数据排序就不会改变
搜索建议
Suggester API
搜索"pple"
每个建议都包含了一个算分,相似性是通过 Levenshtein Edit Distance 算法实现的。核心思想就是一个词改动多少字符可以和另外一个词一致。提供了很多可选参数来控制相似性的模糊程度。例如:"max_edits"
几种 suggest_mode
Missing - 如果索引中已经存在就不提供建议
Popular - 推荐出现频率更加高的词
Always - 无论是否存在都提供建议
默认首字母不一致就不会推荐,但是如果将 prefix_length 设置为 0,就会为 pple 建议 Apple
自动补全和基于上下文提示
Auto Complete
Completion Suggester 提供了自动完成(Auto Complete)的功能。用户每输入一个字符,就需要即时发送一个查询请求到后端查找匹配项
对性能要求比较严苛。ES 采用了不同的数据结构,并非倒排索引。而是将 Analyze 的数据编码成 FST 和索引一起存放。FST 会被 ES 整个加载进内存,速度很快
FST 只能用于前缀查找
设置字段 mapping 为 completion
Context Suggester
可以定义两种类型的 Context
category - 任意字符串
geo - 地理位置信息
实现 context suggester 的具体步骤
定制一个 mapping
索引数据,并且为每个文档加入 context 信息
结合 context 进行 suggestion 查询,根据不同上下文,自动提示
精准度和召回率
跨集群搜索 - Cross Cluster Search
ES 5.3 引入了跨集群搜索功能,推荐使用
允许任何节点扮演 federated 节点,以轻量的方式将搜索请求代理
不需要以 client node 形式加入其他集群
配置及查询
集群分布式模型及选主与脑裂问题
分布式特性
节点
Coordinating Node
Data Node
ES 中主分片和副本分片一定不会分配在一个节点上
Master Node
Master Eligible Node & 选主过程
集群状态
脑裂问题
如何避免脑裂
配置参数建议
分片与集群故障转移
主分片 - 提升系统存储容量
副本分片 - 提高数据可用性
分片数的设定
Es 7.0 之后默认主分片数量是 1,副本分片数量是 0
单节点集群,副本无法分片,集群状态为黄色
增加一个数据节点,集群状态转为绿色,集群具备故障转移能力
master 会决定分片分配到哪个节点
通过增加节点,提高集群计算能力
故障转移
集群健康状态
文档分布式存储
文档会存储在具体某个主分片和副本分片上:例如 文档 1,会存储在 R0 和 R1 分片上
文档到分片的路由算法:shard = hash(_routing) % number_of_primary_shards
Hash 算法确保⽂文档均匀分散到分⽚片中
默认的 _routing 值是⽂档 id
可以⾃行制定 routing 数值,例如⽤相同国家的商品,都分配到指定的 shard
设置 Index Settings 后, Primary 数不能随意修改的根本原因
文档更新过程
用户发出更新请求到一个节点
该节点扮演 Coordinating 角色,通过 hash 算法算出这个数据应该被路由到哪个分片,将该请求发送到对应分片
Es 的更新过程是先 delete,后 index
更新完成后通知 Coordinating 节点更新成功
Coordinating 节点返回用户响应结果
删除文档过程
用户发出更新请求到一个节点
该节点扮演 Coordinating 角色,通过 hash 算法算出这个数据应该被路由到哪个分片,将该请求发送到对应分片
对应节点将数据删除,并且通过 cluster state 找到备份节点,将删除请求发送给备份节点
备份分片节点删除后,通知主分片节点
主分片节点删除完成后通知 Coordinating 节点删除成功
Coordinating 节点返回用户响应结果
分片及其生命周期
分片内部原理
什么是分片
ES 中最⼩的⼯作单元 / 是一个 Lucene 的 Index
一些问题:
为什么 ES 的搜索是近实时的(1 秒后被搜到)
ES 如何保证在断电时数据也不会丢失
为什么删除⽂档,并不会⽴刻释放空间
倒排索引不可变性
倒排索引采⽤ Immutable Design,一旦生成,不可更改
不可变性,带来了的好处如下:
无需考虑并发写文件问题,避免锁机制带来的性能问题
一旦读入内核的⽂件系统缓存,便留在缓存中。只要文件系统存有⾜够的空间,大部分请求就会直接请求内存,不会命中磁盘,提升了很⼤的性能
缓存容易生成和维护 / 数据可以被压缩
不可变更性,带来了的挑战:如果需要让一个新的文档可以被搜索,需要重建整个索引。
Lucene Index
在 Lucene 中,单个倒排索引文件被称为 Segment。Segment 是自包含的,不可变更的。多个 Segments 汇总在一起,称为 Lucene 的 Index,其对应的就是 ES 中的 Shard
当有新文档写入时,会生成新 Segment,查询时会同时查询所有 Segments,并且对结果汇总。Lucene 中有⼀个文件,用来记录所有 Segments 信息,叫做 Commit Point
删除的文档信息,并不会被立刻删除,而是保存在“.del”文件中
ES Refresh
Es 在写入数据时会将数据先写入 Index Buffer 的内存空间
一定时间(默认一秒)后,Index Buffer 会将其中的所有数据写入 segment 中,这个过程就叫 Refresh
当文档被 Refresh 到 segment 中后才可以被搜索到
Refresh 频率:默认 1 秒发生一次,可通过 index.refresh_interval 配置。Refresh 后,数据就可以被搜索到了。这也是为什么 ElasticSearch 被称为近实时搜索
Index Buffer 被占满时,会触发 Refresh,默认值是 JVM 的 10%
Transaction Log
如果有大量写入,就会产生大量 segment,将 segment 写入磁盘的过程相对也是比较耗时的。所以 ES 会借助文件系统缓存,Refresh 时首先将 segment 写入缓存以开放查询
为了保证数据不会丢失,所以在 Index 文档时,同时会写 Transaction Log,现在版本默认落盘(顺序写),每个分片有一个 Transaction Log
在 Es Refresh 时,Index Buffer 会被清空, Transaction Log 不会清空
所以就算断电 Es 中的数据也不会丢失
Es Flush
调用 Refresh,清空 Index Buffer
调用 fsync,将缓存中的 segment 写入磁盘
清空 Transaction Log
因为这个过程比较重,默认 30 分钟一次
Transaction Log 写满(默认 512M)后也会触发
Es Merge
当 Flush 完成后 segment 会从内存被写入磁盘,随着时间流逝,磁盘上的 segment 文件也会越来越多,所以需要定期对其进行处理
Merge 可以将多个 segment 合并成一个,同时会将.del 文件中的数据真正删除
Es 和 lucene 会自动进行 merge 操作
剖析分布式查询及相关性算分
分布式搜索的运行机制
Es 的搜索会分两阶段进行(举例:目前有三个主分片,各有一个备份分片)
第一阶段 Query
⽤用户发出搜索请求到 ES 节点。节点收到请求后, 会以 Coordinating 节点的身份,在 6 个主副分⽚片中随机选择 3 个分片,发送查询请求
被选中的分片执行查询,进行排序。然后每个分片都会返回 From + Size 个排序后的文档 Id 和排序值 给 Coordinating 节点
第二阶段 Fetch
Coordinating Node 会将 Query 阶段,从每个分片获取的排序后的文档 Id 列表,重新进行排序。选取 From 到 From + Size 个⽂档的 Id
以 multi get 请求的方式,到相应的分⽚获取详细的⽂档数据
Query then Fetch 潜在问题
性能问题
每个分片需要查询的文档个数 = from + size
如果系统中主分片数设置比较大,协调节点就需要处理很多数据:number_of_shard * (from + size)
所以如果在处理深度分页时,性能会有很大影响
相关度算分
每个分片都是基于自己分片上的数据进行相关度计算。如果数据量较少,就会导致算分偏离。相关性算分在分片之间是相互独立。当文档总数很少时,如果主分片大于 1,主分片数越多,相关性算分会越不准
解决算分不准的方法
数据量不大时,可以将主分片数设置为 1
当数据量足够大时,只要保证数据均匀分散在各个分片上,结果一般也就不会出现偏差
排序
Elasticsearch 默认采⽤用相关性算分对结果进行降序排序
可以通过设定 sort 参数,⾃行设定排序
如果不指定_score,算分为 Null
排序过程
排序是针对字段原始内容进行的,倒排索引无法发挥作用
需要用到正排索引,通过文档 id 和字段快速得到字段原始内容
Es 中有两种实现方式
Fielddata
Doc Values(列式存储,对 Text 类型无效)
Doc Values vs Fielddata
关闭 Doc Values
默认启用,可以通过 mapping 设置关闭
可以增加写入数据的速度 / 减少磁盘空间
如果重新打开,需要重建索引
什么时候需要关闭
明确该数据不需要做排序和聚合分析
分页
默认情况下,查询按照相关度算分排序,返回前 10 条记录
from:开始位置,size:期望获取文档总数
深度分页问题
当一个查询:From = 990,size = 10
会在每个分片上都获取 from + size 个文档,这个例子也就是 1000。然后通过 Coordinating Node 聚合所有结果。最后通过排序取前 1000 个文档
页数越深,占用内存越多。为了避免深度分页带来的内存开销,Es 默认限定到 10000 个文档
Search After 避免深度分页
避免深度分页的性能问题,可以实时获取下一页文档信息
限制
不支持指定页数(from)
只能往下翻
第一步搜索必须指定 sort,并且保证值是唯一的(可以通过加入_id 保证唯一性)
然后使用上一次最后一个文档的 sort 值进行查询
解决深度分页原理
假定 size 是 10,查询 990 - 1000
通过唯一排序值定位,将每个分片要处理的文档数都控制在 10
Scroll API
调用第一次,指定 scroll 存活时间,基于该请求创建一个快照,但是如果有新的数据写入,无法被查到
每次查询后,输入上一次的 scroll_id
scorll 查询适合作用于归档数据,例如导出全部数据
Es 并发问题
Es 采用的是乐观锁并发控制
Es 中文档是不可变更的。如果更新一个文档,会将旧文档标记为删除,同时增加一个全新的文档,并将文档的 version 字段 + 1
内部版本控制
If_seq_no + If_primary_term
外部版本控制
version + version type = external
Bucket & Metric
Metric Aggregation
Aggregation 属于 search 的一部分
单值分析:只输出一个分析结果
min,max,avg,sum
Cardinality(类似 distinct,count)
多值分析:输出多个分析结果
stats,extended stats
percentile,percentile rank
top hits
Bucket Aggregation
按照一定的的规则,将文档分配到不同桶中,从而达到分类目的
常见的 Bucket Aggregation
Terms
数字类型
Range / Data Range
Histogram / Data Histogram
支持嵌套,也就是桶中依然可以分桶
Text 字段默认不支持 Terms Aggregation 分析,需要打开 fielddata
keyword 字段默认支持 fielddata
优化 Term 聚合性能
可以在 keyword 字段上,将该字段打开,每当有新的数据写入,他的 term 就会被加载到 cache 中,此时做 Term Aggregation 性能就会提升
适合场景:频繁做聚合查询并且索引在不断写入
Range & Histogram 聚合直方图分桶
按照数字范围进行分桶
在 Range Aggregation 中可以自定义 key
Histogram Aggregation
Bucket 聚合分析允许通过子聚合进一步分析,子聚合分析可以是 Bucket 或者 Metric
聚合的作用范围和排序
ES 聚合分析的默认作⽤用范围是 query 的查询结果集
同时 ES 还⽀支持以下⽅方式改变聚合的作⽤用范围
Filter 可以在具体的 Aggregation 中做限定
Post_Filter 对指定字段分桶,并显示该字段指定值的信息
Global 指定 global 的聚合统计,会忽略掉 query 中 range 的限定
排序
指定 order,按照 count 和 key 进行排序
按照聚合结果进行排序
默认情况,按照 count 降序排序
指定 size,就能返回相应的桶
聚合精准度的问题
Es 对海量数据的分析,会损失精准度,满足实时性的需求
近似统计法
如果数据量很大,并且精准度要求很高,应该使用 Hadoop
如果精度要求很高,实施性要求也很高,数据量不是很大,Es 可以满足需求
当数据量越来越大,Es 将数据分散到不同分片,此时做统计 Es 会采用近似计算,牺牲精准度换取实时性
Min 的聚合过程
假设数据有三个分片
Coordinating Node 会在三个节点上拿到各自的最小值
Coordinating Node 在三个最小值中找到最小值,返回
Term 的聚合结果可能存在偏差
在 Terms Aggregation 的返回中有两个特殊的数值
doc_count_error_upper_bound : 被遗漏的 term 分桶,包含的⽂档,有可能的最⼤值
sum_other_doc_count: 除了了返回结果 bucket 的 terms 以外,其他 terms 的文档总数(总数-返回的总数)
举例:数据是两个分片,需要在两个分片上找到各自 bucket 中 count 最多的 top3
第一个分片 count 最多的 top3 是 ABC bucket,舍弃掉了 D bucket
第二个分片 count 最多的 top3 是 ABD bucket,舍弃掉 C bucket
两个分片结果汇总,count 为 A(12)、B(6)、C(4)、D(3),
所以最终结果会舍弃掉 D bucket,返回的 top3 是 ABC bucket,但其实 D bucket 的 count 数是 6,大于 C bucket 的 4,结果存在误差
如何解决 Term 结果存在的偏差
Terms 聚合分析不准的原因:数据分散在多个分⽚上, Coordinating Node 无法获取数据全貌
解决⽅方案 1:当数据量量不大时,设置 PrimaryShard 为 1;实现准确性
解决方案 2:在分布式数据上,设置 shard_size 参数,提高精确度
原理:每次从 Shard 上额外多获取数据(bucket)提升准确率,也就是之前每个分片只取 3 个,如果每个分片都拿 4 个,结果自然就会准确
打开 show_term_doc_count_error,帮助我们了解聚合分析的结果是否精确
shard_size 设定
调整 shard size ⼤小,降低 doc_count_error_upper_bound 来提升准确度
增加整体计算量,提高了准确度,但会降低响应时间
Shard Size 默认⼤小设定:shard size = size * 1.5 +10
Es 对象及 Nested
Elasticsearch 并不擅长处理关联关系。我们一般采用以下四种方法处理关联
对象类型
嵌套对象(Nested Object)
⽗子关联关系(Parent / Child )
应⽤端关联
对象查询带来的问题
插入一部电影的数据,其中包含两个演员名字
查询的演员名称是没有的,但是返回了搜索结果
为什么会搜索到不需要的结果
存储时,Es 对于内部对象的边界并没有考虑在内,JSON 格式被处理成扁平式键值对的结构
可以⽤ Nested Data Type 解决这个问题
Nested 数据类型:允许对象数组中的对象被独⽴索引
使⽤用 nested 和 properties 关键字,将所有 actors 索引到多个分隔的文档
在内部, Nested ⽂档会被保存在两个 Lucene 文档中,在查询时做 Join 处理
Nested Aggregation,需要添加 nested 关键字,如果没有添加普通 aggs 是不工作的
Update By Query & Reindex API
一般在以下⼏种情况时,我们需要重建索引
索引的 Mappings 发⽣变更:字段类型更改,分词器及字典更新
索引的 Settings 发生变更:索引的主分⽚数发生改变
集群内,集群间需要做数据迁移
Update By Query:在现有索引上重建
为索引增加子字段
增加子字段后,对于新写入的数据可以查询到,但是对于原有数据,通过子字段无法查询到
此时需要执行 update_by_query,之后即可返回数据
Reindex:在其他索引上重建索引
Reindex API ⽀持把文档从⼀个索引拷贝到另外⼀个索引
使⽤ Reindex API 的一些场景
修改索引的主分片数
改变字段的 Mapping 中的字段类型
集群内数据迁移 / 跨集群的数据迁移
注意事项
reindex 的索引 source 字段必须是 enable 的
reindex 只会在目标索引中创建不存在的文档,如果文档已经存在,会导致版本冲突
可以指定 op_type 字段为 create,表示只有没有的数据才会被创建
如果需要跨集群 reindex,需要修改 elasticsearch.yml 指定白名单,并且重启
数据建模
如何对字段进行建模
字段类型
是否需要搜索及分词
是否需要聚合及排序
是否需要额外存储
字段类型
Text
用于全文本字段,文本会被 Analyzer 分词
默认不支持聚合分析和排序。需要设置 fielddata 为 true
Keyword
用于 id,枚举,及不需要分词的文本。如:电话号码,手机号码,邮政编码,性别等
适用于 Filter(精确匹配),Sort 和 Aggragations
设置多字段类型
Es 默认会为文本类型字段设置为 text,并且设置一个 keyword 子字段
在处理人类语言时,通过增加各种分词起,提高搜索结构
数值类型
尽量选择贴近的类型。例如,可以用 byte 就不要用 long
枚举类型:设置为 keyword。即便是数字类型也应该设置为 keyword,获取更好的性能
其他:日期 / 布尔 / 地理信息
检索
如果是不需要搜索的字段,可以将该字段的 index 设置为 false
如果不需要排序或聚合分析,即使是 keyword 字段,也应该将 Doc_values / fielddata 设置为 false
更新频繁,聚合查询频繁的 keyword 字段,应该将 eager_global_ordinals 设置为 true,这个设置可以更好利用缓存特性
一个文档中最好避免大量字段
字段数过多不易维护
Mapping 信息保存在 cluster state 中,数据量过大,对集群性能会有影响(Cluster state 信息需要和所有节点同步)
删除或修改数据需要 reindex
默认最大字段是 1000,可以设置 index.mapping.total_fields.limt 限定最⼤大字段数
什么情况会导致文档中有成百上千字段?
Dynamic(生产环境中尽量不要打开 Dynamic)
true - 未知字段会被自动加入
false - 新字段不会被索引,但会保存在 _source 字段中
strict - 新增字段不会被索引,文档写入失败,可以控制到字段级别
尽量避免空值 - 在该字段增加 null_value 的设置
Ingest Node
Elasticsearch 5.0 后,引入的一种新的节点类型。默认配置下,每个节点都是 Ingest Node
具有预处理理数据的能力,可拦截 Index 或 Bulk API 的请求
对数据进⾏转换,并重新返回给 Index 或 Bulk API
⽆无需 Logstash,就可以进行数据的预处理,例如
为某个字段设置默认值;重命名某个字段的字段名;对字段值进行 Split 操作
支持设置 Painless 脚本,对数据进行更加复杂的加⼯
集群部署
一个节点在默认情况会下同时扮演:master eligible,data node 和 ingest node
生产环境中建议设置单一角色的节点
master 节点:负责集群状态(cluster state)管理
通常可以使用低配置的机器
一般生产环境配置 3 台,一个集群只有一个活跃的主节点
负责分片管理,索引创建,集群管理等操作
data 节点:负责数据存储和处理客户端请求
应该是用高配置服务器,SSD 提升磁盘吞吐量
ingest 节点:负责数据处理
使用高 CPU 配置,低配置磁盘即可
coordinating 节点:负责 load balance,降低 master、data 节点负担
数据节点相对有比较大的内存占用
coordinating 节点有时会开销很高的查询,导致 OOM
如果和 master 混合部署,这些都有可能影响 master 节点,导致集群的不稳定
当磁盘容量无法满足需求时,读写压力大时,可以增加数据节点。
如果有大量复杂或聚合查询,增加 coordinating node 可以提升查询性能,coordinating 节点之前最好还有一层 LB 来负载均衡,应用只需要和 LB 交互即可完成读写
有些集群需要单独设置 Ingest Node,通过 Pipline 的方式对数据写入做预先处理,可以为其增加写 LB
Hot & Warm Architecture
Hot 节点
对于比较新的,热门的数据放在 Hot 节点上
用于数据写入
Index 操作对 CPU 和 IO 都有比较高的要求,需要使用高配置服务器,通常使用 SSD
Warm 节点
旧的,不常使用的数据放在 Warm 节点上
用于保存只读索引,通常使用大容量磁盘
如何配置
使用 Shard Filtering
标记节点(Tagging)
需要通过 “node.attr” 来标记一个节点
查看节点信息
配置索引到 Hot 节点
创建索引时,将其创建在 Hot 节点上
配置索引到 Warm 节点
Index.routing.allocation 是一个索引级的 dynamic setting,可以通过 API 在后期进行设定
当数据很少使用时,将其移动到 Warm 节点
Rack Awareness
Es 节点可能分配在多个机架
当一个机架断电,可能会丢失几个节点
如果一个索引相同的主分片和副本分片,同时在这个机架上,就有可能导致数据丢失
通过 Rack Awareness 的机制,就可以尽可能避免将同一个索引的主副分片同时分配在一个机架上
分片设计和管理
单个分片
7.0 开始,新创建一个索引时,默认只有一个主分片
单个分片,查询算分,聚合不准的问题都得以解决
单个索引,单个分片,集群无法实现水平扩展
即使增加新的节点,也无法实现水平扩展
两个分片
随着数据越来越大,集群增加一个节点后,Es 会自动进行分片移动,也叫 Shard Rebalancing
如何设计分片数
当分片数 > 节点数
一旦集群中有新的数据节点加入,分片就可以自动分配
分片在重新分配时,系统不会有 downtime
多分片的好处:一个索引如果分布在不同节点,多个节点可以并行执行
查询可以并行执行
写入可以分散到多个机器
分片过多的副作用
Shard 是 Es 实现集群水平扩展的最小单位
过多设置分片数会带来一些潜在问题
每个分片是一个 Lucene 索引,会使用机器资源。过多分片会导致机器额外性能开销
每次搜索请求,需要从每个分片上获取数据
分片的 Meta 信息由 Master 节点维护。过多会增加管理负担。经验值,分片总数控制在 10w 以内
如何确定分片总数
存储角度
日志类应用,单个分片不要超过 50G
搜索类应用,单个分片不要超过 20G
为什么要控制分片大小
提高 Update 性能
Merge 时,减少所需资源
丢失节点后,具备更快的恢复速度 / 便于分片在集群内 Rebalancing
如何确定副本分片数
副本是主分片的拷贝
提高系统可用性,相应查询请求,防止数据丢失
需要占用和主分片一样的资源
对性能的影响
副本会降低数据的写入速度:因为需要拷贝,有几分副本就会有几倍的 CPU 资源消耗在索引上
会减缓主分片的查询压力,但是会消耗同样的内存资源
如果机器充足,提高副本数可以提高整体查询的 QPS
容量规划
一个集群需要多少节点,一个索引设置几个分片
规划上需要保持一定余量,当出现负载波动节点丢失,还能正常运行
硬件配置
数据节点尽量使用 SSD
搜索等性能要求高的场景,建议 SSD,按照 1:10 的比例配置内存和硬盘
日志类和查询并发低的场景,可以考虑使用机械硬盘存储,按照 1:50 配置内存和硬盘
单节点数据建议控制在 2TB 以内,最大不超过 5TB
JVM 配置机器内存的一半,JVM 最大内存配置不建议超过 32G
拆分索引
读多场景
如果业务上有大量查询是基于一个字段进行 filter,该字段又是一个数量不变的枚举值,例如用户所在地区
如果在单个索引有大量数据,可以考虑拆分成多个索引
查询性能提高
如果要对多个索引查询,也可以在查询中指定多个索引得以实现
写多场景(日志归档)
用户更多会查询近期数据,对旧数据查询相对较少
对数据写入性能要求较高
可以创建基于时间序列的索引
在索引名字中增加时间信息
按照每天 / 每周 / 每月进行划分
基于 Index Alias
好处
随着时间推移,便于对索引做老化处理
可以利用 Hot & Warm Architecture
版权声明: 本文为 InfoQ 作者【石刻掌纹】的原创文章。
原文链接:【http://xie.infoq.cn/article/bc5dac31d171e9aad8358b67c】。文章转载请联系作者。
评论