7.4 KiB
7.4 KiB
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() {
// 实现逻辑
}
}
实施步骤
立即执行 (紧急)
- 设置 Redis 内存限制 (防止 OOM):
redis-cli CONFIG SET maxmemory 512mb
redis-cli CONFIG SET maxmemory-policy volatile-lru
- 清理当前 Redis 数据 (谨慎操作):
# 查看当前内存使用
redis-cli INFO memory
# 清理所有数据 (注意: 会清空所有缓存)
redis-cli FLUSHDB
# 或者只清理特定模式的 key
redis-cli --scan --pattern 'websocket:messages:*' | xargs redis-cli DEL
短期优化 (1-2天)
- 修改
RedisCache.java添加默认过期时间 - 优化
MessageCacheService.java缓存参数 - 添加 Redis 配置到
application.yml - 为
activeMovingObjectsCache添加清理逻辑
长期优化 (1周内)
- 创建
RedisCacheCleanupTask定时清理任务 - 审查所有
setCacheObject调用,确保设置了过期时间 - 实施 Redis 监控和告警
- 考虑使用 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%
- 内存使用稳定在设置的上限以内
- 不会出现内存持续增长的情况
- 系统性能不会受到明显影响
注意事项
- 生产环境操作前务必备份
- Redis 配置修改需要重启服务
- FLUSHDB 会清空所有缓存,可能导致短暂性能下降
- 监控内存使用情况,及时调整策略