Files
java-template/docs/alert_rules_cache_sync_solutions.md
T
BrunoGaoandClaude e402548e45 fix: 修复customer_id字段缺失问题,完成数据库schema同步
数据库修复:
- t_user_health_data_daily: 添加customer_id字段(租户ID)
- t_user_health_data_weekly: 添加customer_id字段(租户ID)

ljwx-boot同步更新:
- TUserHealthDataDailyMapper.xml: 添加customer_id字段映射
- TUserHealthDataWeeklyMapper.xml: 添加customer_id字段映射

其他改进:
- 告警规则缓存优化
- Redis缓存性能提升
- 健康数据处理性能优化
- 添加完整的文档和工具脚本

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2025-09-10 13:49:59 +08:00

12 KiB
Raw Blame History

告警规则Redis缓存同步方案设计

📊 现状分析

共享Redis架构确认 ✅

经过代码分析确认,ljwx-boot 和 ljwx-bigscreen 共用一个Redis实例:

ljwx-boot配置 (application-local.yml:19):

redis:
  url: redis://default:123456@localhost:6379/1  # DB=1

ljwx-bigscreen配置 (redis_config.py:17):

self.db = int(os.getenv('REDIS_DB', 0))  # DB=0(默认)

重要发现: 两个服务使用不同的Redis DB,完全避免了键冲突!

当前实现情况

通过分析 TAlertRulesFacadeImpl 发现,系统已经实现了完善的Redis缓存同步机制:

// 当前实现 - ljwx-boot/ljwx-boot-modules/src/main/java/com/ljwx/modules/health/facade/impl/TAlertRulesFacadeImpl.java:87
RedisUtil.publish("alert_rules_channel", "update:" + customerId);

现有机制:

  • ✅ 事件触发: 增删改操作后自动触发Redis更新
  • ✅ 分客户缓存: 按 customer_id 分别缓存告警规则
  • ✅ 发布订阅: 使用Redis pub/sub通知缓存更新
  • ✅ JSON存储: 规则数据以JSON格式存储到Redis
  • ✅ 无键冲突: DB分离架构避免键冲突

API接口:

  • POST /t_alert_rules/ - 新增规则 (line:76)
  • PUT /t_alert_rules/ - 更新规则 (line:84)
  • DELETE /t_alert_rules/ - 删除规则 (line:91)

🚀 优化方案对比

方案一:现有机制改进 (推荐)

优势:

  • 基于现有架构,改动最小
  • 已实现分客户隔离和事件触发
  • 可直接延长TTL时间

改进点:

  1. 缓存策略优化
  2. 订阅者管理完善
  3. 容错机制增强

方案二:数据库触发器

优势:

  • 数据库级别保证一致性
  • 无需修改业务代码

劣势:

  • 数据库性能影响
  • 跨平台兼容性问题
  • 调试困难

方案三:消息队列 (RabbitMQ/Kafka)

优势:

  • 解耦性好
  • 消息可靠性高

劣势:

  • 架构复杂度增加
  • 运维成本提升
  • 延迟相对较高

方案四:CDC (Change Data Capture)

优势:

  • 实时性极高
  • 数据一致性强

劣势:

  • 技术复杂度高
  • 维护成本大

🎯 推荐方案详细设计

核心架构(共享Redis优化版)

ljwx-admin → ljwx-boot (Redis DB=1) → MySQL
                ↓
            Redis Pub/Sub(跨DB消息)
                ↓
        ljwx-bigscreen (Redis DB=0) → generate_alerts (Python)

架构优势:

  • DB分离: ljwx-boot使用DB=1,ljwx-bigscreen使用DB=0,完全避免键冲突
  • 跨DB通信: Redis pub/sub消息在所有DB间共享,实现完美的事件通知
  • 零冲突: 现有缓存键完全隔离,无需担心键名冲突

1. 缓存策略改进

1.1 TTL策略调整

// 当前 TTL 可以设置为更长时间,如24小时
RedisUtil.setex("alert_rules_" + customerId, 86400, jsonString);

1.2 缓存Key设计

alert_rules_{customer_id}          # 主缓存
alert_rules_version_{customer_id}  # 版本控制
alert_rules_lock_{customer_id}     # 分布式锁

1.3 版本控制机制

