273 lines
7.4 KiB
Markdown
273 lines
7.4 KiB
Markdown
# 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. **监控内存使用情况,及时调整策略**
|