【FusionInsight Elasticsearch二次开发最佳实践】业务场景&数据规划
Elasticsearch是一个实时的分布式搜索和分析引擎,它可以帮助我们用很快的速度去处理大规模数据,可以用于全文检索、结构化检索、推荐、分析以及统计聚合等多种场景。Elasticsearch的数据存储在本地lucene文件,采用json的数据格式,同时提供了简单易用的restful接口。在实际应用场景下的实践和探索结果,从业务场景,数据规划,如何进行写入和查询调优,包括常见的问题出发。总结和梳理Elasticsearch的最佳实践。
1 业务场景
1.1 数据特点
数据体量大,如大量的日志型数据,轨迹型数据等。
1. 从大量的日志型数据中,根据关键字搜索出相关的信息。需要把所有搜索结果过滤出来,便于进行二次分析(需要返回大量的查询结果,一般万级别以上)
2. 从轨迹型数据中,根据关键信息搜索出相关的轨迹变化。需要对搜索过程中按照一定的字段进行聚合(需要返回的查询结果相对较小,对查询响应时间要求高)。
从数据存储周期来看:大部分数据保存周期为3-6个月,部分数据保存1年以上,甚至永久保存。
从业务场景来看:基本都属于写多查少的场景,或者写入和查询都多的场景。这就对ES集群的存储和查询能力有较高的要求。
1.2 ES在业务场景下的特点
一次写入多次查询:ES适合一次写入多次查询的场景,支持update更新操作,但是如果需要进行索引mapping的更改,需要reindex重建或者新建索引,更改成本较大,所以在大部分场景下,数据写入之后不再更新。
全文检索:ES的优势在于全文检索,模糊查询,适合需要从大量的数据中根据某些关键信息挖掘有价值的数据的场景。
2 数据规划
2.1 为什么数据规划很重要
任何系统都有一套更为适用的规则或者其系统规格,前期的详细设计能为我们后期维护优化节约大量的精力。在我们实际的经验中,发现大部分问题(分片严重超规格,单个分片超大,索引mapping设置不合理等问题)都是由于数据的前期规划不够,大大增加了后期整改和优化的难度和成本。
在进行数据规划之前,首先要对数据有充分的了解,包括不限于:
数据量有多大,包括总量,增量,以及未来的趋势?
数据的生命之旅是怎么样的,即从写入到删除的过程?
我们期望从数据里面挖掘出什么,即数据是用来做什么的?
2.2 合理的规格
常见的系统规格如下所示:
单Elasticsearch集群实例数 | 建议控制在约300个EsNode实例 |
单Elasticsearch集群支持的最大shard数 | 建议控制在5万以内 |
单EsNode实例,最大shard数 | 建议控制在500以内 |
单shard支持存储的数据量 | 建议单个shard大小20-30GB |
单EsNode实例,最大存储量 | 建议单个EsNode存储数据5TB以内 |
单index分片总数 | 建议控制在EsNode实例个数的2倍以内 |
单index字段个数 | 建议控制在1024个字段范围内 |
单批次查询返回的数据量 | 建议控制在10000以内 |
2.3 分片如何规划
每个分片都可以处理索引和查询请求,在设定分片数目时,可从以下几个方面考虑:
1. 建议单个分片保存的数据量在20GB左右,最大不超过30GB,过大的分片会降低查询以及索引恢复的性能。
2. 根据索引预计承载的最大数据容量和单个分片容量确定主分片个数。
3. 为了提升数据可靠性,合理设置副本分片个数,至少设置为1,如果集群的存储空间足够,推荐设置为2。
4. 每个node可以支撑的shards个数是有限的,node是物理资源分配的对象,随着shards中数据的增大,shards中的数据在查询时被不断加载到内存,达到一定量时,将会把HeapSize耗尽,导致频繁GC,系统将不能正常工作。推荐1GB内存管理15个shard,以一个Elasticsearch实例内存最大31G为例,单实例管理的shard数保持在500以内。
5. 配置total_shards_per_node参数,让分片更加均匀的分布在各个实例上。表示限制每个实例上分布该该索引的分片最大个数,如2,即表示每个实例上最多分布2个该索引的分片。
说明:total_shards_per_node参数值=索引总分片数/数据实例数(向上取整)。
2.4 数据结构如何规划
通常的使用场景中,一个集群内的业务数据一般分为几类,为了更加方便数据管理,通常会将相近的数据结构的数据归为一类。这时候建议使用索引模板规范索引的建立,不同类别的数据创建不同的索引模板。
Elasticsearch提供一种叫“索引模板”方式,帮助用户在新创建索引时采用提前设定好的参数,简化创建过程。索引模板可以设置包括order、template、settings和mappings等部分设置灵活。索引模板仅在新创建索引时起作用,修改索引模板不对会现有已经创建的索引产生影响。索引模板设定后,会根据模板设定规则,在满足设定条件的时候起作用,如果创建的索引中已经指定索引模板设置的参数则索引模板不会起作用。
索引模板设置内容如下:
order:模板优先级,最大值2147483647(JAVA Integer.MAX_VALUE - 1),Order值越大优先级越高,如果不设置则为0;
template:模板匹配的名称方式,比如:“te*”表示匹配名称以“te”开头的索引,如果为“*”表示匹配所有的索引;
settings:索引设置,一般定义的是索引的主分片、拷贝分片、刷新时间、自定义分析器等;
mappings:索引中各个字段的映射定义,可以设置动态映射和自定义字段映射;
aliases:设置索引别名。
注意:如果匹配到多个模板(和匹配规则无关,只要匹配到了就行,即匹配规则不影响模板的优先级),会将这些模板的设置进行合并,不同的优先级相同的设置属性优先使用优先级高的配置,如果相同的优先级相同的属性设置则优先使用最先创建的那个模板,其他的不相同的属性会合并到一起。
{ "order": 0, "template": "*", "settings": { "index": { "indexing.slowlog.source": "500", "indexing.slowlog.threshold.index.debug": "2s", "indexing.slowlog.threshold.index.info": "5s", "indexing.slowlog.threshold.index.warn": "10s", "number_of_replicas": "1", "search.slowlog.threshold.query.debug": "2s", "search.slowlog.threshold.query.info": "5s", "search.slowlog.threshold.query.warn": "10s", "store.type": "niofs", "translog.durability": "async", "translog.flush_threshold_size": "3gb" } } }
2.4.2 索引的创建
1. 为了减少索引数量并避免非常庞大的映射,请考虑将相同索引结构的数据存储在相同的索引中;
2. 避免将不相关的数据放在同一个索引中,以避免稀疏,将这些文件放在不同的索引中通常会更好;
3. 针对数据量比较大的索引,建议按周期进行分表,比如按天进行索引划分;
4. 建议提前创建好索引,不要自动创建索引,可以避免集群内出现一些杂乱的数据。可通过设置参数auto_create_index:true关闭自动创建索引。
2.4.3 索引mapping的设置
写入数据前进行合理的mapping设置是必须的,而且一旦索引已经创建好,已经存在的字段无法更新:
1. Elasticsearch可以对数据做动态mapping,但请不要这么做,尽量在创建index时便赋予index固定的mapping配置。设置动态mapping,当大量数据写入的同时伴随着新的字段的增加,会造成大量的put_mapping操作,从而造成EsMaster阻塞,影响整个ES集群的运行。不建议使用动态mapping,如果需要使用动态mapping,建议尽量使用较为精准的匹配规则,杜绝*通配符等暴力操作。
{ "mappings": { "mytype": { "dynamic": false } } }
2. 如果数据量巨大,可以分的字段个数太多,如超过1000个字段,最好给字段赋予不同的级别索引到不同的index中。例如,常用的查询字段可以写入到一个index中,字段长度较长且不常用的索引到另一个index中。
3. 合理的设计Mapping,根据实际的业务数据去设置优化Mapping,根据具体的字段和需求去选择对应的类型设置,可参考如下几点:
a. string类型默认分成:text和keyword两种类型。需要分词:text,否则keyword;
b. 枚举类型,基于性能keyword,即便是整形;
c. 数值类型,尽量选择贴近大小的类型;
d. 日期类型,如果需要基于时间轴做分析,必须date类型,如果仅需秒级返回,建议使用keyword;
e. 其他类型,布尔、日期、地理位置,使用对应的类型即可;
f. 如果某个字段不需要被检索,则不需要建立索引,可以减少很多的CPU计算操作,将“index”参数设置为“false”;
{ { "content": { "index": false } } }
g. 如果字段完全不需要检索,排序,聚合分析,将“enable”参数设置为“false”;
{ { "content": { "enabled": false } } }
4. “_all”字段,默认将写入的字段拼接成一个大的字符串,并对该字段进行分词,用于支持整个doc的全文检索,“_all”字段在查询时占用更多的CPU,同时占用更多的磁盘存储空间,默认为“false”,不建议开启该字段;
{ "_all": { "enable": false } }
5. norms字段,norm是索引评分因子,如果不用按评分对文档进行排序,设置为“false”,默认是“true”;
6. _source字段,默认是开启的,如果不需要update、reindex和高亮操作,可以禁止“_source”,节省更多的磁盘空间;
a. doc_value字段,默认是开启的,存储的是正排索引,如果不需要对字段进行排序和聚合,可以禁用该字段的doc_value,能节约大量的磁盘空间。
{ "mappings": { "my_type": { "properties": { "filed": { "doc_values": false, "type": "keyword" } } } } }
2.4.4 索引setting的设置
1. 修改索引刷新时间:默认为1s,即每1s都会将写入到内存里面的数据刷新到磁盘里面,可修改为60s或者120s,减轻磁盘的IO压力;
"refresh_interval" : "60s"
2. 修改Translog策略参数,默认translog的持久化策略默认为request,即每个请求都进行flush,影响ES写入速度。将持久化策略改成async,表示按设置的sync_interval周期性进行刷新,同时调大translog刷盘间隔时间,默认为5s,调大至120s或者180s。调大translog flush的大小为3G,即大小超过3GB进行刷新操作;
"translog. durability ":"async" "sync_interval": "180s" "translog.flush_threshold_size":"3gb"
3. 设置index.type.store为niofs模式,可以减轻集群的IO负载;
"index.type.store" : "niofs"
4. 针对于5机器节点以上,为了让各个实例上的分片均匀分布,添加如下参数,设置每个索引在单个实例上的分片个数,如下所示为每个索引在每个实例上的分片为2个。
"index.routing.allocation.total_shards_per_node":"2"
2.5 数据生命周期如何规划
Elasticsearch中的open状态的索引都会占用堆内存来存储倒排索引,过多的索引会导致集群整体内存使用率多大,甚至引起内存溢出。所以需要根据自身业务管理历史数据的生命周期,如近3个月的数据open用于快速查询;过去3-6月的数据索引close以释放内存,需要时再开启;超过6个月的可以删除索引。
可以使用索引模板的方式按照一定时间创建新的索引,例如按天创建索引,索引的命名可能是index-yyyy-mm-dd,每天生成不同的索引,清除历史数据时可直接关闭或删除。
2.5.1 滚动索引
当一个索引过大或者过于陈旧时,滚动索引可以将索引的别名滚动到一个新的索引上,这个新的索引结构与旧索引结构相同。
滚动索引API需要指定一个索引别名和条件。发送请求时这个索引别名需要指向一个可以写入的索引,否则请求将无效。
索引别名:
指定的索引别名有以下两种情况:
(1)如果别名指向一个索引,is_write_index未配置,此时旧索引别名将滚动到新索引上,同时旧索引别名将被删除;
(2)如果索引别名指向一个或者多个索引,同时有一个或者多个索引的is_write_index设置为true,此时,这些可写入的索引将滚动别名同时将is_write_index设置为false,将别名滚动到新的索引同时将新索引的is_write_index设置为true。
滚动条件:
滚动索引的API支持三个条件,如下所示。
参数名称 |
参数说明 |
max_age | 索引最大年龄,单位支持d(天)、h(时)、m(分钟)、s(秒) |
max_docs | 索引应该包含最大的文档数,不包含索引副本的文档数量 |
max_size | 索引主分片的最大估算大小,单位支持pb(PB)、tb(TB)、gb(GB)、mb(MB)、kb(KB) |
当发出滚动请求的时候指定索引满足设定条件中的任意一个或者多个索引将会被滚动,如果不满足则不会滚动,elasticsearch暂不提供监听功能,需要滚动时需要手动触发。
索引命名规则:
滚动索引的命名必须满足一定规则否则无法正常滚动索引或者需要手动指定新的索引名称。
1. 以-和数字结尾:如果现有索引名称以-和数字结尾例如log-1,新的索引名称将会根据现有数字大小加1,无论就得索引名称如何,新的索引的编号数字都会填充为6位,log-1索引滚动后新的索引名称为log-000002。
2. 使用日期计算:新索引的命名也支持用滚动日期来命名,要求索引名称同样以-加数字结尾,比如log-2019.08.28-1,如果当天滚动索引的话新的索引名称为log-2019.08.28-000002,如果一天后滚动新的索引名称为log-2019.08.29-000002。
也可以用日期计算进行创建索引并设定别名,如以下操作将会创建logs-2019.08.28-1索引,别名为logs_write:
# PUT /<logs-{now/d}-1> with URI encoding: PUT /%3Clogs-%7Bnow%2Fd%7D-1%3E { "aliases": { "logs_write": {} } }
3. 日期计算:
日期计算的格式为:
<static_name{date_math_expr{date_format|time_zone}}>
参数说明为:
参数名称 | 说明 |
static_name | 是名称的 static text( 静态文本)部分 |
date_math_expr | 是动态计算日期的动态 date math 表达式 |
date_format | 是计算日期应呈现的可选格式。默认是 YYYY.MM.dd |
time_zone | 是可选的时区。默认为 utc |
日期计算表达式只支持公历。
必须将 date math 索引名称表达式包含在尖括号中,并且所有的特殊字符都应进行 URI 编码。例如 :
GET /%3Clogstash - %7Bnow %2Fd %7D %3E / _search { "query": { "match": { "test": "data" } } }
用于日期计算的特殊字符必须按照如下 URI 编码 :
< | %3C |
> | %3E |
/ | %2F |
{ | %7B |
} | %7D |
| | %7C |
+ | %2B |
: | %3A |
, | %2C |
以下示例显示了不同形式索引表达式和它们解析的最终索引名称,给定的当前时间是 2024 年 3 月 22 日 utc。
表达 | 结果 |
<logstash-{now/d}> | logstash-2024.03.22 |
<logstash-{now/M}> | logstash-2024.03.01 |
<logstash-{now/M{YYYY.MM}}> | logstash-2024.03 |
<logstash-{now/M-1M{YYYY.MM}}> | logstash-2024.02 |
<logstash-{now/d{YYYY.MM.dd|+12:00}}> | logstash-2024.03.23 |
如果索引中要使用{},需要使用反斜杠“\”进行转义处理,例如:<elastic\\{ON\\}-{now/M}>的结果为elastic{ON}-2024.03.01。
4. 自定义的索引名称:如果索引名称不满足以-加数字结尾,比如log,此时如果不指定新的索引名称请求会报错,自定义新的索引名称请求如下:
POST /test/_rollover/log_new { "conditions": { "max_age": "1m", "max_docs": "1000", "max_size": "5gb" } }
如果滚动成功的话会生成log_new索引。
设置新的索引:
索引滚动时新的索引会根据匹配到的索引模板自动设置,也支持自定义的settings、mappings和aliases设定。请求中的设定值将会覆盖索引模版中相同的设定,比如可以进行以下设定:
POST /test/_rollover { "conditions": { "max_age": "1m", "max_docs":"1000", "max_size":"5gb" }, "settings" :{ "index.number_of_shards":2 } }
新的索引的index.number_of_shards将会被设置为2。
Dry运行:
滚动索引支持dry_run模式,可以在不执行实际滚动的情况下检查请求条件:
POST /test/_rollover?dry_run { "conditions": { "max_age": "1m", "max_docs":"1000", "max_size":"5gb" }, "settings" :{ "index.number_of_shards":2 } }
- 点赞
- 收藏
- 关注作者
评论(0)