1. 定时任务
通过定时任务向Elastic Search中发送创建索引的请求以达成周期创建索引的目的。这里就不过多说明了,大家应该都会。
2. Elastic Search 索引模板
首先我们需要打开Elastic Search的自动创建索引功能,有点要求的可以顺便配置白名单:
# 设置为true可以自动根据新增的数据格式创建对应格式的索引,并向其中添加该数据
PUT _cluster/settings
{
"persistent": {
"action.auto_create_index": "true"
}
}
# 以下展示白名单配置方法
PUT _cluster/settings
{
"persistent": {
"action.auto_create_index": "old_six_*"
}
}
以上白名单配置了,如果索引名是old_six_
开头的,都可以自动进行创建。除此以外的所有新增请求,如果索引不存在则会报响应异常。
其次,我们需要向Elastic Search中埋入索引模板:
PUT _template/old_six_template_name
{
"index_patterns": ["old_six_*"],
"mappings": {
"properties": {
"id": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"data": {
"type":"nested",
"properties":{
"timestamp": {
"type": "long"
},
},
"info": {
"type": "text"
}
}
}
}
}
}
其中old_six_template_name
为创建的模板名称,"index_patterns"
后面为匹配的索引名称,可以匹配多个(通过逗号隔开)。这里在data
中配置了一个嵌套类型,以供后续参考。
这个脚本配置完之后,如果有索引要创建,并且索引的名称符合这里的匹配条件,就会使用这个模板来创建索引结构。
**注意:这里的示例是没有配置优先级的,如果有索引名称匹配到了多个模板,那么可以在模板中配置字段order
优先级来选择使用哪个模板,这里需要注意模板优先级之间可能会发生模板合并!**这里就不说明了,如果有这样的现象可以百度查看寻找解决方法。上文的示例可以用,但不满足那么复杂的场景。
到这里就结束了所有的配置,我们只需要正常插入索引下的数据就可以自动创建索引并加入数据了。
例如:此时我插入数据如下:
POST old_six_2022/_doc/123456
{
"id": "123456",
"data": [
{
"timestamp": 165456456,
"info": "test"
}
]
}
就可以自动创建索引old_six_2022
并插入本条数据,一些使用RestHighLevelClient
的java同学以及其他客户端连接Elastic Search的同学照常插入数据即可。所当作以及存在了该索引,直接用就行。
回归主题,使用本种方式的同学,如果需要索引按周期自动创建,则只需要拼出索引的名字直接插入使用即可。也可以在模板执行别名哈,这里不过多赘述了。
3. 滚动索引管理索引生命周期
使用1和2方法呢对于一些数据量较大,需要定期删除的场景并不友好。比如我只存3个月的日志,那么上述实现方式呢仍然需要定时手动或者定时任务删除索引数据,比较麻烦。因此我们可以用这种方法来操作,从此只需要关注存储空间够不够就行了。
这一块比较复杂,实现难度比较大。这里给出两篇介绍的不错的博客以供参考:
- 对于生命周期概念和阶段定义阐释的比较清楚
- 对于实际操作步骤比较全面
首先我们先创建生命周期滚动策略:
PUT _ilm/policy/old_six_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "20GB",
"max_age": "30d"
},
"set_priority": {
"priority": 100
}
}
},
"delete": {
"min_age": "1065d",
"actions": {
"delete": {}
}
}
}
}
}
上述策略为:当符合索引大小达到20GB或创建后过了30天这两个条件的任意一个条件时,发生索引滚动创建新的索引,原索引在发生滚动后的1065天后删除(标记删除状态,在段合并后真正删除)。
我们可以通过GET _ilm/policy
来查看现存所有的策略。
其次,我们需要在索引模板中引用这个策略:
PUT _template/old_six_template_name
{
"index_patterns": ["old_six_index_*"],
"settings": {
"number_of_replicas": 1,
"number_of_shards": 1,
"index": {
"lifecycle": {
"name": "old_six_policy",
"rollover_alias":"old_six"
}
}
},
"mappings": {
"properties": {
"id": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"data": {
"type":"nested",
"properties":{
"timestamp": {
"type": "long"
},
"stub": {
"type": "text"
},
"info": {
"type": "text"
}
}
}
}
}
}
在模板中可以定义对应的索引别名,并设置为可写索引:
PUT %3Cold_six_index_%7Bnow%2FM%7ByyyyMM%7D%7D%3E
{
"aliases": {
"old_six": {
"is_write_index": true
}
}
}
这里创建的索引会在后面加上yyyy-MM格式的时间。这里指定为可写的原因是:
当我们使用滚动索引的时候,Elastic Search 会将查询整个索引分成多个小的分片,然后分批返回数据,这些数据被缓存到内存中,并用于下一次滚动请求。每批数据的排序和位置信息都会保存在一个叫做 Scroll ID 的内部状态中,这个状态也需要写入到磁盘里。
在滚动索引的过程中,如果 is_write_index
设置为 false,那么所有的新文档都会被写入到一个新的只读(read-only)分片里。这个分片是只读的,也就是说它不能持久化,因为它的主要目的是用来支持滚动请求。如果我们试图往一个只读分片里写入数据,就会导致失败。
因此,当你需要写入新文档到 Elastic Search 中的时候,必须将 is_write_index
设置为 true,这样新文档才能被写入到可持久化的写索引中。这个写索引可以是你原本一直在写入文档的那个索引,或者是一个新建的索引。总之,只要它是可写的就行了。
总之,设置 is_write_index 为 true 是因为它指定了滚动请求需要写入数据的位置,也就是新文档的写入位置。如果不将其设置为 true,就无法将新文档写入到可持久化的索引中。
至此即可完成目标操作。
甜点:Elastic Search实现结构Upsert操作
本例子中,使用Java语言和RestHighLevelClient
原生方法作为示范。原理都一样,语言和工具都只是载体。
还是以上文的结构例子来说,这里对嵌套结构进行说明吧,复杂一点的结构明白了的话,简单的结构也不再话下。
直接上代码:
public void upsert(String id, Map<String, Object> paramsMap) throws IOException {
// 指定索引名称和对应的id
UpdateRequest updateRequest = new UpdateRequest("old_six_2023",id);
// 如果为true,表示无论文档是否存在,脚本都必须运行
updateRequest.scriptedUpsert(false);
// 这里的结构就是你POST数据的结构转为Map,注意data的类型
updateRequest.upsert(paramsMap);
// 制定脚本,前面两个参数不用动,感兴趣的同学自行百度或者私信问我都可
// 第三个参数为执行的脚本,脚本为java,允许包含api子集操作
// 第四个参数是脚本运行时需要的数据结构,对应脚本中的params
Script script = new Script(ScriptType.INLINE, "painless",
"if(ctx._source.data == null){ctx._source.data = params.data}else{for(int i = 0;i<params.data.length;i++) {ctx._source.data.add(params.data[i]);}}",
paramsMap);
// 传入脚本
updateRequest.script(script);
// 最后执行即可
restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
}
需要注意,这里的脚本不能修改,每次修改都会重新编译。Elastic Search每次接收到新的脚本后,会对这个脚本重新编译并缓存,过程操作默认支持每分钟十五次。如果是相同脚本,则会使用缓存起来的脚本数据替换数值进行执行,效率会比DSL执行慢一丢丢。所以脚本最好定义为一个常量,不要用示例的这种写法以防万一。或者也可以通过以下方法在Elastic Search服务端定义好脚本,通过发送相关格式的请求使用脚本,也可以使用getScript
方法获取脚本,并使用它。
PUT /_scripts/script_name
{
"script": {
"lang": "painless",
"source": "if(ctx._source.data == null){ctx._source.data = params.data}else{for(int i = 0;i<params.data.length;i++) {ctx._source.data.add(params.data[i]);}}"
}
}
参考使用服务端缓存的脚本请求示例:
POST old_six_2023/_doc/123456/_update
{
"script": {
"id": "script_name",
"params": {
"id":"123456",
"data":
[{
"timestamp": 165456456,
"info": "test"
}]
}
}
}