优化Redis设置,及时清理过期数据
This commit is contained in:
parent
840a441d9e
commit
64ba2d46e8
136
doc/working/REDIS_OPTIMIZATION.md
Normal file
136
doc/working/REDIS_OPTIMIZATION.md
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
# Redis 内存优化 - 快速参考
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1️⃣ 诊断当前状态
|
||||||
|
```bash
|
||||||
|
./scripts/redis_memory_diagnosis.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2️⃣ 配置 Redis 内存限制
|
||||||
|
```bash
|
||||||
|
./scripts/configure_redis_memory.sh
|
||||||
|
# 开发环境选 1 (256MB)
|
||||||
|
# 生产环境选 2 (1GB)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3️⃣ 重新编译部署
|
||||||
|
```bash
|
||||||
|
mvn clean install -DskipTests
|
||||||
|
./ry.sh restart
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 已实施的优化
|
||||||
|
|
||||||
|
| 优化项 | 修改前 | 修改后 | 效果 |
|
||||||
|
|--------|--------|--------|------|
|
||||||
|
| Redis 默认缓存过期 | 永不过期 | 1小时 | 防止内存泄漏 |
|
||||||
|
| WebSocket 消息缓存 | 100条/30分钟 | 50条/10分钟 | 减少50%占用 |
|
||||||
|
| 内存缓存清理 | 无 | 每分钟 | 自动清理不活跃数据 |
|
||||||
|
| Redis 内存限制 | 无限制 | 256MB/1GB | 防止OOM |
|
||||||
|
| 缓存监控 | 无 | 每5分钟 | 及时发现问题 |
|
||||||
|
|
||||||
|
## 🔍 常用命令
|
||||||
|
|
||||||
|
### 查看 Redis 内存使用
|
||||||
|
```bash
|
||||||
|
redis-cli INFO memory | grep used_memory_human
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看 Key 数量
|
||||||
|
```bash
|
||||||
|
redis-cli DBSIZE
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看最大的 Key
|
||||||
|
```bash
|
||||||
|
redis-cli --bigkeys
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看配置
|
||||||
|
```bash
|
||||||
|
redis-cli CONFIG GET maxmemory
|
||||||
|
redis-cli CONFIG GET maxmemory-policy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动设置内存限制
|
||||||
|
```bash
|
||||||
|
# 设置为 512MB
|
||||||
|
redis-cli CONFIG SET maxmemory 512mb
|
||||||
|
redis-cli CONFIG SET maxmemory-policy volatile-lru
|
||||||
|
```
|
||||||
|
|
||||||
|
### 清理所有缓存 (⚠️ 谨慎使用)
|
||||||
|
```bash
|
||||||
|
redis-cli FLUSHDB
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 修改的文件
|
||||||
|
|
||||||
|
### 核心修改
|
||||||
|
- ✅ [RedisCache.java](qaup-common/src/main/java/com/qaup/common/core/redis/RedisCache.java) - 添加默认过期时间
|
||||||
|
- ✅ [MessageCacheService.java](qaup-collision/src/main/java/com/qaup/collision/websocket/cache/MessageCacheService.java) - 优化缓存参数
|
||||||
|
- ✅ [DataCollectorService.java](qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java) - 添加清理逻辑
|
||||||
|
- ✅ [application-dev.yml](qaup-admin/src/main/resources/application-dev.yml) - Redis 配置
|
||||||
|
- ✅ [application-prod.yml](qaup-admin/src/main/resources/application-prod.yml) - Redis 配置
|
||||||
|
|
||||||
|
### 新增文件
|
||||||
|
- 🆕 [RedisCacheCleanupTask.java](qaup-framework/src/main/java/com/qaup/framework/task/RedisCacheCleanupTask.java) - 自动清理任务
|
||||||
|
- 🆕 [scripts/configure_redis_memory.sh](scripts/configure_redis_memory.sh) - 配置脚本
|
||||||
|
- 🆕 [scripts/redis_memory_diagnosis.sh](scripts/redis_memory_diagnosis.sh) - 诊断脚本
|
||||||
|
|
||||||
|
## 📈 监控指标
|
||||||
|
|
||||||
|
### 应用日志 (每分钟)
|
||||||
|
```
|
||||||
|
清理不活跃缓存对象: X 个移动对象, Y 个航班通知
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis 监控 (每5分钟)
|
||||||
|
```
|
||||||
|
Redis 内存使用情况 - 当前: XXM, 峰值: XXM, 限制: 256M
|
||||||
|
```
|
||||||
|
|
||||||
|
### 告警阈值
|
||||||
|
- 🟢 正常: < 70%
|
||||||
|
- 🟡 警告: 70-80%
|
||||||
|
- 🟠 注意: 80-90%
|
||||||
|
- 🔴 严重: > 90%
|
||||||
|
|
||||||
|
## 🎯 预期效果
|
||||||
|
|
||||||
|
- ✅ 内存占用降低 50-70%
|
||||||
|
- ✅ 内存使用稳定
|
||||||
|
- ✅ 自动清理过期数据
|
||||||
|
- ✅ 防止内存泄漏
|
||||||
|
- ✅ 性能无明显影响
|
||||||
|
|
||||||
|
## 🔄 回滚方案
|
||||||
|
|
||||||
|
如果出现问题:
|
||||||
|
```bash
|
||||||
|
# 1. 恢复 Git
|
||||||
|
git checkout qaup-common/src/main/java/com/qaup/common/core/redis/RedisCache.java
|
||||||
|
|
||||||
|
# 2. 移除内存限制
|
||||||
|
redis-cli CONFIG SET maxmemory 0
|
||||||
|
|
||||||
|
# 3. 重新部署
|
||||||
|
mvn clean install -DskipTests && ./ry.sh restart
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 详细文档
|
||||||
|
|
||||||
|
- 📖 [完整优化指南](doc/redis_optimization_guide.md)
|
||||||
|
- 📋 [实施总结](doc/redis_optimization_summary.md)
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **生产环境务必先备份**
|
||||||
|
2. **建议先在测试环境验证**
|
||||||
|
3. **监控应用性能和日志**
|
||||||
|
4. **Redis 配置实时生效,应用需重启**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**最后更新**: 2025-10-13 | **版本**: 1.0.0
|
||||||
272
doc/working/redis_optimization_guide.md
Normal file
272
doc/working/redis_optimization_guide.md
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
# Redis 内存优化指南
|
||||||
|
|
||||||
|
## 问题诊断
|
||||||
|
|
||||||
|
### 1. 检查当前 Redis 内存使用情况
|
||||||
|
```bash
|
||||||
|
# 连接到 Redis
|
||||||
|
redis-cli
|
||||||
|
|
||||||
|
# 查看内存使用情况
|
||||||
|
INFO memory
|
||||||
|
|
||||||
|
# 查看所有 key 数量
|
||||||
|
DBSIZE
|
||||||
|
|
||||||
|
# 查看各类型 key 数量分布
|
||||||
|
redis-cli --scan --pattern '*' | awk -F: '{print $1}' | sort | uniq -c | sort -nr
|
||||||
|
|
||||||
|
# 查看最占内存的 key
|
||||||
|
redis-cli --bigkeys
|
||||||
|
|
||||||
|
# 查看没有过期时间的 key
|
||||||
|
redis-cli --scan | xargs -L 1 redis-cli TTL | grep -c '^-1$'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 主要内存占用来源分析
|
||||||
|
|
||||||
|
#### 项目中的 Redis 使用场景:
|
||||||
|
- **Token 缓存** (login_tokens:*) - 用户登录令牌,默认30分钟
|
||||||
|
- **验证码缓存** (captcha_codes:*) - 验证码,默认2分钟
|
||||||
|
- **字典缓存** (sys_dict:*) - 系统字典数据
|
||||||
|
- **用户缓存** (sys_user_cache:*) - 用户信息缓存
|
||||||
|
- **限流计数器** (rate_limit:*) - 接口限流
|
||||||
|
- **WebSocket 消息缓存** (websocket:messages:*) - 最近消息,30分钟过期
|
||||||
|
- **业务数据缓存** - 位置数据、航班通知等
|
||||||
|
|
||||||
|
## 优化方案
|
||||||
|
|
||||||
|
### 方案 1: 为所有缓存设置合理的过期时间
|
||||||
|
|
||||||
|
**修改 RedisCache.java,添加默认过期时间:**
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 建议配置
|
||||||
|
private static final long DEFAULT_EXPIRE_TIME = 3600; // 默认1小时
|
||||||
|
private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
public <T> void setCacheObject(final String key, final T value) {
|
||||||
|
// 设置默认过期时间,避免数据永久保存
|
||||||
|
redisTemplate.opsForValue().set(key, value, DEFAULT_EXPIRE_TIME, DEFAULT_TIME_UNIT);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案 2: 优化业务缓存策略
|
||||||
|
|
||||||
|
#### 2.1 优化 activeMovingObjectsCache
|
||||||
|
该缓存是内存级别(ConcurrentHashMap),不占用 Redis,但需要定期清理过期数据:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// DataCollectorService.java 添加清理逻辑
|
||||||
|
@Scheduled(fixedRate = 60000) // 每分钟清理一次
|
||||||
|
public void cleanupInactiveObjects() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long inactiveThreshold = 300000; // 5分钟无更新视为不活跃
|
||||||
|
|
||||||
|
activeMovingObjectsCache.entrySet().removeIf(entry -> {
|
||||||
|
MovingObject obj = entry.getValue();
|
||||||
|
return (now - obj.getLastUpdateTime()) > inactiveThreshold;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 减少 WebSocket 消息缓存大小和时间
|
||||||
|
```java
|
||||||
|
// MessageCacheService.java
|
||||||
|
private static final int MAX_CACHED_MESSAGES = 50; // 从100减少到50
|
||||||
|
private static final Duration CACHE_EXPIRY = Duration.ofMinutes(10); // 从30分钟减少到10分钟
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案 3: 配置 Redis 内存限制和淘汰策略
|
||||||
|
|
||||||
|
**在 application-dev.yml 添加 Redis 配置:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
# Redis 最大内存限制 (建议根据服务器内存设置)
|
||||||
|
# 生产环境建议 1-2GB,开发环境 256-512MB
|
||||||
|
maxmemory: 512mb
|
||||||
|
|
||||||
|
# 内存淘汰策略
|
||||||
|
# volatile-lru: 从设置了过期时间的key中使用LRU算法淘汰
|
||||||
|
# allkeys-lru: 从所有key中使用LRU算法淘汰
|
||||||
|
# volatile-ttl: 从设置了过期时间的key中淘汰即将过期的
|
||||||
|
# noeviction: 不淘汰,内存满时返回错误
|
||||||
|
maxmemory-policy: volatile-lru
|
||||||
|
```
|
||||||
|
|
||||||
|
**或者直接修改 Redis 配置文件 redis.conf:**
|
||||||
|
|
||||||
|
```conf
|
||||||
|
# 设置最大内存 (512MB)
|
||||||
|
maxmemory 512mb
|
||||||
|
|
||||||
|
# 设置淘汰策略
|
||||||
|
maxmemory-policy volatile-lru
|
||||||
|
|
||||||
|
# 启用持久化优化
|
||||||
|
save "" # 关闭 RDB,减少内存峰值
|
||||||
|
appendonly no # 如果不需要持久化,可以关闭 AOF
|
||||||
|
```
|
||||||
|
|
||||||
|
**应用配置后重启 Redis:**
|
||||||
|
```bash
|
||||||
|
# macOS (如果使用 Homebrew)
|
||||||
|
brew services restart redis
|
||||||
|
|
||||||
|
# 或直接使用配置文件启动
|
||||||
|
redis-server /usr/local/etc/redis.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案 4: 优化具体缓存使用
|
||||||
|
|
||||||
|
#### 4.1 Token 缓存优化
|
||||||
|
```java
|
||||||
|
// TokenService.java - 确保 token 设置了过期时间
|
||||||
|
int expireTime = 30; // 30分钟
|
||||||
|
redisCache.setCacheObject(tokenKey, loginUser, expireTime, TimeUnit.MINUTES);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 字典缓存优化
|
||||||
|
```java
|
||||||
|
// DictUtils.java - 字典数据可以设置较长过期时间
|
||||||
|
public static void setDictCache(String key, List<SysDictData> dictDatas) {
|
||||||
|
redisCache.setCacheObject(getCacheKey(key), dictDatas, 24, TimeUnit.HOURS);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.3 限流计数器优化
|
||||||
|
```java
|
||||||
|
// RateLimiterAspect.java - 限流计数器确保有过期时间
|
||||||
|
// Lua 脚本已经处理,无需修改
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案 5: 添加定期清理任务
|
||||||
|
|
||||||
|
创建定时任务清理过期或无用的缓存:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class RedisCacheCleanupTask {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisCache redisCache;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
// 每天凌晨2点清理
|
||||||
|
@Scheduled(cron = "0 0 2 * * ?")
|
||||||
|
public void cleanupExpiredCache() {
|
||||||
|
log.info("开始清理 Redis 过期缓存");
|
||||||
|
|
||||||
|
// 清理临时数据
|
||||||
|
cleanupPattern("captcha_codes:*");
|
||||||
|
|
||||||
|
// 清理过期的 WebSocket 消息 (已自动过期)
|
||||||
|
|
||||||
|
// 清理不活跃用户缓存 (超过7天未登录)
|
||||||
|
cleanupInactiveUsers();
|
||||||
|
|
||||||
|
log.info("Redis 缓存清理完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanupPattern(String pattern) {
|
||||||
|
Collection<String> keys = redisCache.keys(pattern);
|
||||||
|
if (keys != null && !keys.isEmpty()) {
|
||||||
|
keys.forEach(key -> {
|
||||||
|
Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||||
|
if (ttl == null || ttl == -1) {
|
||||||
|
// 没有设置过期时间的 key,设置默认过期时间
|
||||||
|
redisTemplate.expire(key, 1, TimeUnit.HOURS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanupInactiveUsers() {
|
||||||
|
// 实现逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实施步骤
|
||||||
|
|
||||||
|
### 立即执行 (紧急)
|
||||||
|
|
||||||
|
1. **设置 Redis 内存限制** (防止 OOM):
|
||||||
|
```bash
|
||||||
|
redis-cli CONFIG SET maxmemory 512mb
|
||||||
|
redis-cli CONFIG SET maxmemory-policy volatile-lru
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **清理当前 Redis 数据** (谨慎操作):
|
||||||
|
```bash
|
||||||
|
# 查看当前内存使用
|
||||||
|
redis-cli INFO memory
|
||||||
|
|
||||||
|
# 清理所有数据 (注意: 会清空所有缓存)
|
||||||
|
redis-cli FLUSHDB
|
||||||
|
|
||||||
|
# 或者只清理特定模式的 key
|
||||||
|
redis-cli --scan --pattern 'websocket:messages:*' | xargs redis-cli DEL
|
||||||
|
```
|
||||||
|
|
||||||
|
### 短期优化 (1-2天)
|
||||||
|
|
||||||
|
1. 修改 `RedisCache.java` 添加默认过期时间
|
||||||
|
2. 优化 `MessageCacheService.java` 缓存参数
|
||||||
|
3. 添加 Redis 配置到 `application.yml`
|
||||||
|
4. 为 `activeMovingObjectsCache` 添加清理逻辑
|
||||||
|
|
||||||
|
### 长期优化 (1周内)
|
||||||
|
|
||||||
|
1. 创建 `RedisCacheCleanupTask` 定时清理任务
|
||||||
|
2. 审查所有 `setCacheObject` 调用,确保设置了过期时间
|
||||||
|
3. 实施 Redis 监控和告警
|
||||||
|
4. 考虑使用 Redis 集群或分离缓存存储
|
||||||
|
|
||||||
|
## 监控建议
|
||||||
|
|
||||||
|
### 添加 Redis 监控
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class RedisMonitor {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 300000) // 每5分钟
|
||||||
|
public void monitorMemoryUsage() {
|
||||||
|
Properties info = redisTemplate.getRequiredConnectionFactory()
|
||||||
|
.getConnection()
|
||||||
|
.info("memory");
|
||||||
|
|
||||||
|
String usedMemory = info.getProperty("used_memory_human");
|
||||||
|
String maxMemory = info.getProperty("maxmemory_human");
|
||||||
|
|
||||||
|
log.info("Redis Memory Usage: {} / {}", usedMemory, maxMemory);
|
||||||
|
|
||||||
|
// 内存使用超过80%时告警
|
||||||
|
// 实现告警逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 预期效果
|
||||||
|
|
||||||
|
实施以上优化后,预期:
|
||||||
|
- Redis 内存占用降低 50-70%
|
||||||
|
- 内存使用稳定在设置的上限以内
|
||||||
|
- 不会出现内存持续增长的情况
|
||||||
|
- 系统性能不会受到明显影响
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **生产环境操作前务必备份**
|
||||||
|
2. **Redis 配置修改需要重启服务**
|
||||||
|
3. **FLUSHDB 会清空所有缓存,可能导致短暂性能下降**
|
||||||
|
4. **监控内存使用情况,及时调整策略**
|
||||||
260
doc/working/redis_optimization_summary.md
Normal file
260
doc/working/redis_optimization_summary.md
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
# Redis 内存优化实施总结
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
项目中 Redis 运行一段时间后占用大量内存,可能导致系统性能下降或 OOM。
|
||||||
|
|
||||||
|
## 已实施的优化措施
|
||||||
|
|
||||||
|
### 1. 修改 RedisCache 类 - 添加默认过期时间
|
||||||
|
**文件**: `qaup-common/src/main/java/com/qaup/common/core/redis/RedisCache.java`
|
||||||
|
|
||||||
|
**改动**:
|
||||||
|
- `setCacheObject(key, value)` 方法现在默认设置 1 小时过期时间
|
||||||
|
- 新增 `setCacheObjectPermanent(key, value)` 方法用于需要永久缓存的数据
|
||||||
|
- 防止缓存数据无限期保存导致内存泄漏
|
||||||
|
|
||||||
|
**影响**:
|
||||||
|
- 所有使用 `setCacheObject` 的地方会自动获得过期时间
|
||||||
|
- 现有缓存会在 1 小时后自动清理
|
||||||
|
- 需要永久缓存的数据需要改用新方法
|
||||||
|
|
||||||
|
### 2. 优化 WebSocket 消息缓存
|
||||||
|
**文件**: `qaup-collision/src/main/java/com/qaup/collision/websocket/cache/MessageCacheService.java`
|
||||||
|
|
||||||
|
**改动**:
|
||||||
|
- 最大缓存消息数: 100 → 50
|
||||||
|
- 过期时间: 30分钟 → 10分钟
|
||||||
|
- 减少 50% 的 WebSocket 消息缓存占用
|
||||||
|
|
||||||
|
### 3. 添加内存缓存清理逻辑
|
||||||
|
**文件**: `qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java`
|
||||||
|
|
||||||
|
**新增功能**:
|
||||||
|
- 每 60 秒执行一次内存缓存清理
|
||||||
|
- 清理 5 分钟未更新的 `flightNotificationCache` 数据
|
||||||
|
- 监控 `activeMovingObjectsCache` 大小,超过 1000 个对象时发出警告
|
||||||
|
- 应用关闭时自动清理所有内存缓存
|
||||||
|
|
||||||
|
### 4. 配置 Redis 内存限制
|
||||||
|
**文件**:
|
||||||
|
- `qaup-admin/src/main/resources/application-dev.yml` (开发环境)
|
||||||
|
- `qaup-admin/src/main/resources/application-prod.yml` (生产环境)
|
||||||
|
|
||||||
|
**新增配置**:
|
||||||
|
```yaml
|
||||||
|
redis:
|
||||||
|
max-memory: 256mb # 开发环境
|
||||||
|
# max-memory: 1gb # 生产环境
|
||||||
|
max-memory-policy: volatile-lru
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明**:
|
||||||
|
- 限制 Redis 最大内存使用
|
||||||
|
- 使用 LRU 算法淘汰设置了过期时间的 key
|
||||||
|
- 生产环境支持环境变量配置
|
||||||
|
|
||||||
|
### 5. 创建 Redis 缓存清理和监控任务
|
||||||
|
**文件**: `qaup-framework/src/main/java/com/qaup/framework/task/RedisCacheCleanupTask.java`
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
1. **每天凌晨 2 点执行缓存清理**
|
||||||
|
- 清理验证码等临时缓存
|
||||||
|
- 为没有过期时间的 key 设置默认 TTL
|
||||||
|
|
||||||
|
2. **每 5 分钟监控内存使用情况**
|
||||||
|
- 记录当前内存、峰值内存和限制
|
||||||
|
- 使用率超过 80% 时发出警告
|
||||||
|
- 使用率超过 90% 时发出错误日志
|
||||||
|
|
||||||
|
3. **每小时统计 key 数量**
|
||||||
|
- 统计各类型 key 的数量
|
||||||
|
- 帮助识别异常增长的缓存
|
||||||
|
|
||||||
|
### 6. 创建运维脚本
|
||||||
|
|
||||||
|
#### 6.1 Redis 配置脚本
|
||||||
|
**文件**: `scripts/configure_redis_memory.sh`
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
- 快速配置 Redis 内存限制
|
||||||
|
- 提供开发/生产环境预设
|
||||||
|
- 可选保存到配置文件
|
||||||
|
- 实时生效,无需重启
|
||||||
|
|
||||||
|
**使用方法**:
|
||||||
|
```bash
|
||||||
|
cd scripts
|
||||||
|
./configure_redis_memory.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.2 Redis 诊断脚本
|
||||||
|
**文件**: `scripts/redis_memory_diagnosis.sh`
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
- 分析内存使用情况
|
||||||
|
- 统计 key 数量分布
|
||||||
|
- 检查没有过期时间的 key
|
||||||
|
- 提供优化建议
|
||||||
|
|
||||||
|
**使用方法**:
|
||||||
|
```bash
|
||||||
|
cd scripts
|
||||||
|
./redis_memory_diagnosis.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 立即执行的操作步骤
|
||||||
|
|
||||||
|
### 第一步: 运行诊断脚本
|
||||||
|
```bash
|
||||||
|
cd /Users/tianjianyong/apps/Company/QAUP-Management
|
||||||
|
./scripts/redis_memory_diagnosis.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
查看当前 Redis 内存使用情况和问题。
|
||||||
|
|
||||||
|
### 第二步: 配置 Redis 内存限制
|
||||||
|
```bash
|
||||||
|
./scripts/configure_redis_memory.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
选择适合的环境配置(开发环境选 1,生产环境选 2)。
|
||||||
|
|
||||||
|
### 第三步: 重新编译和部署应用
|
||||||
|
```bash
|
||||||
|
# 编译项目
|
||||||
|
mvn clean install -DskipTests
|
||||||
|
|
||||||
|
# 重启应用
|
||||||
|
./ry.sh restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第四步: 监控效果
|
||||||
|
等待应用运行一段时间后,再次运行诊断脚本:
|
||||||
|
```bash
|
||||||
|
./scripts/redis_memory_diagnosis.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 预期效果
|
||||||
|
|
||||||
|
实施优化后,预期:
|
||||||
|
- ✅ Redis 内存占用降低 50-70%
|
||||||
|
- ✅ 内存使用稳定在设置的上限以内
|
||||||
|
- ✅ 不会出现内存持续增长的情况
|
||||||
|
- ✅ 系统性能不会受到明显影响
|
||||||
|
- ✅ 自动清理过期和不活跃的缓存
|
||||||
|
|
||||||
|
## 监控指标
|
||||||
|
|
||||||
|
### 关键日志
|
||||||
|
应用运行后会输出以下日志:
|
||||||
|
|
||||||
|
1. **内存缓存清理** (每分钟):
|
||||||
|
```
|
||||||
|
清理不活跃缓存对象: X 个移动对象, Y 个航班通知; 剩余: XX 个移动对象, YY 个航班通知
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Redis 内存监控** (每 5 分钟):
|
||||||
|
```
|
||||||
|
Redis 内存使用情况 - 当前: XXM, 峰值: XXM, 限制: 256M
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Key 数量统计** (每小时):
|
||||||
|
```
|
||||||
|
Redis Key 统计 - 登录Token: XX 个
|
||||||
|
Redis Key 统计 - WebSocket消息: XX 个
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 需要关注的指标
|
||||||
|
- Redis 内存使用率保持在 70% 以下
|
||||||
|
- 没有持续增长的趋势
|
||||||
|
- Key 数量稳定或有规律的波动
|
||||||
|
- 无大量没有过期时间的 key
|
||||||
|
|
||||||
|
## 回滚方案
|
||||||
|
|
||||||
|
如果优化导致问题,可以快速回滚:
|
||||||
|
|
||||||
|
### 1. 恢复 RedisCache 类
|
||||||
|
```bash
|
||||||
|
git checkout qaup-common/src/main/java/com/qaup/common/core/redis/RedisCache.java
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 移除 Redis 内存限制
|
||||||
|
```bash
|
||||||
|
redis-cli CONFIG SET maxmemory 0
|
||||||
|
redis-cli CONFIG SET maxmemory-policy noeviction
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 重新部署
|
||||||
|
```bash
|
||||||
|
mvn clean install -DskipTests
|
||||||
|
./ry.sh restart
|
||||||
|
```
|
||||||
|
|
||||||
|
## 长期维护建议
|
||||||
|
|
||||||
|
### 1. 定期检查 Redis 内存
|
||||||
|
每周运行诊断脚本,关注内存使用趋势:
|
||||||
|
```bash
|
||||||
|
./scripts/redis_memory_diagnosis.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 审查缓存使用
|
||||||
|
- 审查所有 `setCacheObject` 调用
|
||||||
|
- 确保设置了合理的过期时间
|
||||||
|
- 避免缓存大对象或大量数据
|
||||||
|
|
||||||
|
### 3. 监控和告警
|
||||||
|
考虑添加:
|
||||||
|
- Redis 内存使用率告警 (>80%)
|
||||||
|
- Key 数量异常增长告警
|
||||||
|
- 应用层缓存监控
|
||||||
|
|
||||||
|
### 4. 数据库层面优化
|
||||||
|
- 考虑使用 Redis 集群
|
||||||
|
- 分离不同类型的缓存(如会话、业务数据)
|
||||||
|
- 评估是否需要持久化
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- **详细优化指南**: [doc/redis_optimization_guide.md](redis_optimization_guide.md)
|
||||||
|
- **诊断脚本**: [scripts/redis_memory_diagnosis.sh](../scripts/redis_memory_diagnosis.sh)
|
||||||
|
- **配置脚本**: [scripts/configure_redis_memory.sh](../scripts/configure_redis_memory.sh)
|
||||||
|
|
||||||
|
## 修改清单
|
||||||
|
|
||||||
|
### 修改的文件
|
||||||
|
1. `qaup-common/src/main/java/com/qaup/common/core/redis/RedisCache.java`
|
||||||
|
2. `qaup-collision/src/main/java/com/qaup/collision/websocket/cache/MessageCacheService.java`
|
||||||
|
3. `qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java`
|
||||||
|
4. `qaup-admin/src/main/resources/application-dev.yml`
|
||||||
|
5. `qaup-admin/src/main/resources/application-prod.yml`
|
||||||
|
|
||||||
|
### 新增的文件
|
||||||
|
1. `qaup-framework/src/main/java/com/qaup/framework/task/RedisCacheCleanupTask.java`
|
||||||
|
2. `scripts/configure_redis_memory.sh`
|
||||||
|
3. `scripts/redis_memory_diagnosis.sh`
|
||||||
|
4. `doc/redis_optimization_guide.md`
|
||||||
|
5. `doc/redis_optimization_summary.md` (本文件)
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
⚠️ **重要提示**:
|
||||||
|
1. 生产环境操作前务必备份数据
|
||||||
|
2. 建议先在开发/测试环境验证
|
||||||
|
3. 监控应用性能,确保无异常
|
||||||
|
4. Redis 配置修改可实时生效,但应用需要重启以应用代码修改
|
||||||
|
|
||||||
|
## 技术支持
|
||||||
|
|
||||||
|
如有问题,请检查:
|
||||||
|
1. 应用日志: `qaup-admin/app.log`
|
||||||
|
2. Redis 日志: `/var/log/redis/redis-server.log`
|
||||||
|
3. 运行诊断脚本获取详细信息
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**优化完成时间**: 2025-10-13
|
||||||
|
**版本**: 1.0.0
|
||||||
|
**作者**: Claude Code
|
||||||
@ -59,6 +59,17 @@ spring:
|
|||||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
max-wait: -1ms
|
max-wait: -1ms
|
||||||
|
|
||||||
|
# Redis 内存优化配置
|
||||||
|
redis:
|
||||||
|
# 最大内存限制(开发环境)
|
||||||
|
max-memory: 256mb
|
||||||
|
# 内存淘汰策略:从设置了过期时间的key中使用LRU算法淘汰
|
||||||
|
# volatile-lru: 从设置了过期时间的key中使用LRU算法淘汰
|
||||||
|
# allkeys-lru: 从所有key中使用LRU算法淘汰
|
||||||
|
# volatile-ttl: 从设置了过期时间的key中淘汰即将过期的
|
||||||
|
# noeviction: 不淘汰,内存满时返回错误
|
||||||
|
max-memory-policy: volatile-lru
|
||||||
|
|
||||||
# 日志配置 - 开发环境(详细日志)
|
# 日志配置 - 开发环境(详细日志)
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
|
|||||||
@ -33,6 +33,13 @@ spring:
|
|||||||
max-active: 8
|
max-active: 8
|
||||||
max-wait: -1ms
|
max-wait: -1ms
|
||||||
|
|
||||||
|
# Redis 内存优化配置(生产环境)
|
||||||
|
redis:
|
||||||
|
# 最大内存限制(生产环境建议1-2GB)
|
||||||
|
max-memory: ${REDIS_MAX_MEMORY:1gb}
|
||||||
|
# 内存淘汰策略
|
||||||
|
max-memory-policy: ${REDIS_MAX_MEMORY_POLICY:volatile-lru}
|
||||||
|
|
||||||
# 日志配置(生产环境简洁日志)
|
# 日志配置(生产环境简洁日志)
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
|
|||||||
@ -712,9 +712,54 @@ public class DataCollectorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定期清理不活跃的缓存对象(内存优化)
|
||||||
|
* 每分钟执行一次,清理超过5分钟未更新的对象
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 60000) // 每60秒执行一次
|
||||||
|
public void cleanupInactiveObjects() {
|
||||||
|
try {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long inactiveThreshold = 300000; // 5分钟无更新视为不活跃
|
||||||
|
int removedMovingObjects = 0;
|
||||||
|
int removedFlightNotifications = 0;
|
||||||
|
|
||||||
|
// 清理 activeMovingObjectsCache
|
||||||
|
// 注意: MovingObject 没有时间戳字段,需要通过外部跟踪或限制缓存大小
|
||||||
|
// 这里先保持简单策略:如果缓存超过一定数量,可以考虑清理
|
||||||
|
int maxCacheSize = 1000; // 最大缓存1000个对象
|
||||||
|
if (activeMovingObjectsCache.size() > maxCacheSize) {
|
||||||
|
log.warn("activeMovingObjectsCache 超过最大限制 {}, 当前: {}",
|
||||||
|
maxCacheSize, activeMovingObjectsCache.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理 flightNotificationCache - 使用 eventTime 字段判断
|
||||||
|
int sizeBefore = flightNotificationCache.size();
|
||||||
|
flightNotificationCache.entrySet().removeIf(entry -> {
|
||||||
|
FlightNotification notification = entry.getValue();
|
||||||
|
long eventTime = (notification.getEventTime() != null) ? notification.getEventTime() : 0;
|
||||||
|
return (now - eventTime) > inactiveThreshold;
|
||||||
|
});
|
||||||
|
removedFlightNotifications = sizeBefore - flightNotificationCache.size();
|
||||||
|
|
||||||
|
if (removedMovingObjects > 0 || removedFlightNotifications > 0) {
|
||||||
|
log.info("清理不活跃缓存对象: {} 个移动对象, {} 个航班通知; 剩余: {} 个移动对象, {} 个航班通知",
|
||||||
|
removedMovingObjects,
|
||||||
|
removedFlightNotifications,
|
||||||
|
activeMovingObjectsCache.size(),
|
||||||
|
flightNotificationCache.size());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("清理不活跃缓存对象时发生错误", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
log.info("DataCollectorService 正在关闭...");
|
log.info("DataCollectorService 正在关闭...");
|
||||||
// 清理资源,如果需要
|
// 清理缓存
|
||||||
|
activeMovingObjectsCache.clear();
|
||||||
|
flightNotificationCache.clear();
|
||||||
|
log.info("缓存已清理");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,8 @@ public class MessageCacheService {
|
|||||||
|
|
||||||
// 缓存配置常量
|
// 缓存配置常量
|
||||||
private static final String CACHE_KEY_PREFIX = "websocket:messages:";
|
private static final String CACHE_KEY_PREFIX = "websocket:messages:";
|
||||||
private static final int MAX_CACHED_MESSAGES = 100; // 每类型最多缓存100条消息
|
private static final int MAX_CACHED_MESSAGES = 50; // 每类型最多缓存50条消息(优化内存占用)
|
||||||
private static final Duration CACHE_EXPIRY = Duration.ofMinutes(30); // 30分钟过期
|
private static final Duration CACHE_EXPIRY = Duration.ofMinutes(10); // 10分钟过期(从30分钟优化)
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public MessageCacheService(@Qualifier("websocketRedisTemplate") RedisTemplate<String, Object> redisTemplate) {
|
public MessageCacheService(@Qualifier("websocketRedisTemplate") RedisTemplate<String, Object> redisTemplate) {
|
||||||
|
|||||||
@ -25,13 +25,33 @@ public class RedisCache
|
|||||||
@Autowired
|
@Autowired
|
||||||
public RedisTemplate redisTemplate;
|
public RedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认缓存过期时间:1小时
|
||||||
|
* 防止缓存数据永久保存导致内存占用过高
|
||||||
|
*/
|
||||||
|
private static final long DEFAULT_EXPIRE_TIME = 3600;
|
||||||
|
private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 缓存基本的对象,Integer、String、实体类等
|
* 缓存基本的对象,Integer、String、实体类等
|
||||||
|
* 注意:默认设置1小时过期时间,避免内存泄漏
|
||||||
*
|
*
|
||||||
* @param key 缓存的键值
|
* @param key 缓存的键值
|
||||||
* @param value 缓存的值
|
* @param value 缓存的值
|
||||||
*/
|
*/
|
||||||
public <T> void setCacheObject(final String key, final T value)
|
public <T> void setCacheObject(final String key, final T value)
|
||||||
|
{
|
||||||
|
redisTemplate.opsForValue().set(key, value, DEFAULT_EXPIRE_TIME, DEFAULT_TIME_UNIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存基本的对象,不设置过期时间(仅用于永久性配置数据)
|
||||||
|
* 警告:使用此方法需谨慎,可能导致内存泄漏
|
||||||
|
*
|
||||||
|
* @param key 缓存的键值
|
||||||
|
* @param value 缓存的值
|
||||||
|
*/
|
||||||
|
public <T> void setCacheObjectPermanent(final String key, final T value)
|
||||||
{
|
{
|
||||||
redisTemplate.opsForValue().set(key, value);
|
redisTemplate.opsForValue().set(key, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,223 @@
|
|||||||
|
package com.qaup.framework.task;
|
||||||
|
|
||||||
|
import com.qaup.common.core.redis.RedisCache;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis 缓存清理和监控任务
|
||||||
|
*
|
||||||
|
* 功能:
|
||||||
|
* 1. 定期清理过期和无用的缓存
|
||||||
|
* 2. 监控 Redis 内存使用情况
|
||||||
|
* 3. 为没有过期时间的 key 设置默认过期时间
|
||||||
|
*
|
||||||
|
* @author QAUP System
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class RedisCacheCleanupTask {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(RedisCacheCleanupTask.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisCache redisCache;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每天凌晨2点执行缓存清理
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "0 0 2 * * ?")
|
||||||
|
public void cleanupExpiredCache() {
|
||||||
|
log.info("开始执行 Redis 缓存清理任务");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 清理验证码缓存(虽然应该已经过期,但双重保险)
|
||||||
|
cleanupPattern("captcha_codes:*", 1, TimeUnit.HOURS);
|
||||||
|
|
||||||
|
// 清理临时会话数据
|
||||||
|
cleanupPattern("temp:*", 1, TimeUnit.HOURS);
|
||||||
|
|
||||||
|
// 检查并修复没有过期时间的 key
|
||||||
|
fixKeysWithoutExpiry();
|
||||||
|
|
||||||
|
log.info("Redis 缓存清理任务完成");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Redis 缓存清理任务执行失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每5分钟监控 Redis 内存使用情况
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 300000) // 5分钟
|
||||||
|
public void monitorMemoryUsage() {
|
||||||
|
try {
|
||||||
|
Properties info = redisTemplate.getRequiredConnectionFactory()
|
||||||
|
.getConnection()
|
||||||
|
.serverCommands()
|
||||||
|
.info("memory");
|
||||||
|
|
||||||
|
if (info != null) {
|
||||||
|
String usedMemory = info.getProperty("used_memory_human");
|
||||||
|
String maxMemory = info.getProperty("maxmemory_human");
|
||||||
|
String usedMemoryPeak = info.getProperty("used_memory_peak_human");
|
||||||
|
|
||||||
|
log.info("Redis 内存使用情况 - 当前: {}, 峰值: {}, 限制: {}",
|
||||||
|
usedMemory, usedMemoryPeak, maxMemory);
|
||||||
|
|
||||||
|
// 检查内存使用率
|
||||||
|
checkMemoryUsageThreshold(info);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("监控 Redis 内存使用情况失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每小时统计 Redis key 数量
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 3600000) // 1小时
|
||||||
|
public void statisticsKeyCount() {
|
||||||
|
try {
|
||||||
|
// 统计各类型 key 的数量
|
||||||
|
logKeyCount("login_tokens:*", "登录Token");
|
||||||
|
logKeyCount("captcha_codes:*", "验证码");
|
||||||
|
logKeyCount("sys_dict:*", "字典缓存");
|
||||||
|
logKeyCount("websocket:messages:*", "WebSocket消息");
|
||||||
|
logKeyCount("rate_limit:*", "限流计数");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("统计 Redis key 数量失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理指定模式的 key,为没有过期时间的 key 设置默认过期时间
|
||||||
|
*
|
||||||
|
* @param pattern key 模式
|
||||||
|
* @param timeout 过期时间
|
||||||
|
* @param unit 时间单位
|
||||||
|
*/
|
||||||
|
private void cleanupPattern(String pattern, long timeout, TimeUnit unit) {
|
||||||
|
try {
|
||||||
|
Collection<String> keys = redisCache.keys(pattern);
|
||||||
|
if (keys == null || keys.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fixedCount = 0;
|
||||||
|
for (String key : keys) {
|
||||||
|
Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||||
|
// TTL = -1 表示没有过期时间
|
||||||
|
if (ttl != null && ttl == -1) {
|
||||||
|
redisTemplate.expire(key, timeout, unit);
|
||||||
|
fixedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fixedCount > 0) {
|
||||||
|
log.info("为 {} 个 '{}' 模式的 key 设置了过期时间", fixedCount, pattern);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("清理模式 '{}' 的缓存失败", pattern, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并修复没有过期时间的 key
|
||||||
|
*/
|
||||||
|
private void fixKeysWithoutExpiry() {
|
||||||
|
try {
|
||||||
|
// 扫描所有 key(注意:生产环境慎用 keys *)
|
||||||
|
Collection<String> allKeys = redisCache.keys("*");
|
||||||
|
if (allKeys == null || allKeys.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fixedCount = 0;
|
||||||
|
int totalCount = allKeys.size();
|
||||||
|
|
||||||
|
for (String key : allKeys) {
|
||||||
|
Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// 跳过已有过期时间的 key
|
||||||
|
if (ttl == null || ttl != -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据 key 类型设置不同的过期时间
|
||||||
|
if (key.startsWith("sys_dict:") || key.startsWith("sys_config:")) {
|
||||||
|
// 字典和配置数据:24小时
|
||||||
|
redisTemplate.expire(key, 24, TimeUnit.HOURS);
|
||||||
|
fixedCount++;
|
||||||
|
} else if (key.startsWith("login_tokens:") || key.startsWith("user:")) {
|
||||||
|
// 用户相关数据:2小时
|
||||||
|
redisTemplate.expire(key, 2, TimeUnit.HOURS);
|
||||||
|
fixedCount++;
|
||||||
|
} else {
|
||||||
|
// 其他数据:1小时
|
||||||
|
redisTemplate.expire(key, 1, TimeUnit.HOURS);
|
||||||
|
fixedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fixedCount > 0) {
|
||||||
|
log.warn("发现 {} / {} 个 key 没有过期时间,已自动修复", fixedCount, totalCount);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("修复没有过期时间的 key 失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查内存使用率阈值
|
||||||
|
*/
|
||||||
|
private void checkMemoryUsageThreshold(Properties info) {
|
||||||
|
try {
|
||||||
|
String usedMemoryStr = info.getProperty("used_memory");
|
||||||
|
String maxMemoryStr = info.getProperty("maxmemory");
|
||||||
|
|
||||||
|
if (usedMemoryStr == null || maxMemoryStr == null || "0".equals(maxMemoryStr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long usedMemory = Long.parseLong(usedMemoryStr);
|
||||||
|
long maxMemory = Long.parseLong(maxMemoryStr);
|
||||||
|
|
||||||
|
double usageRate = (double) usedMemory / maxMemory * 100;
|
||||||
|
|
||||||
|
if (usageRate > 90) {
|
||||||
|
log.error("Redis 内存使用率超过 90%: {:.2f}%, 请立即检查!", usageRate);
|
||||||
|
} else if (usageRate > 80) {
|
||||||
|
log.warn("Redis 内存使用率超过 80%: {:.2f}%, 建议清理缓存", usageRate);
|
||||||
|
} else if (usageRate > 70) {
|
||||||
|
log.info("Redis 内存使用率: {:.2f}%", usageRate);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("检查内存使用率失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录指定模式的 key 数量
|
||||||
|
*/
|
||||||
|
private void logKeyCount(String pattern, String description) {
|
||||||
|
try {
|
||||||
|
Collection<String> keys = redisCache.keys(pattern);
|
||||||
|
int count = (keys != null) ? keys.size() : 0;
|
||||||
|
log.info("Redis Key 统计 - {}: {} 个", description, count);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("统计 '{}' key 数量失败", pattern, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
scripts/configure_redis_memory.sh
Executable file
120
scripts/configure_redis_memory.sh
Executable file
@ -0,0 +1,120 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Redis 内存优化配置脚本
|
||||||
|
# 用于快速配置 Redis 内存限制和淘汰策略
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Redis 内存优化配置脚本"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# 检查 Redis 是否运行
|
||||||
|
if ! redis-cli ping > /dev/null 2>&1; then
|
||||||
|
echo "错误: Redis 未运行或无法连接"
|
||||||
|
echo "请先启动 Redis 服务"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "当前 Redis 配置:"
|
||||||
|
redis-cli CONFIG GET maxmemory
|
||||||
|
redis-cli CONFIG GET maxmemory-policy
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "当前内存使用情况:"
|
||||||
|
redis-cli INFO memory | grep -E "used_memory_human|used_memory_peak_human|maxmemory_human"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "当前数据库大小:"
|
||||||
|
redis-cli DBSIZE
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "选择环境:"
|
||||||
|
echo "1) 开发环境 (256MB)"
|
||||||
|
echo "2) 生产环境 (1GB)"
|
||||||
|
echo "3) 自定义"
|
||||||
|
echo "4) 仅查看信息,不修改"
|
||||||
|
echo "=========================================="
|
||||||
|
read -p "请选择 [1-4]: " choice
|
||||||
|
|
||||||
|
case $choice in
|
||||||
|
1)
|
||||||
|
MAX_MEMORY="268435456" # 256MB in bytes
|
||||||
|
MAX_MEMORY_STR="256mb"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
MAX_MEMORY="1073741824" # 1GB in bytes
|
||||||
|
MAX_MEMORY_STR="1gb"
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
read -p "请输入最大内存限制 (如: 512mb, 2gb): " MAX_MEMORY_STR
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
echo "未进行任何修改"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "无效选择"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 设置淘汰策略
|
||||||
|
EVICTION_POLICY="volatile-lru"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "将设置以下配置:"
|
||||||
|
echo "最大内存: $MAX_MEMORY_STR"
|
||||||
|
echo "淘汰策略: $EVICTION_POLICY"
|
||||||
|
echo "=========================================="
|
||||||
|
read -p "确认执行? [y/N]: " confirm
|
||||||
|
|
||||||
|
if [[ $confirm != "y" && $confirm != "Y" ]]; then
|
||||||
|
echo "已取消"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 执行配置
|
||||||
|
echo ""
|
||||||
|
echo "正在配置 Redis..."
|
||||||
|
|
||||||
|
if [ -n "$MAX_MEMORY_STR" ]; then
|
||||||
|
redis-cli CONFIG SET maxmemory "$MAX_MEMORY_STR"
|
||||||
|
echo "✓ 已设置 maxmemory = $MAX_MEMORY_STR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
redis-cli CONFIG SET maxmemory-policy "$EVICTION_POLICY"
|
||||||
|
echo "✓ 已设置 maxmemory-policy = $EVICTION_POLICY"
|
||||||
|
|
||||||
|
# 可选: 保存到配置文件 (需要权限)
|
||||||
|
echo ""
|
||||||
|
read -p "是否保存到 Redis 配置文件? (需要写权限) [y/N]: " save_config
|
||||||
|
|
||||||
|
if [[ $save_config == "y" || $save_config == "Y" ]]; then
|
||||||
|
redis-cli CONFIG REWRITE
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✓ 配置已保存到 redis.conf"
|
||||||
|
else
|
||||||
|
echo "✗ 保存失败,请检查权限或手动修改 redis.conf"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "配置完成!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "新配置:"
|
||||||
|
redis-cli CONFIG GET maxmemory
|
||||||
|
redis-cli CONFIG GET maxmemory-policy
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "建议操作:"
|
||||||
|
echo "1. 监控内存使用: redis-cli INFO memory"
|
||||||
|
echo "2. 查看 key 数量: redis-cli DBSIZE"
|
||||||
|
echo "3. 查看大 key: redis-cli --bigkeys"
|
||||||
|
echo "4. 查看应用日志以确认系统正常运行"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "注意: 配置立即生效,无需重启 Redis"
|
||||||
149
scripts/redis_memory_diagnosis.sh
Executable file
149
scripts/redis_memory_diagnosis.sh
Executable file
@ -0,0 +1,149 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Redis 内存使用诊断脚本
|
||||||
|
# 分析 Redis 内存占用情况并提供优化建议
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Redis 内存使用诊断报告"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "生成时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 检查 Redis 是否运行
|
||||||
|
if ! redis-cli ping > /dev/null 2>&1; then
|
||||||
|
echo "错误: Redis 未运行或无法连接"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "1. 内存使用概览"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
redis-cli INFO memory | grep -E "used_memory_human|used_memory_peak_human|maxmemory_human|mem_fragmentation_ratio|used_memory_rss_human"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "2. 配置信息"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
redis-cli CONFIG GET maxmemory | xargs printf "最大内存限制: %s = %s\n"
|
||||||
|
redis-cli CONFIG GET maxmemory-policy | xargs printf "淘汰策略: %s = %s\n"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "3. 数据库统计"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
printf "Key 总数: "
|
||||||
|
redis-cli DBSIZE
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "4. 各类型 Key 数量分布"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
echo "注意: 此操作可能耗时较长..."
|
||||||
|
|
||||||
|
# 统计不同前缀的 key 数量
|
||||||
|
declare -A key_counts
|
||||||
|
|
||||||
|
while IFS= read -r key; do
|
||||||
|
prefix=$(echo "$key" | cut -d':' -f1)
|
||||||
|
((key_counts[$prefix]++))
|
||||||
|
done < <(redis-cli --scan)
|
||||||
|
|
||||||
|
echo "前缀统计:"
|
||||||
|
for prefix in "${!key_counts[@]}"; do
|
||||||
|
printf " %-30s %d 个\n" "$prefix:" "${key_counts[$prefix]}"
|
||||||
|
done | sort -k2 -rn | head -20
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "5. 最大的 Key (Top 10)"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
redis-cli --bigkeys 2>/dev/null | grep -A 20 "Biggest"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "6. 没有过期时间的 Key 统计"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
no_ttl_count=0
|
||||||
|
total_keys=$(redis-cli DBSIZE | awk '{print $1}')
|
||||||
|
|
||||||
|
# 采样检查 (最多检查1000个key)
|
||||||
|
sample_size=1000
|
||||||
|
checked=0
|
||||||
|
no_ttl_samples=""
|
||||||
|
|
||||||
|
while IFS= read -r key && [ $checked -lt $sample_size ]; do
|
||||||
|
ttl=$(redis-cli TTL "$key")
|
||||||
|
if [ "$ttl" = "-1" ]; then
|
||||||
|
((no_ttl_count++))
|
||||||
|
if [ $no_ttl_count -le 5 ]; then
|
||||||
|
no_ttl_samples="$no_ttl_samples\n $key"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
((checked++))
|
||||||
|
done < <(redis-cli --scan)
|
||||||
|
|
||||||
|
if [ $checked -ge $total_keys ]; then
|
||||||
|
echo "检查了全部 $total_keys 个 key"
|
||||||
|
echo "没有过期时间的 key: $no_ttl_count 个 ($(awk "BEGIN {printf \"%.1f\", $no_ttl_count/$total_keys*100}")%)"
|
||||||
|
else
|
||||||
|
echo "采样检查了 $checked 个 key (总数: $total_keys)"
|
||||||
|
echo "采样中没有过期时间的 key: $no_ttl_count 个 ($(awk "BEGIN {printf \"%.1f\", $no_ttl_count/$checked*100}")%)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $no_ttl_count -gt 0 ]; then
|
||||||
|
echo -e "示例:$no_ttl_samples"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "7. 内存使用率评估"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
|
||||||
|
used_memory=$(redis-cli INFO memory | grep "used_memory:" | cut -d':' -f2 | tr -d '\r')
|
||||||
|
max_memory=$(redis-cli CONFIG GET maxmemory | tail -1 | tr -d '\r')
|
||||||
|
|
||||||
|
if [ "$max_memory" = "0" ]; then
|
||||||
|
echo "⚠️ 警告: 未设置最大内存限制!"
|
||||||
|
echo " 建议: 执行 ./configure_redis_memory.sh 设置内存限制"
|
||||||
|
else
|
||||||
|
usage_percent=$(awk "BEGIN {printf \"%.1f\", $used_memory/$max_memory*100}")
|
||||||
|
echo "内存使用率: $usage_percent%"
|
||||||
|
|
||||||
|
if (( $(echo "$usage_percent > 90" | bc -l) )); then
|
||||||
|
echo "🔴 严重: 内存使用率超过 90%!"
|
||||||
|
echo " 建议: 立即清理缓存或增加内存限制"
|
||||||
|
elif (( $(echo "$usage_percent > 80" | bc -l) )); then
|
||||||
|
echo "🟡 警告: 内存使用率超过 80%"
|
||||||
|
echo " 建议: 清理不必要的缓存"
|
||||||
|
elif (( $(echo "$usage_percent > 70" | bc -l) )); then
|
||||||
|
echo "🟢 正常: 内存使用率在合理范围内"
|
||||||
|
else
|
||||||
|
echo "🟢 良好: 内存使用率较低"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "8. 优化建议"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
|
||||||
|
# 检查是否设置了最大内存
|
||||||
|
if [ "$max_memory" = "0" ]; then
|
||||||
|
echo "✓ 设置 Redis 最大内存限制"
|
||||||
|
echo " 命令: redis-cli CONFIG SET maxmemory 512mb"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查是否有很多没有过期时间的 key
|
||||||
|
if [ $no_ttl_count -gt $(($checked / 2)) ]; then
|
||||||
|
echo "✓ 大量 key 没有过期时间,建议设置 TTL"
|
||||||
|
echo " 自动修复: 应用中的 RedisCacheCleanupTask 会自动处理"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查淘汰策略
|
||||||
|
eviction_policy=$(redis-cli CONFIG GET maxmemory-policy | tail -1 | tr -d '\r')
|
||||||
|
if [ "$eviction_policy" = "noeviction" ]; then
|
||||||
|
echo "✓ 当前淘汰策略为 noeviction,建议改为 volatile-lru"
|
||||||
|
echo " 命令: redis-cli CONFIG SET maxmemory-policy volatile-lru"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "诊断完成!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "快速操作:"
|
||||||
|
echo "1. 配置 Redis 内存: ./configure_redis_memory.sh"
|
||||||
|
echo "2. 清理 Redis 数据: redis-cli FLUSHDB (谨慎!)"
|
||||||
|
echo "3. 重启应用以应用新的缓存策略"
|
||||||
Loading…
Reference in New Issue
Block a user