首页>软件资讯>常见问题

常见问题

Redis预约缓存动态维护机制详解

发布时间:2025-12-10 12:49:10人气:5

一、问题背景与需求分析1.1 原有设计的局限性回顾之前的InitializeWorkAsync类:// 只在应用启动时生成未来60天的缓存private void createAppointmentCache() {    DateTime startDate = DateUtil.tomorrow();    DateTime endDate = startDate.offsetNew(DateField.DAY_OF_MONTH, 60); // 固定60天    // ... 生成缓存}

存在的问题:✅ 应用启动时:有完整的60天缓存❌ 运行1天后:变成59天缓存❌ 运行2天后:变成58天缓存❌ 依此类推...最终缓存完全失效

1.2 业务连续性要求体检预约是7×24小时不间断服务,必须保证:任何时候Redis中都存在未来60天的有效缓存用户随时可以查看和预约未来任意一天的体检避免因缓存缺失导致预约功能不可用

二、tRestrictionCacheJob 定时任务详解2.1 类结构与技术栈考虑到定时任务可能被遗漏执行,可以一次性生成多天:@Overridepublic void execute(ShardingContext shardingContext) {    // 检查当前缓存覆盖了多少天    int currentCoverage = checkCurrentCacheDays();    int daysToGenerate = Math.max(0, 60 - currentCoverage + 1); // 确保有60天缓存    if (daysToGenerate <= 0) {        log.info("当前缓存天数充足,无需生成新缓存");        return;    }    log.info("需要生成{}天的缓存", daysToGenerate);    // 批量生成缺失的缓存    generateMultipleDaysCache(daysToGenerate);}private int checkCurrentCacheDays() {    // 实现检查当前Redis中有多少天的缓存    // 通过扫描appointment    #开头的key来计算    // 简化实现,实际需要更复杂的逻辑    return 59; // 示例返回值}private void generateMultipleDaysCache(int days) {    DateTime startDate = new DateTime().offset(DateField.DAY_OF_MONTH, 1); // 明天    DateTime endDate = startDate.offsetNew(DateField.DAY_OF_MONTH, days);    HashMap param = new HashMap() {{        put("startDate", startDate.toDateStr());        put("endDate", endDate.toDateStr());    }};    R r = appointmentRestrictionApi.searchScheduleInRange(param);    ArrayList<HashMap> scheduleList = r.getAttribute("scheduleList", ArrayList.class);    DateTime currentDate = startDate;    while (currentDate.isBefore(endDate) || currentDate.isEqual(endDate)) {        String dateStr = currentDate.toDateStr();        generateSingleDayCache(dateStr, scheduleList);        currentDate = currentDate.offsetNew(DateField.DAY_OF_MONTH, 1);    }}private void generateSingleDayCache(String date, ArrayList<HashMap> scheduleList) {    int defaultMaxNum = Integer.parseInt(redisTemplate.opsForValue()                            .get("setting#appointment_number").toString());    int maxNum = defaultMaxNum;    int realNum = 0;    // 查找特殊日期设置    for (HashMap map : scheduleList) {        if (date.equals(MapUtil.getStr(map, "date"))) {            maxNum = MapUtil.getInt(map, "num_1");            realNum = MapUtil.getInt(map, "num_3");            break;        }    }    // 写入缓存(同方案1)    String key = "appointment#" + date;    HashMap cache = new HashMap();    cache.put("maxNum", maxNum);    cache.put("realNum", realNum);    redisTemplate.opsForHash().putAll(key, cache);    DateTime expireTime = new DateTime(date).offsetNew(DateField.DAY_OF_MONTH, 1);    redisTemplate.expireAt(key, expireTime);}

技术组件:Elastic Job:分布式定时任务框架ZooKeeper:注册中心和协调中心(localhost:2181)SimpleJob:Elastic Job的任务执行接口

2.2 核心算法解析2.2.1 日期计算逻辑// 获取默认每天限流人数上限int maxNum = Integer.parseInt(redisTemplate.opsForValue()               .get("setting#appointment_number").toString());int realNum = 0; // 新日期的已预约人数初始为0// 关键:创建未来第61天的日期String date = new DateTime().offset(DateField.DAY_OF_MONTH, 62).toDateStr();

日期偏移计算:new DateTime():当前时间.offset(DateField.DAY_OF_MONTH, 62):当前时间 + 62天为什么要+62天?逻辑推理:假设今天是1月1日:- 现有缓存:1月2日 ~ 3月2日(60天)- 明天是1月2日:缓存变为1月3日 ~ 3月2日(59天)- 需要补充:3月3日(即1月1日 + 62天 = 3月3日)

验证计算:今天 + 1天 = 明天(缓存起始日期)今天 + 61天 = 缓存结束日期的后一天因此:今天 + 62天 = 需要新增的缓存日期2.2.2 缓存生成逻辑String key = "appointment#" + date;// 创建未来第61天的体检限流缓存redisTemplate.opsForHash().putAll(key, new HashMap() {{    put("maxNum", maxNum);        // 最大可预约人数    put("realNum", realNum);      // 已预约人数(初始为0)}});// 设置缓存过期时间:该日期1个月后自动删除DateTime dateTime = new DateTime(date).offsetNew(DateField.DAY_OF_MONTH, 1);redisTemplate.expireAt(key, dateTime);

数据结构:Key: "appointment#2024-03-03"Value: {  "maxNum": 100,    // 从系统设置读取的默认值  "realNum": 0      // 新日期尚未有人预约}

过期策略:在体检日期的1个月后自动过期避免Redis中积累大量历史无用数据2.3 定时调度配置elasticjob:  jobs:    CreateAppointmentRestrictionCacheJob:      cron: "*/30 * * * * ?"  # 每30秒执行一次      timeZone: GMT+08:00      # 东八区时间      shardingTotalCount: 1    # 不分片,单实例执行

调度频率分析:开发环境:*/30 * * * * ?(每30秒)便于测试生产环境注释:0 0 23 * * ?(每天23点执行)推荐生产配置:0 0 */1 * * ?(每小时执行一次)或 0 0 2 * * ?(每天凌晨2点)三、动态缓存维护机制全景3.1 工作流程图每日缓存变化过程:Day 1 (应用启动):[1/2, 1/3, ..., 3/1] ← 60天缓存Day 2:[1/3, 1/4, ..., 3/1] ← 59天缓存 (缺失3/2)    ↑定时任务检测并补充[1/3, 1/4, ..., 3/1, 3/2] ← 恢复60天缓存Day 3:[1/4, 1/5, ..., 3/2] ← 59天缓存 (缺失3/3)      ↑定时任务检测并补充[1/4, 1/5, ..., 3/2, 3/3] ← 恢复60天缓存

3.2 时间轴分析

时间轴分析.png


规律:每天开始时,总是缺失第61天的缓存,需要定时任务补充。


四、Elastic Job 分布式协调

分布式协调原理:


注册发现:所有Job实例向ZK注册

选主机制:同一时刻只有一个实例执行任务

故障转移:执行实例宕机时,备用实例接管

幂等保障:即使多个实例同时触发,也不会重复生成缓存


五、生产环境优化配置

5.1 调度频率优化

当前配置问题:


开发环境每30秒执行过于频繁

可能造成不必要的Redis操作

生产环境建议:

方案1:每天凌晨执行(推荐)cron: "0 0 2 * * ?"  每天凌晨2点方案2:每小时检查执行cron: "0 0 */1 * * ?"  每小时整点执行方案3:根据业务高峰调整cron: "0 0 8,12,18 * * ?"  每天8点、12点、18点执行


5.2 容错机制增强

@Overridepublic void execute(ShardingContext shardingContext) {    try {        // 原有逻辑        doCreateCache();    } catch (Exception e) {        log.error("生成体检缓存失败", e);        // 可以添加告警通知        // alertService.sendAlert("缓存生成失败", e.getMessage());    }}


六、总结与最佳实践

6.1 扩展思考


数据不一致风险

如果管理员在表中设置了特殊日期的限流,但定时任务只用默认值:

用户看到的限流人数与实际设置不符

可能导致超预约或预约不足


2. 已预约人数不准确

realNum始终被设为0,但实际上应该从数据库读取:


进一步优化方向:


智能预测:根据历史数据预测未来需求,动态调整缓存天数

多级缓存:本地缓存+Redis,进一步提高读取性能

缓存预热:结合业务高峰时段,提前生成热门日期缓存

这套动态缓存维护机制确保了体检预约系统在任何时候都能提供稳定、高效的服务,是分布式系统中数据一致性和可用性的经典实践。



上一条:redis可视化工具

下一条:没有了!