• 编码技巧——MongoDB的过期时间


    在开发中遇到一个场景:将消费订单校验失败的消息记录下来,因为校验失败的原因除了业务失败还可能是RPC中下游的异常导致,记录这些失败记录便于做流量回放和补偿,并且消费订单的消息是具备时效性的;准备使用MongoDB来存储数据,并且需要一个TTL的功能;

    本篇介绍MongoDB的过期删除策略及使用;MongoDB的集合有TTL (time to live,即生存的时间) 特性,可以让MongoDB自动移除过期了的数据;

    这种机制便比较适合一些记录【消息数据/事件数据】这种具备时效性数据的业务场景;

    原理上,MongoDB通过一个TTL索引来实现这种机制:MongoDB通过一个后台线程去不断的读取集合中某个日期类型的索引,并且移除掉满足过期条件的文档documents;

    下面介绍下如何使用;

    使用步骤

    通过 db.collection.createIndex() 命令创建索引Index,然后配合Index的expireAfterSeconds选项来对某个字段做TTL索引;

    注意:这个字段必须是date类型或者是一个包含date类型值的数组字段,一般我们使用date类型;

    创建TTL过期规则有2种模式:

    (1)一种是固定时间间隔后失效的模式,类似Redis的ttl,即设置一个失效时间,当MongoDB发现当前时间与时间索引字段的时差超过了这个时间间隔,则认为数据过期;

    (2)另一种是指定失效的时间点,即设置一个过期时间点,当MongoDB发现当前系统时间已经超过那个时间点,则认为数据过期;但是这种方式下,真正失效数据的时间与我们设定的时间点存在一点误差(0~60s),因为MongoDB后台线程的检测间隔是60秒

    下面我们分别看下两种模式的设置方式;

    (1)指定过期的时间间隔

    假如我们准备存放订单消费消息,其中的记录格式为:

    1. {
    2. "_id": "5f43d5c00b34962beb026aad",
    3. "messageBody": "...",
    4. "reqNo": "1598281152423-6245975993217447",
    5. "createdAt": Date()
    6. }

    接下来,对 createdAt 字段建立一个TTL索引:

    1. # db为数据库名,consume_message为集合名;
    2. db.consume_message.createIndex({ "createdAt": 1 }, {expireAfterSeconds: 3600 })

    其含义是,记录在createdAt字段的值的时刻基础上,再加上3600秒之后的那个时间过期;其中 createdAt: 1 表示对 createdAt 字段建立正序的索引;索引的选项 expireAfterSeconds: 3600 表示 记录在3600秒(即1小时) 之后过期;使用时,将createdAt的值设置为当前时间即可;

    实际测试时,过期时间符合预期;

    (2)指定过期的时间点

    有时候,我们不希望在记录创建时刻之后的多少秒再删除(因为这样相当于固定死了失效间隔),而是希望在程序运行时指定某个时刻 (例如避开流量的高峰期) 进行失效;

    因此,我们希望给文档(记录)指定一个特定的过期时间点,这种规则,MongoDB也是支持的——在创建TTL索引时,只需把 expireAfterSeconds 配置的值设为0即可;

    例如我们把这个TTL索引字段取名叫做expireAt,数据记录的结构如下所示:

    1. {
    2. "_id": "5f43d5c00b34962beb026aad",
    3. "messageBody": "...",
    4. "reqNo": "1598281152423-6245975993217447",
    5. "expireAt": Date()
    6. }

    然后,创建expireAt字段的TTL索引:

    1. # db为数据库名,consume_message为集合名;
    2. db.consume_message.createIndex({ "expiredAt": 1 }, {expireAfterSeconds: 0})

    至此,我们就把expireAt字段配置为TTL索引,其中expireAfterSeconds:0 表示MongoDB将用索引字段,也就是expireAt的时间值加0秒后的时间(即expireAt的值本身)作为判断数据失效的依据;

    实际测试时,过期时间不符合预期,而是发现到了指定的过期时间,数据还未删除,等了几十秒后才删除;可以发现MongoDB并不是立刻删除该数据的,而是在大概60秒之后才删除,这是因为MongoDB后台线程的检测间隔是60秒

    第二种过期模式实际生产中用到的场景较多,如日志数据/订单数据仅保留X天,可以在每条记录入库的时候,就给他添加一个expireAt字段,用来标记该记录的过期时间;

    在Springboot下的使用示例

    在SpringBoot中继承MongoDB,《SpringBoot整合MongoDB》这篇文章讲的很细,这里仅贴个使用示例的代码;

    记录实体(集合)定义:

    1. /**
    2. * 活动订单消息
    3. * 集合(表名):activity_consume_message_content
    4. */
    5. @Data
    6. @Document(collection = "activity_consume_message_content")
    7. @CompoundIndexes({
    8. @CompoundIndex(name = "activity_user", def = "{'activityId':1,'userId':1}")
    9. })
    10. public class ActivityConsumeMessageContent {
    11. @Id
    12. private ObjectId id;
    13. private Long activityId;
    14. private String userId;
    15. private String orderNo;
    16. private ConsumeOrderMessageDto consumeMessage;
    17. /**
    18. * 过期时间 推荐使用expireAt模式 可以运行时配置过期时间 (ttl模式无法修改失效间隔配置)
    19. */
    20. @Indexed(name = "expire_time", background = true, expireAfterSeconds = 0)
    21. private Date expireAt;
    22. }

    指定失效时间:

    1. // 订单消息默认保存30天 支持配置
    2. Date expireAt = new Date(System.currentTimeMillis() + ConfigManager.getLong("consumeMessageContent.expireDays", 30) * 24 * 60 * 60 * 1000);
    3. consumeMessageContent.setExpireAt(expireAt);
    4. mongoTemplate.insert(consumeMessageContent);

    效果:

    注意,测试环境使用Robo 3T作为client链接MongoDB,但是发现到期时间显示的值与预期时间差了8小时,这是因为Robo 3T默认设置的时区是UTC,而我们位于东八区,只需要将Robo 3T设置为Local Timezone即可显示正常的时间;

    参考:

    SpringBoot整合MongoDB

    通过mongodbTTL机制让集合中的数据自动过期删除

  • 相关阅读:
    ds配置datax数据同步工具
    图文详解Linux基础经典教程(08)——CentOS安装MySQL数据库
    1.在vsCode上创建Hello,World
    从零开始学React--JSX
    第n个程序员节
    【软件设计师-从小白到大牛】上午题基础篇:第二章 操作系统
    22年11月-自研-面试题
    使用解构赋值简化axios返回对象属性元素的提取
    【无标题】
    LF-YOLO: A Lighter and Faster YOLO for Weld Defect Detection of X-ray Image
  • 原文地址:https://blog.csdn.net/minghao0508/article/details/125597645