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