Redis 7 内存优化详解

核心概念

Redis 作为内存数据库,内存优化是生产环境中的核心关注点。Redis 7 提供了多种内存优化策略,包括数据结构优化、编码方式选择、内存淘汰策略、内存碎片整理等手段,帮助在有限内存下存储更多数据并保持高性能。

内存优化的关键维度

  • 数据结构选择:选择合适的底层编码方式
  • 内存淘汰策略:合理配置过期键清理机制
  • 内存碎片管理:减少内存浪费
  • 持久化优化:平衡内存占用与数据安全

数据结构层面优化

1. 压缩列表与紧凑编码

@Configuration
public class RedisMemoryOptimization {
  
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory factory) {
      
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
      
        // 配置序列化方式,减少内存占用
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
      
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL);
      
        serializer.setObjectMapper(mapper);
      
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);
      
        return template;
    }
}

2. Redis 配置优化

# redis.conf 内存优化配置

# Hash 类型优化 - 使用 ziplist 编码
hash-max-ziplist-entries 512
hash-max-ziplist-value 64

# List 类型优化 - 使用 quicklist
list-max-ziplist-size -2
list-compress-depth 0

# Set 类型优化 - 小集合使用 intset
set-max-intset-entries 512

# ZSet 类型优化 - 使用 ziplist
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

# String 类型优化 - 共享整数对象
maxmemory-policy allkeys-lru

3. 数据结构选择策略

@Service
public class MemoryEfficientRedisService {
  
    @Autowired
    private StringRedisTemplate redisTemplate;
  
    // 优化1:使用 Hash 代替多个 String key
    // 不推荐:user:1001:name, user:1001:age, user:1001:email
    // 推荐:使用 Hash 存储
    public void saveUserOptimized(String userId, Map<String, String> userInfo) {
        String key = "user:" + userId;
        redisTemplate.opsForHash().putAll(key, userInfo);
        // 内存占用:Hash < 多个 String key
    }
  
    // 优化2:使用位图存储布尔值
    public void recordUserLogin(String date, long userId) {
        String key = "login:" + date;
        redisTemplate.opsForValue().setBit(key, userId, true);
        // 1亿用户仅需 12MB 内存
    }
  
    // 优化3:使用 HyperLogLog 统计基数
    public void addUniqueVisitor(String pageId, String userId) {
        String key = "uv:" + pageId;
        redisTemplate.opsForHyperLogLog().add(key, userId);
        // 统计百万级UV仅需 12KB 内存
    }
  
    // 优化4:合理使用 ZSet 的 score
    public void addToLeaderboard(String leaderboardKey, String userId, double score) {
        redisTemplate.opsForZSet().add(leaderboardKey, userId, score);
        // 避免存储冗余数据,score 可以是时间戳或分数
    }
}

内存淘汰策略

1. 淘汰策略配置

@Component
public class RedisEvictionPolicy {
  
    /**
     * Redis 内存淘汰策略
     * 
     * noeviction: 不淘汰,内存满时返回错误
     * allkeys-lru: 所有key中淘汰最近最少使用
     * allkeys-lfu: 所有key中淘汰最不经常使用(Redis 4.0+)
     * allkeys-random: 所有key中随机淘汰
     * volatile-lru: 设置过期时间的key中淘汰LRU
     * volatile-lfu: 设置过期时间的key中淘汰LFU
     * volatile-random: 设置过期时间的key中随机淘汰
     * volatile-ttl: 淘汰即将过期的key
     */
  
    @Value("${spring.redis.maxmemory:2gb}")
    private String maxMemory;
  
    @Value("${spring.redis.maxmemory-policy:allkeys-lru}")
    private String maxMemoryPolicy;
  
    @PostConstruct
    public void configureEviction() {
        // 通过 CONFIG SET 动态配置
        redisTemplate.execute((RedisCallback<Void>) connection -> {
            connection.setConfig("maxmemory", maxMemory);
            connection.setConfig("maxmemory-policy", maxMemoryPolicy);
            return null;
        });
    }
}

2. 过期键清理优化

@Service
public class ExpirationOptimization {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    // 优化1:批量设置过期时间
    public void batchSetExpire(Map<String, Object> data, long seconds) {
        redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                data.forEach((key, value) -> {
                    operations.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
                });
                return null;
            }
        });
    }
  
    // 优化2:使用随机过期时间避免缓存雪崩
    public void setWithRandomExpire(String key, Object value, long baseSeconds) {
        Random random = new Random();
        long randomSeconds = baseSeconds + random.nextInt(300); // 加5分钟随机值
        redisTemplate.opsForValue().set(key, value, randomSeconds, TimeUnit.SECONDS);
    }
  
    // 优化3:惰性删除 + 定期删除
    public void configureExpireStrategy() {
        redisTemplate.execute((RedisCallback<Void>) connection -> {
            // 设置定期删除频率(默认10次/秒)
            connection.setConfig("hz", "10");
            return null;
        });
    }
}

