数据库修复: - 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]>
400 lines
12 KiB
Markdown
400 lines
12 KiB
Markdown
# 告警规则Redis缓存同步方案设计
|
||
|
||
## 📊 现状分析
|
||
|
||
### 共享Redis架构确认 ✅
|
||
经过代码分析确认,**ljwx-boot 和 ljwx-bigscreen 共用一个Redis实例**:
|
||
|
||
**ljwx-boot配置** (`application-local.yml:19`):
|
||
```yaml
|
||
redis:
|
||
url: redis://default:123456@localhost:6379/1 # DB=1
|
||
```
|
||
|
||
**ljwx-bigscreen配置** (`redis_config.py:17`):
|
||
```python
|
||
self.db = int(os.getenv('REDIS_DB', 0)) # DB=0(默认)
|
||
```
|
||
|
||
**重要发现**: 两个服务使用**不同的Redis DB**,完全避免了键冲突!
|
||
|
||
### 当前实现情况
|
||
通过分析 `TAlertRulesFacadeImpl` 发现,系统已经实现了完善的Redis缓存同步机制:
|
||
|
||
```java
|
||
// 当前实现 - 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策略调整
|
||
```java
|
||
// 当前 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 版本控制机制
|
||
```java
|
||
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订阅者
|
||
```python
|
||
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 多级缓存策略
|
||
```python
|
||
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 健康检查机制
|
||
```python
|
||
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配置优化
|
||
```yaml
|
||
# 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
|
||
```
|
||
|
||
```python
|
||
# 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
|
||
)
|
||
```
|
||
|
||
### 缓存参数
|
||
```properties
|
||
# 告警规则缓存配置
|
||
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*
|
||
*技术负责人: 系统架构团队* |