《Elastic Stack 实战手册》——三、产品能力——3.5 进阶篇——3.5.5.Shard allocation (3) https://developer.aliyun.com/article/1228683
索引维度分片分配
如同设计系统需要先从架构角度考虑系统模块设计,再细化到每一个应用设计应用自身的功能模块,索引分片的调整也需要先从集群角度,设定大的分配策略导向,如节点分布 (shard
awareness)、平衡 (shard rebalance) 等,再细化到每一个索引考虑单索引分片如何调整以达到最佳的表现。
再以上一节中的商品、订单检索系统为例,因为这分属两个子系统,符合我们预期的理解是即使商品检索系统负载很高,也不应该影响订单系统的检索耗时。
隔离不同索引
对不同业务所属索引进行物理隔离,实现 A 索引在执行大负载操作时,不会对 B 产生影响,前提是使用节点属性,将集群按照需要分割为多个群组。
比如集群包含以下设置的节点:
通过 index.routing.allocation.* 配置可以启用索引的分片控制,具体的:
l index.routing.allocation.include.{attribute}
l 将索引分片分配到包含任一指定属性的节点上。{attribute} 可以指定为节点内置属性,如 _ip、_host 等,也可以指定为自定义属性,如 zone 等,也可以混用。
l index.routing.allocation.require.{attribute}
l 将索引分片分配到指定节点上,节点必须包含指定的全部属性。
l index.routing.allocation.exclude.{attribute}
l 不要将索引分配到包含任一指定属性的节点上。
如果设置索引的分片控制参数为:如果参数设置为:如果参数设置为:则索引分片不会被分配到 Node C。
排查分片分配
当我们在创建索引后,发现索引分片不能被正常分配时,可以通过 explain 接口来查看原因,如下:
curl -XGET '{host:port}/_cluster/allocation/explain'
在 response 中可以看到具体分片未被正常分配的原因,如:
{ "index": "test_v1", "current_state": "unassigned", "unassigned_info": { "reason": "INDEX_CREATED", "last_allocation_status": "no" }, "can_allocate": "no", "allocate_explanation": "cannot allocate because allocation is not permitted to any of the nodes", "node_allocation_decisions": [ { "node_decision": "no", "deciders": [ { "decider": "filter", "decision": "NO", "explanation": "node does not match index setting [index.routing.allocation.require] filters [node:\"xxx\",_ip:\"1.1.1.1\"]" } ] } ] }
表示因为没有节点能够同时满足 node.attr.node: xxx 且 IP 地址为 1.1.1.1 的节点 (response 内容适当删减了非必要信息)。
平衡索引分片在各节点的分配
在索引按业务分组隔离之后,以商品检索为例,后续又追加了商品评价索引,用于存放商品的评价记录,由于用量较低,我们希望将分组内机器资源,主要用来承载商品检索服务。
不考虑分片数据倾斜的问题,即每个分片的负载一致,我们可以将索引的分片数 (主分片和副本分片的总和) ,设置为与节点个数一致,并通过设置索引分片,在各节点的分配个数来强迫索引在各节点间均衡分配。
要控制索引在单个节点的数量,可以通过 index.routing.allocation.total_shards_per_node 参数设置。
比如现有 6 个节点,我们可以将索引的分片数 index.number_of_shards 设置为 3,副本数 index.number_of_replicas 设置为 2,同时将索引在每个节点上的最大分片数 index.routing.allocation.total_shards_per_node 设置为 1,即可以保证索引在每个节点分配一个分片,又能充分利用每个节点的算力。
冷热隔离/归档
回到日志数据的问题,为了便于回溯问题,我们期望将数据的存档时间,从一周延长为半年,但是不要求全部的数据,都有同样的可用性和响应时间,
l 一周内需要有高性能的读写能力。
l 一月内数据仅需保证秒级的查询 RT 即可。
l 一年内的数据不需要实时保证可读,只要能保证数据在必要情况下可恢复使用即可。
对于冷热时效明显的数据场景 (比如日志类) ,热数据 (如当日数据) 的读写频率都要明显高于冷数据 (如一周前数据),这个时候从成本的角度出发,我们期望将冷数据存放于容量较大、IO 及 CPU 性能较弱的存储类机器,将热数据存放于 IO、CPU 性能较好的计算类机器。
我们可以将节点按照存储优化型、计算优化型、通用型等定向优化的类型分组,使用 node.roles 将节点分组,可使用的角色包括:
l data_content 存储用户定义的业务数据,需要保证高性能的读写。
l data_hot 存储新的时序数据,读写频繁,CPU、IO 敏感型数据。
l data_warm 读写频率降低,写很少,可容忍读 RT 变高。
l data_cold 保留只读数据,比如历史记录。
l data_frozen 用于保留数据快照,比如对冷数据将其副本作为 searchable snapshot 存放到 data_frozen 节点,并关闭原始索引的副本,在保证数据可用性的情况下进一步减少空间冗余,降低数据成本。
实际应用中,通常会搭配 ILM (index lifecycle management) 来使用这类 data tier 属性,如:
{ "phases" : { "hot" : { "actions" : { "rollover" : { "max_age" : "1d", "max_size" : "5gb" } } }, "warm" : { "min_age" : "7d", "actions" : { "forcemerge" : { "max_num_segments" : 1 } } }, "cold" : { "min_age" : "30d", "actions" : { "searchable_snapshot": { "snapshot_repository" : "snapshot_house" } } } } }
将一周内数据作为热点数据,存储于 data_hot 节点,其他一月内数据作为温数据,存储于
data_warm 节点,超过一个月的数据,启用 searchable_snapshot,集群内只保留主分片,副本备份到指定的远端路径下。
Elasticsearch 内部使用 index.routing.allocation.include._tier_preference 属性来实现这个操作,与 index.routing.allocation.include._tier 不同,_tier_preference 属性不是仅限定一种角色的节点,比如设置
{ "index.routing.allocation.include._tier_preference": "data_hot,data_warm,data_cold" }
表示将索引优先存储于 data_hot 节点,如果不存在 data_hot 次选 data_warm,最后使用 data_cold 兜底存储。在索引的生命周期中,新创建的索引 _tier_preference 先设置为 data_hot,在超过热点周期后,更新 _tier_preference 为 data_warm。data_hot 将其迁移到存在的 data_warm 节点。
通过这样的手段,我们可以根据索引数据的使用场景,合理的调配硬件资源,达到成本利用的最优化。
创作人简介:
毛夏君,关注研究中间件,比如 ES,Redis,RocketMQ 等技术领域。
博客:微信公众号——tiaotiaoba_abc