1. 前言
索引是具有相同结构的文档的集合,每个索引都拥有一个唯一的索引名称,它是ES里面非常重要的概念。一个ES集群中可以有多个索引,不同的索引代表不同的业务类型数据。
什么时候需要创建新的索引呢?一般来说有两类场景:
- 按业务分类:比如存储新闻数据的索引news、存储日志的索引logs等。
- 按日期范围分类:当数据量特别大时,可以根据时间范围创建索引,例如面对海量的线上日志,可以根据月份来创建日志索引:logs-202401、logs-202402等等。
索引的命名需要遵循以下约束:
- 只能使用小写字母,不能使用大写字母
- 不能包括 \ / * ? " < > | ` # : 及空格等特殊符号
- 不能以 - _ + 作为开始字符
- 不能命名为"."或者"..."
- 不能超过255个字节
- 不建议使用中文命名
2. 索引定义
在索引文档前,首先需要定义索引。包括:索引名称、索引设置、索引映射、别名、分析器等。
如下示例,我们创建了一个名称为"student"的索引,它拥有1个分片数和1个副本数;在索引映射里我们定义了三个字段分别是:学号 student_id、姓名 name、性别 gender,字段类型都是keyword,用于精准匹配;同时该索引还有一个别名:student_alias。
json
PUT student
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"student_id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"gender": {
"type": "keyword"
}
}
},
"aliases": {
"student_alias": {}
}
}
2.1 索引设置
索引设置可以分为两大类:静态设置和动态设置。
静态设置:只允许在创建索引时或者针对已关闭的索引进行设置,例如:
- index.number_of_shards:索引拥有的主分片数,默认值为1,上限是1024,创建后不能修改
- index.codec:索引压缩算法,默认使用LZ4算法,可以指定
best_compression
以获得更高的压缩比
动态设置:可以借助更新设置API的方式进行动态更新,更新后立即生效,例如:
- index.number_of_replicas:主分片拥有的副本分片数
- index.refresh_interval:refresh操作的频率,决定了文档写入后多久可以被搜索到,默认1s
- index.max_result_window:搜索结果的窗口大小,默认值10000,调大该值可能会影响ES性能
2.2 索引映射
索引映射可以理解为数据库表结构Schema,通过index.mappings
属性设置。索引映射的内容包含:字段名称、字段类型、分析器的定义(针对text类型)、fielddata、doc_values等设置。
如下示例,定义"news"索引用来存放新闻数据,标题title数据类型是text,使用「ik_max_word」分词器、内容content数据类型也是text,使用「ik_smart」分词器。
json
PUT news
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"content": {
"type": "text",
"analyzer": "ik_smart"
}
}
}
}
2.3 索引别名
索引创建后,索引名称就不支持修改了,但是可以通过设置别名来满足不同的业务,ES的大部分API都可以通过索引别名来调用。
比如,面对线上海量的日志数据,一般会根据日志的时间写入到不同的索引,比如按年、按月、甚至按天来创建索引,但是对外提供的检索服务一般会按照相对时间来查询日志,比如查询近一个月的日志,此时程序要查询的索引名称会随着时间不断变化,我们就可以创建一个索引别名"logs_last_month"来指向最近一个月的所有索引。
如下示例,索引别名"logs_last_month"从"logs_202402" 指向了 "logs_202403"。
json
POST _aliases
{
"actions": [
{
"remove": {
"index": "logs_202402",
"alias": "logs_last_month"
}
},
{
"add": {
"index": "logs_202403",
"alias": "logs_last_month"
}
}
]
}
2.4 索引模板
索引模板是一种告诉Elasticsearch在创建索引时如何配置索引的方法。
试想这样一个场景,面对线上海量的日志,需要按照月份来创建多个日志索引,如何保证日志索引之间的配置是一样的呢?即拥有相同的settings、mappings等配置。
使用相同的配置来创建索引固然是一种方案,但是太繁琐了,索引模板应运而生。
Elasticsearch7.8版本之后开始支持两种索引模板类型:普通索引模板和组件索引模板。
2.4.1 普通索引模板
普通索引模板就是预先创建好的一个完整的索引模板,ES在创建索引时,如果发现可以匹配到索引模板,就会采用模板设置。
如下命令会创建一个普通索引模板,它会匹配"logs-*"的索引名称,索引映射字段和数据类型都已指定。
json
PUT _index_template/logs_template
{
"index_patterns": [
"logs-*"
],
"template": {
"mappings": {
"properties": {
"trace_id": {
"type": "keyword"
},
"content": {
"type": "text"
},
"time": {
"type": "date"
}
}
}
}
}
现在我们创建一个"logs-2024"索引,会发现它成功应用到了上述模板的配置。
json
PUT logs-2024
GET logs-2024
{
"logs-2024": {
"aliases": {},
"mappings": {
"properties": {
"content": {
"type": "text"
},
"time": {
"type": "date"
},
"trace_id": {
"type": "keyword"
}
}
}
......
}
}
2.4.2 组件索引模板
组件模板的核心在于将原有普通模板定义的mappings、settings等配置以组件的方式分隔,以便最小化更新模板。
如下示例,定义settings组件:主分片数和副本分片数均为1,refresh时间间隔是5s,采用best_compression压缩方式
json
PUT _component_template/mylogs-settings
{
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"refresh_interval": "5s",
"codec": "best_compression"
}
}
}
如下示例,定义mappings组件,包含三个字段
json
PUT _component_template/mylogs-mappings
{
"template": {
"mappings": {
"properties": {
"trace_id": {
"type": "keyword"
},
"content": {
"type": "text"
},
"time": {
"type": "date"
}
}
}
}
}
最后就是基于组件模板来定义索引模板,如下示例,效果和直接定义普通索引模板是一样的
json
PUT _index_template/logs-template-for-components
{
"index_patterns": [
"logs-*"
],
"priority": 10,
"composed_of": [
"mylogs-settings",
"mylogs-mappings"
]
}
2.4.3 动态模板
动态模板允许我们更好地控制ES如何在默认的动态字段映射规则之外映射数据。通过将index.mappings.dynamic
设置为true或runtime,可以启用动态映射,当我们索引一个原先不存在的字段时,ES会动态的新增映射字段。但是有些时候,ES动态映射规则不是很理想,例如ES会把整型默认映射为long类型,占用8个字节,如果我们不需要这么大的范围就很浪费空间,就可以通过动态模板来调整ES的映射规则。
ES动态模板支持的匹配条件:
- match_mapping_type | unmatch_mapping_type:匹配或没匹配到指定的数据类型
- match | unmatch:模式匹配或没匹配到指定的字段名称
- path_match | path_unmatch:匹配或没匹配到字段的路径
如下示例,创建了一个动态模板,第一条规则是把string类型且以"ip"结尾的字段映射为ip类型;第二条规则是把string类型且以"_date"结尾的字段映射为date类型。
json
PUT _index_template/my-dynamic-template
{
"index_patterns": [
"index-dynamic-*"
],
"template": {
"mappings": {
"dynamic_templates": [
{
"string_as_ip": {
"match_mapping_type": "string",
"match": "*ip",
"mapping": {
"type": "ip"
}
}
},
{
"string_as_date": {
"match_mapping_type": "string",
"match": "*_date",
"mapping": {
"type": "date"
}
}
}
]
}
}
}
我们创建一个索引,让它能应用到动态模板。接着往里面索引一个文档,查看索引映射,发现字段数据类型符合预期。
json
PUT index-dynamic-01
POST index-dynamic-01/_doc
{
"node-ip":"127.0.0.1",
"create_date":"2024-01-01T00:00:00"
}
GET index-dynamic-01
{
"index-dynamic-01": {
"mappings": {
"properties": {
"create_date": {
"type": "date"
},
"node-ip": {
"type": "ip"
}
}
}
}
}
但是,如果索引一个非法的文档,例如字符串不是日期格式的,ES就会报错:
json
POST index-dynamic-01/_doc
{
"xx_date":"haha"
}
{
"error": {
"root_cause": [
{
"type": "document_parsing_exception",
"reason": "[2:13] failed to parse field [xx_date] of type [date] in document with id '1-trmI4BODFb3LbQXXSd'. Preview of field's value: 'haha'"
}
],
"type": "document_parsing_exception",
"reason": "[2:13] failed to parse field [xx_date] of type [date] in document with id '1-trmI4BODFb3LbQXXSd'. Preview of field's value: 'haha'",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "failed to parse date field [haha] with format [strict_date_optional_time||epoch_millis]",
"caused_by": {
"type": "date_time_parse_exception",
"reason": "Failed to parse with all enclosed parsers"
}
}
},
"status": 400
}