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);
}
}
最佳实践总结
内存优化核心策略
- 数据结构选择
- 小数据量使用压缩编码(ziplist、intset)
- 合理使用 Hash、Bitmap、HyperLogLog
- 避免大 key,拆分为多个小 key
- 过期策略配置
- 设置合理的过期时间
- 使用随机过期避免雪崩
- 选择合适的淘汰策略
- 内存碎片管理
- 启用自动碎片整理
- 定期监控碎片率
- 必要时重启实例
- 序列化优化
- 使用高效序列化协议(Protobuf、Kryo)
- 压缩大对象
- 避免存储冗余数据
监控指标
- used_memory:实际使用内存
- used_memory_rss:操作系统分配内存
- mem_fragmentation_ratio:碎片率
- evicted_keys:淘汰键数量
- expired_keys:过期键数量
面试要点
- 理解底层编码:ziplist、quicklist、intset 等
- 掌握淘汰策略:LRU、LFU 的区别和适用场景
- 实战经验:能说出具体的优化案例和效果
- 监控能力:知道如何发现和解决内存问题
Redis 内存优化是一个系统工程,需要从数据结构、配置、监控等多个维度综合考虑,在保证性能的同时最大化内存利用率。