性能与容量之间的矛盾由来已久,计算机的多级存储体系就是其中一个经典的例子,同样的问题在Elasticsearch中也存在。为了保证Elasticsearch的读写性能,官方建议磁盘使用SSD固态硬盘。然而Elasticsearch要解决的是海量数据的存储和检索问题,海量的数据就意味需要大量的存储空间,如果都使用SSD固态硬盘成本将成为一个很大的问题,这也是制约许多企业和个人使用Elasticsearch的因素之一。为了解决这个问题,Elasticsearch冷热分离架构应运而生。
1. 实现原理
1.1 节点异构
传统的Elasticsearch集群中所有节点均采用相同的配置,然而Elasticsearch并没有对节点的规格一致性做要求,换而言之就是每个节点可以是任意规格,当然这样做会导致集群各节点性能不一致,影响集群稳定性。但是如果有规则的将集群的节点分成不同类型,部分是高性能的节点用于存储热点数据,部分是性能相对差些的大容量节点用于存储冷数据,却可以一方面保证热数据的性能,另一方面保证冷数据的存储,降低存储成本,这也是Elasticsearch冷热分离架构的基本思想,如下图为一个3热节点,2冷节点的冷热分离Elasticsearch集群:
其中热节点为16核64GB 1TB SSD盘,用于满足对热数据对读写性能的要求,冷节点为8C32GB 5TB HDD在保证一定读写性能的基础之上提供了成本较低的大存储HDD盘来满足冷节点对数据存储的需求。
1.2 数据分布
集群节点异构后接着要考虑的是数据分布问题,即用户如何对冷热数据进行标识,并将冷数据移动到冷节点,热数据移动到热节点。
节点指定冷热属性
仅仅将不同的节点设置为不同的规格还不够,为了能明确区分出哪些节点是热节点,哪些节点是冷节点,需要为对应节点打标签
Elasticsearch支持给节点打标签,具体方式是在elasticsearch.yml文件中增加
node.attr.{attribute}: {value}
配置。其中attribute为用户自定义的任意标签名,value为该节点对应的该标签的值,例如对于冷热分离,可以使用如下设置
node.attr.temperature: hot //热节点
node.attr.temperature: warm //冷节点
ps:中文通常叫冷热,英文叫hot/warm
索引指定冷热属性
节点有了冷热属性后,接下来就是指定数据的冷热属性,来设置和调整数据分布。冷热分离方案中数据冷热分布的基本单位是索引,即指定某个索引为热索引,另一个索引为冷索引。通过索引的分布来实现控制数据分布的目的。
Elasticsearch提供了index shard filtering功能(2.x开始),该功能在索引配置中提供了如下几个配置
index.routing.allocation.include.{attribute}
Assign the index to a node whose {attribute} has at least one of the comma-separated values.
index.routing.allocation.require.{attribute}
Assign the index to a node whose {attribute} has all of the comma-separated values.
index.routing.allocation.exclude.{attribute}
Assign the index to a node whose {attribute} has none of the comma-separated values.
用户可以在创建索引,或后续的任意时刻设置这些配置来控制索引在不同标签节点上的分配动作。
index.routing.allocation.include.{attribute}
表示索引可以分配在包含多个值中其中一个的节点上。
index.routing.allocation.require.{attribute}
表示索引要分配在包含索引指定值的节点上(通常一般设置一个值)。
index.routing.allocation.exclude.{attribute}
表示索引只能分配在不包含所有指定值的节点上。
数据分布控制
Elasticsearch的索引分片分配由ShardAllocator
决定,ShardAllocator
通过在索引分片创建或rebalance时对每个节点调用一系列AllocationDecider
来决定是否将节点分配到指定节点上,其中一个AllocationDecider是FilterAllocationDecider
,该decider用于应用集群,富贵论坛节点的一些基于attr的分配规则,涉及到节点级别配置的核心代码如下
private Decision shouldIndexFilter(IndexMetaData indexMd, RoutingNode node, RoutingAllocation allocation) {
if (indexMd.requireFilters() != null) {
if (indexMd.requireFilters().match(node.node()) == false) {
return allocation.decision(Decision.NO, NAME, "node does not match index setting [%s] filters [%s]",
IndexMetaData.INDEX_ROUTING_REQUIRE_GROUP_PREFIX, indexMd.requireFilters());
}
}
if (indexMd.includeFilters() != null) {
if (indexMd.includeFilters().match(node.node()) == false) {
return allocation.decision(Decision.NO, NAME, "node does not match index setting [%s] filters [%s]",
IndexMetaData.INDEX_ROUTING_INCLUDE_GROUP_PREFIX, indexMd.includeFilters());
}
}
if (indexMd.excludeFilters() != null) {
if (indexMd.excludeFilters().match(node.node())) {
return allocation.decision(Decision.NO, NAME, "node matches index setting [%s] filters [%s]",
IndexMetaData.INDEX_ROUTING_EXCLUDE_GROUP_SETTING.getKey(), indexMd.excludeFilters());
}
}
return null;
}
2 冷热集群搭建及使用实践
2.1 集群规格选型
根据业务数据量及读写性能要求选择合适的冷热节点规格
- 存储量计算:根据冷热数据各自数据量及要求保留时间,计算出冷热数据源数据量,然后使用如下公式计算出冷热节点各自的磁盘需求量实际空间 = 源数据 * (1 + 副本数量) * (1 + 数据膨胀) / (1 - 内部任务开销) / (1 - 操作系统预留) ≈ 源数据 * (1 + 副本数量) * 1.45 ES建议存储容量 = 源数据 * (1 + 副本数量) * 1.45 * (1 + 预留空间) ≈ 源数据 * (1 + 副本数量) * 2.2
- 副本数量:副本有利于增加数据的可靠性,但同时会增加存储成本。默认和建议的副本数量为1,对于部分可以承受异常情况导致数据丢失的场景,可考虑设置副本数量为0。
- 数据膨胀:除原始数据外,ES 需要存储索引、列存数据等,在应用编码压缩等技术后,一般膨胀10%。
- 内部任务开销:ES 占用约20%的磁盘空间,用于 segment 合并、ES Translog、日志等。
- 操作系统预留:Linux 操作系统默认为 root 用户预留5%的磁盘空间,用于关键流程处理、系统恢复、防止磁盘碎片化问题等。
- 预留空间:为保证集群的正常运行建议预留50%的存储空间
- 计算资源预估:
ES 的计算资源主要消耗在写入和查询过程,而不同业务场景在写入和查询方面的复杂度不同、比重不同,导致计算资源相比存储资源较难评估
- 日志场景:日志属于典型的写多读少类场景,计算资源主要消耗在写入过程中。我们在日志场景的经验是:2核8GB内存的资源最大可支持0.5w/s的写入能力,但注意不同业务场景可能有偏差。由于实例性能基本随计算资源总量呈线性扩容,您可以按实例资源总量估算写入能力。例如6核24GB内存的资源可支持1.5w/s的写入能力,40核160GB内存的资源可支持10w/s的写入能力。
- Metric 及 APM 等结构化数据场景:这也是写多读少类场景,但相比日志场景计算资源消耗较小,2核8GB内存的资源一般可支持1w/s的写入能力,您可参照日志场景线性扩展的方式,评估不同规格实例的实际写入能力。
- 站内搜索及应用搜索等搜索场景:此类为读多写少类场景,计算资源主要消耗在查询过程,由于查询复杂度在不同使用场景差别非常大,计算资源也最难评估,建议您结合存储资源初步选择计算资源,然后在测试过程中验证、调整。2.2 搭建集群自建
按照选定冷热节点规格部署服务器,搭建集群,热节点使用SSD盘,冷节点使用HDD盘,对热节点elasticsearcy.yml增加如下配置
node.attr.temperature: hot
对冷节点增加如下配置
node.attr.temperature: warm
启动集群,冷热分离的Elasticsearch集群即搭建完成
- 购买云ES服务
腾讯云预计于12月中旬上线冷热分离集群,用户只需要在创建页面上根据需要即可分钟级拉起一个冷热分离架构的ES集群,方便快速,扩展性好,运维成本低
- 验证
使用如下命令可以验证节点冷热属性
GET _cat/nodeattrs?v&h=node,attr,value&s=attr:desc
node attr value
node1 temperature hot
node2 temperature hot
node3 temperature warm
node4 temperature hot
node5 temperature warm
...
可以看到该集群为三热二冷的冷热分离集群(当然要注意如果其中有专用主节点或专用协调节点这类无法分配shard的节点,即使设置了冷热属性也不会有分片可以分配到其上)
3. 为索引设置冷热属性
业务方可以根据实际情况决定索引的冷热属性
- 对于热数据,索引设置如下
PUT hot_data_index/_settings
{
"index.routing.allocation.require.temperature": "hot"
}
- 对于冷数据,索引设置
PUT hot_data_index/_settings
{
"index.routing.allocation.require.temperature": "warm"
}
- 验证
创建索引
PUT hot_warm_test_index
{
"settings": {
"number_of_replicas": 1,
"number_of_shards": 3
}
}
查看分片分配,可以看到分片均匀分配在五个节点上
GET _cat/shards/hot_warm_test_index?v&h=index,shard,prirep,node&s=node
index shard prirep node
hot_data_index 1 p node1
hot_data_index 0 r node1
hot_data_index 2 r node2
hot_data_index 2 p node3
hot_data_index 1 r node4
hot_data_index 0 p node5
设置索引为热索引
PUT hot_warm_test_index/_settings
{
"index.routing.allocation.require.temperature": "hot"
}
查看分片分配,发现分片均分配在热节点上
GET _cat/shards/hot_warm_test_index?v&h=index,shard,prirep,node&s=node
index shard prirep node
hot_data_index 1 p node1
hot_data_index 0 r node1
hot_data_index 0 p node2
hot_data_index 2 r node2
hot_data_index 2 p node4
hot_data_index 1 r node4
设置索引为冷索引
PUT hot_warm_test_index/_settings
{
"index.routing.allocation.require.temperature": "warm"
}
查看分片分配,发现分片均分配到冷节点上
GET _cat/shards/hot_warm_test_index?v&h=index,shard,prirep,node&s=node
index shard prirep node
hot_data_index 1 p node3
hot_data_index 0 r node3
hot_data_index 2 r node3
hot_data_index 0 p node5
hot_data_index 2 p node5
hot_data_index 1 r node5
4. 索引生命周期管理
从ES6.6开始,Elasticsearch提供索引生命周期管理功能,索引生命周期管理可以通过API或者kibana界面配置,详情参考[[index-lifecycle-management], 本文仅通过kibana界面演示如何使用索引生命周期管理结合冷热分离架构实现索引数据的动态管理。
kibana中的索引生命周期管理位置如下图(版本6.8.2):
点击创建create policy,进入配置界面,可以看到索引的生命周期被分为:Hot phrase
,Warm phase
, Cold phase
,Delete phrase
四个阶段
- Hot phrase: 该阶段可以根据索引的文档数,大小,时长决定是否调用rollover API来滚动索引,详情可以参考[[indices-rollover-index],因与本文关系不大不再详细赘述。
- Warm phrase: 当一个索引在Hot phrase被roll over后便会进入Warm phrase,进入该阶段的索引会被设置为read-only, 用户可以为这个索引设置要使用的attribute, 如对于冷热分离策略,这里可以选择temperature: warm属性。另外还可以对索引进行forceMerge, shrink等操作,这两个操作具体可以参考官方文档。
- Cold phrase: 可以设置当索引rollover一段时间后进入cold阶段,这个阶段也可以设置一个属性。从冷热分离架构可以看出冷热属性是具备扩展性的,不仅可以指定hot, warm, 也可以扩展增加hot, warm, cold, freeze等多个冷热属性。如果想使用三层的冷热分离的话这里可以指定为temperature: cold, 此处还支持对索引的freeze操作,详情参考官方文档。
- Delete phrase: 可以设置索引rollover一段时间后进入delete阶段,进入该阶段的索引会自动被删除。
冷热分离架构是Elasticsearch的经典架构之一,使用该架构用户可以在保证热数据良好读写性能的同时,仍可以存储海量的数据,极大地丰富了ES的应用场景,解决了用户的成本问题。再结合ES在6.6推出的索引生命周期管理,使得ES集群在使用性和自动化方面表现出色,真正地解决了用户在性能,存储成本,自动化数据管理等方面的问题。