内存碎片管理

1. 碎片整理配置

@Component
public class MemoryDefragmentation {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    // 主动触发内存碎片整理
    public void defragmentMemory() {
        redisTemplate.execute((RedisCallback<String>) connection -> {
            // Redis 4.0+ 支持
            return connection.execute("MEMORY", "PURGE".getBytes());
        });
    }
  
    // 配置自动碎片整理
    public void configureAutoDefrag() {
        redisTemplate.execute((RedisCallback<Void>) connection -> {
            // 启用自动碎片整理
            connection.setConfig("activedefrag", "yes");
          
            // 碎片率达到 10% 时触发
            connection.setConfig("active-defrag-threshold-lower", "10");
          
            // 碎片率达到 100% 时强制整理
            connection.setConfig("active-defrag-threshold-upper", "100");
          
            // 最小碎片字节数
            connection.setConfig("active-defrag-ignore-bytes", "100mb");
          
            // CPU 使用率限制
            connection.setConfig("active-defrag-cycle-min", "5");
            connection.setConfig("active-defrag-cycle-max", "75");
          
            return null;
        });
    }
  
    // 监控内存碎片率
    public double getFragmentationRatio() {
        Map<String, String> info = redisTemplate.execute(
            (RedisCallback<Map<String, String>>) connection -> {
                Properties props = connection.info("memory");
                return props.entrySet().stream()
                    .collect(Collectors.toMap(
                        e -> e.getKey().toString(),
                        e -> e.getValue().toString()
                    ));
            });
      
        long usedMemory = Long.parseLong(info.get("used_memory"));
        long usedMemoryRss = Long.parseLong(info.get("used_memory_rss"));
      
        return (double) usedMemoryRss / usedMemory;
    }
}

性能优化实践

1. Key 命名优化

@Service
public class KeyNamingOptimization {
  
    // 不推荐:过长的 key 名称
    // "application:user:profile:information:detail:1001"
  
    // 推荐:简短且有意义的 key
    // "app:u:prof:1001"
  
    private static final String USER_PREFIX = "u:";
    private static final String PROFILE_PREFIX = "p:";
  
    public String buildOptimizedKey(String userId) {
        // 使用短前缀,减少内存占用
        return USER_PREFIX + PROFILE_PREFIX + userId;
    }
  
    // 使用数字 ID 代替字符串
    public void saveWithNumericId(long userId, Object data) {
        // 数字 ID 比字符串占用更少内存
        String key = "u:" + userId;
        redisTemplate.opsForValue().set(key, data);
    }
}

2. 序列化优化

@Configuration
public class SerializationOptimization {
  
    // 使用高效的序列化方式
    @Bean
    public RedisSerializer<Object> redisSerializer() {
        // 方案1:使用 Protobuf(推荐)
        return new ProtobufRedisSerializer();
      
        // 方案2:使用 Kryo
        // return new KryoRedisSerializer();
      
        // 方案3:使用 FST
        // return new FstRedisSerializer();
    }
  
    // 自定义 Protobuf 序列化器
    public static class ProtobufRedisSerializer implements RedisSerializer<Object> {
      
        @Override
        public byte[] serialize(Object obj) throws SerializationException {
            if (obj == null) {
                return new byte[0];
            }
            // Protobuf 序列化,体积小、速度快
            // 实现略
            return new byte[0];
        }
      
        @Override
        public Object deserialize(byte[] bytes) throws SerializationException {
            if (bytes == null || bytes.length == 0) {
                return null;
            }
            // Protobuf 反序列化
            // 实现略
            return null;
        }
    }
}

3. Pipeline 批量操作

@Service
public class PipelineOptimization {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    // 使用 Pipeline 减少网络往返
    public void batchOperations(Map<String, Object> data) {
        redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                data.forEach((key, value) -> {
                    operations.opsForValue().set(key, value);
                });
                return null;
            }
        });
        // Pipeline 可以将多次网络请求合并为一次
    }
}

监控与诊断

1. 内存监控

@Component
public class RedisMemoryMonitor {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    @Scheduled(fixedRate = 60000) // 每分钟执行一次
    public void monitorMemory() {
        Map<String, String> memoryInfo = getMemoryInfo();
      
        long usedMemory = Long.parseLong(memoryInfo.get("used_memory"));
        long maxMemory = Long.parseLong(memoryInfo.get("maxmemory"));
        double usedPercent = (double) usedMemory / maxMemory * 100;
      
        log.info("Redis 内存使用率: {}%", String.format("%.2f", usedPercent));
      
        // 内存使用率超过 80% 时告警
        if (usedPercent > 80) {
            alertHighMemoryUsage(usedPercent);
        }
      
        // 检查碎片率
        double fragRatio = getFragmentationRatio();
        if (fragRatio > 1.5) {
            log.warn("内存碎片率过高: {}", fragRatio);
        }
    }
  
