QDAirPortBackend0122/doc/work/redis_optimization_guide.md
2026-01-22 13:19:47 +08:00

7.4 KiB
Raw Blame History

Redis 内存优化指南

问题诊断

1. 检查当前 Redis 内存使用情况

# 连接到 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添加默认过期时间:

// 建议配置
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但需要定期清理过期数据:

// 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 消息缓存大小和时间

// 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 配置:

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:

# 设置最大内存 (512MB)
maxmemory 512mb

# 设置淘汰策略
maxmemory-policy volatile-lru

# 启用持久化优化
save ""  # 关闭 RDB减少内存峰值
appendonly no  # 如果不需要持久化,可以关闭 AOF

应用配置后重启 Redis:

# macOS (如果使用 Homebrew)
brew services restart redis

# 或直接使用配置文件启动
redis-server /usr/local/etc/redis.conf

方案 4: 优化具体缓存使用

4.1 Token 缓存优化

// TokenService.java - 确保 token 设置了过期时间
int expireTime = 30; // 30分钟
redisCache.setCacheObject(tokenKey, loginUser, expireTime, TimeUnit.MINUTES);

4.2 字典缓存优化

// DictUtils.java - 字典数据可以设置较长过期时间
public static void setDictCache(String key, List<SysDictData> dictDatas) {
    redisCache.setCacheObject(getCacheKey(key), dictDatas, 24, TimeUnit.HOURS);
}

4.3 限流计数器优化

// RateLimiterAspect.java - 限流计数器确保有过期时间
// Lua 脚本已经处理,无需修改

方案 5: 添加定期清理任务

创建定时任务清理过期或无用的缓存:

@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):
redis-cli CONFIG SET maxmemory 512mb
redis-cli CONFIG SET maxmemory-policy volatile-lru
  1. 清理当前 Redis 数据 (谨慎操作):
# 查看当前内存使用
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 监控

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