public void updateAlertRulesCache(Long customerId) {
    String lockKey = "alert_rules_lock_" + customerId;
    String versionKey = "alert_rules_version_" + customerId;
    
    // 获取分布式锁
    if (RedisUtil.tryLock(lockKey, 5000)) {
        try {
            // 增加版本号
            Long version = RedisUtil.incr(versionKey);
            
            // 更新缓存
            List<TAlertRules> rules = getRulesByCustomerId(customerId);
            String cacheKey = "alert_rules_" + customerId;
            
            Map<String, Object> cacheData = new HashMap<>();
            cacheData.put("version", version);
            cacheData.put("rules", rules);
            cacheData.put("updateTime", System.currentTimeMillis());
            
            RedisUtil.setex(cacheKey, 86400, JSON.toJSONString(cacheData));
            
            // 发布更新通知
            RedisUtil.publish("alert_rules_channel", 
                "update:" + customerId + ":" + version);
                
        } finally {
            RedisUtil.unlock(lockKey);
        }
    }
}

2. 订阅者管理完善

2.1 Python端跨DB订阅者

import redis
import json
import logging
from threading import Thread
import time

class AlertRulesSubscriber:
    def __init__(self, redis_client, boot_redis_client):
        self.redis = redis_client  # ljwx-bigscreen Redis (DB=0)
        self.boot_redis = boot_redis_client  # ljwx-boot Redis (DB=1)
        self.local_cache = {}
        self.running = True
        
    def start_subscriber(self):
        """启动订阅者"""
        pubsub = self.redis.pubsub()
        pubsub.subscribe('alert_rules_channel')
        
        def subscriber_thread():
            for message in pubsub.listen():
                if message['type'] == 'message':
                    self.handle_cache_update(message['data'])
                    
        Thread(target=subscriber_thread, daemon=True).start()
        
    def handle_cache_update(self, message):
        """处理缓存更新消息"""
        try:
            # 解析消息: "update:customer_id:version"
            parts = message.decode('utf-8').split(':')
            if len(parts) >= 2 and parts[0] == 'update':
                customer_id = parts[1]
                version = int(parts[2]) if len(parts) > 2 else 0
                
                # 检查是否需要更新本地缓存
                if self.should_update_cache(customer_id, version):
                    self.refresh_local_cache(customer_id)
                    
        except Exception as e:
            logging.error(f"处理缓存更新失败: {e}")
            
    def should_update_cache(self, customer_id, version):
        """检查是否需要更新缓存"""
        local_version = self.local_cache.get(f"version_{customer_id}", 0)
        return version > local_version
        
    def refresh_local_cache(self, customer_id):
        """刷新本地缓存 - 从ljwx-boot的Redis DB获取"""
        try:
            cache_key = f"alert_rules_{customer_id}"
            # 从ljwx-boot的Redis DB=1获取数据
            cached_data = self.boot_redis.get(cache_key)
            
            if cached_data:
                data = json.loads(cached_data)
                self.local_cache[customer_id] = data['rules']
                self.local_cache[f"version_{customer_id}"] = data['version']
                logging.info(f"更新告警规则缓存: customer_id={customer_id}")
                
        except Exception as e:
            logging.error(f"刷新本地缓存失败: {e}")
            
    def get_alert_rules(self, customer_id):
        """获取告警规则"""
        # 先从本地缓存获取
        if customer_id in self.local_cache:
            return self.local_cache[customer_id]
            
        # 本地缓存miss,从ljwx-boot的Redis获取
        try:
            cache_key = f"alert_rules_{customer_id}"
            cached_data = self.boot_redis.get(cache_key)
            
            if cached_data:
                data = json.loads(cached_data)
                self.local_cache[customer_id] = data['rules']
                self.local_cache[f"version_{customer_id}"] = data['version']
                return data['rules']
        except Exception as e:
            logging.error(f"从ljwx-boot Redis获取规则失败: {e}")
            
        # 兜底:从数据库获取
        return self.get_rules_from_database(customer_id)

3. 容错机制设计

3.1 多级缓存策略

class AlertRulesCacheManager:
    def __init__(self):
        self.l1_cache = {}  # 进程内缓存
        self.l2_cache = None  # Redis缓存
        self.l3_cache = None  # 数据库
        
    def get_alert_rules(self, customer_id):
        """三级缓存获取"""
        # L1: 进程缓存
        if self.is_l1_cache_valid(customer_id):
            return self.l1_cache[customer_id]['rules']
            
        # L2: Redis缓存  
        rules = self.get_from_redis(customer_id)
        if rules:
            self.update_l1_cache(customer_id, rules)
            return rules
            
        # L3: 数据库兜底
        rules = self.get_from_database(customer_id)
        if rules:
            self.update_all_cache(customer_id, rules)
            return rules
            
        return []