    private Map<String, String> getMemoryInfo() {
        return redisTemplate.execute((RedisCallback<Map<String, String>>) connection -> {
            Properties props = connection.info("memory");
            return props.entrySet().stream()
                .collect(Collectors.toMap(
                    e -> e.getKey().toString(),
                    e -> e.getValue().toString()
                ));
        });
    }
  
    // 分析大 key
    public List<String> findBigKeys() {
        return redisTemplate.execute((RedisCallback<List<String>>) connection -> {
            // 使用 MEMORY USAGE 命令分析
            List<String> bigKeys = new ArrayList<>();
            Set<byte[]> keys = connection.keys("*".getBytes());
          
            for (byte[] key : keys) {
                Long memoryUsage = connection.memoryUsage(key);
                if (memoryUsage != null && memoryUsage > 10 * 1024 * 1024) { // 大于10MB
                    bigKeys.add(new String(key));
                }
            }
          
            return bigKeys;
        });
    }
}

2. 慢查询分析

@Component
public class SlowLogAnalyzer {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    // 配置慢查询
    public void configureSlowLog() {
        redisTemplate.execute((RedisCallback<Void>) connection -> {
            // 慢查询阈值:10ms
            connection.setConfig("slowlog-log-slower-than", "10000");
          
            // 慢查询日志最大长度
            connection.setConfig("slowlog-max-len", "128");
          
            return null;
        });
    }
  
    // 分析慢查询
    @Scheduled(fixedRate = 300000) // 每5分钟执行一次
    public void analyzeSlowLog() {
        List<Object> slowLogs = redisTemplate.execute(
            (RedisCallback<List<Object>>) connection -> 
                connection.slowLogGet(10) // 获取最近10条慢查询
        );
      
        if (slowLogs != null && !slowLogs.isEmpty()) {
            log.warn("发现慢查询: {}", slowLogs);
            // 进行优化处理
        }
    }
}

分布式场景优化

1. 集群模式内存优化

@Configuration
public class RedisClusterMemoryOptimization {
  
    @Bean
    public RedisClusterConfiguration clusterConfiguration() {
        RedisClusterConfiguration config = new RedisClusterConfiguration();
      
        // 配置集群节点
        config.clusterNode("node1", 7000);
        config.clusterNode("node2", 7001);
        config.clusterNode("node3", 7002);
      
        // 设置最大重定向次数
        config.setMaxRedirects(3);
      
        return config;
    }
  
    // 使用 Hash Tag 确保相关数据在同一节点
    public void saveRelatedData(String userId, Map<String, Object> data) {
        // 使用 {} 包裹的部分用于计算 slot
        String keyPrefix = "{user:" + userId + "}:";
      
        data.forEach((field, value) -> {
            String key = keyPrefix + field;
            redisTemplate.opsForValue().set(key, value);
        });
        // 相关数据在同一节点,减少跨节点通信
    }
}

2. 读写分离优化

@Configuration
public class RedisReadWriteSeparation {
  
    @Bean
    public LettuceConnectionFactory masterConnectionFactory() {
        // 主节点:写操作
        RedisStandaloneConfiguration config = 
            new RedisStandaloneConfiguration("master-host", 6379);
        return new LettuceConnectionFactory(config);
    }
  
    @Bean
    public LettuceConnectionFactory slaveConnectionFactory() {
        // 从节点:读操作
        RedisStandaloneConfiguration config = 
            new RedisStandaloneConfiguration("slave-host", 6379);
        config.setDatabase(0);
      
        LettuceClientConfiguration clientConfig = 
            LettuceClientConfiguration.builder()
                .readFrom(ReadFrom.REPLICA_PREFERRED) // 优先从副本读取
                .build();
              
        return new LettuceConnectionFactory(config, clientConfig);
    }
}

最佳实践总结

内存优化核心策略

  1. 数据结构选择
    • 小数据量使用压缩编码(ziplist、intset)
    • 合理使用 Hash、Bitmap、HyperLogLog
    • 避免大 key,拆分为多个小 key
  2. 过期策略配置
    • 设置合理的过期时间
    • 使用随机过期避免雪崩
    • 选择合适的淘汰策略
  3. 内存碎片管理
    • 启用自动碎片整理
    • 定期监控碎片率
    • 必要时重启实例
  4. 序列化优化
    • 使用高效序列化协议(Protobuf、Kryo)
    • 压缩大对象
    • 避免存储冗余数据

监控指标

  • used_memory:实际使用内存
  • used_memory_rss:操作系统分配内存
  • mem_fragmentation_ratio:碎片率
  • evicted_keys:淘汰键数量
  • expired_keys:过期键数量

面试要点

  1. 理解底层编码:ziplist、quicklist、intset 等
  2. 掌握淘汰策略:LRU、LFU 的区别和适用场景
  3. 实战经验:能说出具体的优化案例和效果
  4. 监控能力:知道如何发现和解决内存问题

Redis 内存优化是一个系统工程,需要从数据结构、配置、监控等多个维度综合考虑,在保证性能的同时最大化内存利用率。