Spring Boot 整合 Elasticsearch:按月分索引后怎么查?
摘要: 按月拆 Elasticsearch 索引,写入时拼接年月只是第一步。按 ID 更新、按条件删除、单月查询和跨月查询都需要独立的路由依据。MetaLite 让 ES DAO 实现
TableRouter,在每种操作生成请求前决定物理索引。本文结合操作日志示例拆解动态索引路由,并指出“按月写入、查询仍返回逻辑索引”只有配合别名或通配符策略才可能查到数据。
日志类数据很适合按月建立索引:
app-log-2026-07
app-log-2026-08
app-log-2026-09
这样可以控制单索引规模,也便于按月归档和删除。
但很多实现只完成了写入路由:
index = "app-log-" + yyyyMM;
随后才发现,更新和查询时未必还拿得到创建时间。
分索引的难点不是字符串拼接,而是每种操作都必须回答:物理索引依据从哪里来?
一、逻辑索引和物理索引要分开
MetaLite 的实体使用 @Table 声明逻辑名称:
@Table("app-log")
public class OperateLogEntity implements Entity {
private String id;
private LocalDateTime createTime;
}
逻辑索引表达业务集合,物理索引表达实际存储位置:
逻辑索引:app-log
物理索引:app-log-2026-08
如果不需要拆分,二者可以相同;需要动态拆分时,由 DAO 的路由方法转换。
二、TableRouter 为什么有多组方法
TableRouter 没有只定义一个 route(),而是按操作和可用参数提供不同入口:
insertRoute(logicTable, entity)
queryRoute(logicTable)
queryRoute(logicTable, criteria)
queryRoute(logicTable, query)
updateRoute(logicTable, id)
updateRoute(logicTable, criteria)
deleteRoute(logicTable, id)
deleteRoute(logicTable, criteria)
原因很直接:不同操作掌握的路由信息不同。
- 插入时有完整实体,可以读取创建时间;
- 按 ID 更新时通常只有 ID;
- 条件查询可以从 Criteria 中寻找时间范围;
- 分页查询还可能从 Query 中获取条件;
- 无条件 count 不一定知道目标月份。
默认实现全部返回逻辑索引,只有需要分索引的 DAO 才覆盖对应方法。
三、写入按月路由最容易实现
演示 DAO 的插入规则是:
@Override
public String insertRoute(String logicTable, Entity entity) {
if (entity instanceof OperateLogEntity log) {
return logicTable + "-" + DateTimeUtil.format2Str(
log.getCreateTime(), Format.YYYY_MM);
}
return logicTable;
}
ES DAO 在构造 CreateRequest 之前调用它:
String index = insertRoute(logicTable, entity);
CreateRequest request = builder
.index(index)
.id(pkValue)
.document(entity)
.build();
批量插入也会逐条执行 insertRoute,所以一个批次中的不同实体可以写入不同月份索引。
这比用第一条数据决定整个批次索引更安全。
四、按 ID 更新为什么可能找不到索引
更新接口可能只有:
documentId = 9842...
如果 ID 本身不包含月份,updateRoute(logicTable, id) 无法推断文档在哪个物理索引。
常见解决方案有三种:
- ID 编码时间或分片信息;
- 业务调用同时提供月份,再构造带路由信息的条件;
- 先通过独立映射找到索引,再定点更新。
直接返回逻辑索引 app-log,只有当该名称被配置成可写别名且能唯一定位文档时才有意义。普通字符串不会自动代表所有 app-log-* 索引。
更新别名还要注意 Elasticsearch 的 write index 配置,避免多个物理索引同时匹配写操作。
五、单月查询应从时间条件计算索引
如果查询条件包含完整月份范围:
createTime >= 2026-08-01
createTime < 2026-09-01
queryRoute(logicTable, criteria) 可以返回:
app-log-2026-08
这样只搜索一个索引,分片数、查询成本和结果归并都最可控。
不过当前 Criteria 是通用表达模型,路由实现需要自行解析其中与时间有关的条件。框架不会凭字段名自动猜测哪个字段是分索引键。
六、跨月查询要返回多个索引表达式
查询 7 月到 9 月的数据时,可以构造:
app-log-2026-07,app-log-2026-08,app-log-2026-09
或者使用受控通配符:
app-log-2026-*
也可以维护读别名,例如:
app-log-recent
选择哪种方式取决于:
- 时间跨度是否明确;
- 历史索引数量;
- 是否允许查询全部历史数据;
- 索引生命周期和别名维护方式;
- ES 集群对通配符展开的限制。
范围已知时,枚举准确月份通常比无边界通配符更可控。
七、示例代码当前展示的是扩展点,不是完整跨月方案
OperateLogEsDao 当前只在 insertRoute 中拼接月份,而查询、更新和删除路由仍返回 logicTable。
因此它准确证明了:
BaseEntityEsDao 支持在操作前调用 TableRouter
DAO 可以按实体时间把写入路由到月索引
但它不能单独证明:
app-log 查询一定会自动命中 app-log-*
按 ID 更新会自动找到历史月份
跨月查询已经自动拆分和归并
这些能力需要索引别名、通配符或更完整的路由实现配合。
把演示代码的边界说清楚,比把一个扩展接口宣传成全自动分索引更有工程价值。
八、缺失索引如何处理
跨月查询经常包含没有数据、因此从未创建的月份。
此时要明确:
- 查询不存在的索引是报错还是忽略;
- 是否提前创建每月索引和 Mapping;
- 是否允许自动创建索引;
- 通配符没有匹配结果时返回空集还是异常;
- 批量请求中部分索引失败如何呈现。
当前 BaseEntityEsDao 捕获 Elasticsearch 异常后统一转换为 DaoException,没有在 DAO 层自动吞掉“索引不存在”。
如果业务希望缺失月份等价于空数据,应在索引治理或查询构建层显式实现,而不是依赖异常信息变化。
九、Mapping 和索引模板必须跟上路由
按月创建物理索引后,每个月都要保持一致的字段类型和分析器。
否则同一个逻辑字段可能出现:
2026-07:userId 是 keyword
2026-08:userId 被动态映射成 long
跨月查询和聚合就会失败。
生产环境通常需要索引模板或创建流程统一:
- Mapping;
- 分片与副本;
- refresh 策略;
- 生命周期策略;
- 读写别名;
- 版本升级兼容。
TableRouter 只负责返回索引名称,不负责创建模板或迁移历史索引。
十、动态索引路由必须满足读写对称
可以把核心契约总结为:
写入时能确定物理索引
按 ID 修改和删除时能重新定位同一索引
单分片查询尽量定点
跨分片查询明确列出范围
缺失索引行为可预期
Mapping 在所有物理索引间兼容
MetaLite 把路由决策放在 DAO 接口层,而不是内置一套固定的月份规则,是因为分片键可能是日期、租户、业务 ID 或其他字段。
扩展点的价值不在于替业务自动猜测,而在于让每种操作都必须显式回答“这次究竟访问哪个物理索引”。
框架简介
MetaLite 是面向企业生产环境的新一代 Java 微服务技术底座。系列文章重点分享代码背后的设计思路、技术取舍与工程实践。
源码基线
JDK 21、Spring Boot 3.2.9、Spring Cloud 2023.0.1、Spring Cloud Alibaba 2023.0.1.3,具体组件版本以项目 backend-bom 为准。
作者简介
15 年 Spring 体系企业级开发经验,专注于 Java 微服务架构、工程治理与生产实践。
持续更新
MetaLite 系列内容将持续更新,围绕核心设计、源码链路、技术取舍与生产实践展开。欢迎关注作者,及时获取后续内容。
在线演示
演示地址: https://admin.metalite.top/
演示账号: guess
演示密码: admin@2026

6808

被折叠的 条评论
为什么被折叠?