3.2 健康检查机制

class CacheHealthChecker:
    def check_cache_health(self):
        """缓存健康检查"""
        checks = {
            'redis_connection': self.check_redis_connection(),
            'cache_consistency': self.check_cache_consistency(),
            'pub_sub_status': self.check_pub_sub_status(),
        }
        return checks
        
    def auto_recovery(self):
        """自动恢复机制"""
        if not self.check_redis_connection():
            self.reconnect_redis()
            
        if not self.check_pub_sub_status():
            self.restart_subscriber()

📈 性能对比分析

方案 实时性 一致性 复杂度 性能影响 推荐指数
现有机制改进 毫秒级 强一致 低 最小 ⭐⭐⭐⭐⭐
数据库触发器 毫秒级 强一致 中 中等 ⭐⭐⭐
消息队列 秒级 最终一致 高 较大 ⭐⭐
CDC方案 毫秒级 强一致 很高 较大 ⭐⭐

🛠️ 实施步骤

阶段一:缓存策略优化 (1-2天)

  1. 调整Redis TTL为24小时
  2. 添加版本控制机制
  3. 完善分布式锁逻辑

阶段二:订阅者完善 (2-3天)

  1. Python端实现Redis订阅者
  2. 本地缓存与Redis缓存结合
  3. 添加容错和重连机制

阶段三:监控和测试 (1-2天)

  1. 添加缓存监控指标
  2. 压力测试验证
  3. 故障场景测试

🔧 配置建议

Redis配置优化

# ljwx-boot Redis配置 (DB=1)
redis:
  url: redis://default:123456@localhost:6379/1
  timeout: 5000
  lettuce:
    pool:
      max-active: 20
      max-idle: 10
      min-idle: 5
  # 启用键空间通知
  notify-keyspace-events: Ex
# ljwx-bigscreen Redis配置 (DB=0)
class RedisConfig:
    def __init__(self):
        self.host = '127.0.0.1'
        self.port = 6379
        self.db = 0  # ljwx-bigscreen使用DB=0
        self.password = '123456'
        
    def get_boot_redis_client(self):
        """获取ljwx-boot的Redis客户端(DB=1)"""
        return Redis(
            host=self.host, port=self.port, 
            db=1, password=self.password  # DB=1
        )

缓存参数

# 告警规则缓存配置
alert.rules.cache.ttl=86400
alert.rules.cache.local.size=1000
alert.rules.cache.local.ttl=300
alert.rules.pub.channel=alert_rules_channel

📊 监控指标

建议监控以下指标:

  • 缓存命中率: L1/L2/L3缓存命中率
  • 更新延迟: 从修改到缓存更新的延迟
  • 订阅者状态: pub/sub连接状态
  • 数据一致性: 缓存与数据库的一致性检查
  • 错误率: 缓存更新失败率

⚠️ 注意事项

  1. 渐进式部署: 先在测试环境验证,再灰度发布
  2. 回滚准备: 保留原有查询数据库的逻辑作为兜底
  3. 监控告警: 设置缓存相关的监控告警
  4. 数据预热: 系统启动时预加载热点客户的规则数据
  5. 容量规划: 根据客户数量规划Redis内存容量

🎯 总结

推荐采用现有机制改进方案,因为:

  1. 零冲突架构: ljwx-boot(DB=1)和ljwx-bigscreen(DB=0)完全隔离,无键冲突风险
  2. 改动最小: 基于现有pub/sub机制改进,无需修改架构
  3. 风险可控: 保留数据库兜底逻辑,多级缓存保障
  4. 性能最佳: TTL可安全延长到24小时,大幅减少数据库查询
  5. 维护简单: 不引入额外中间件,现有运维流程不变
  6. 扩展性好: DB分离架构天然支持服务隔离和独立扩展

🔥 关键优势

共享Redis + DB分离是最优方案:

  • 硬件成本最低: 共用一个Redis实例
  • 管理成本最低: 统一的Redis运维
  • 冲突风险为零: DB分离完全避免键冲突
  • 通信效率最高: pub/sub跨DB消息零延迟

通过这套方案,Redis告警规则缓存的TTL可以安全地延长到24小时或更久,同时保证数据的实时性和一致性,完美实现您的需求!


文档创建时间: 2025-09-09
技术负责人: 系统架构团队