【架构实战】Redis性能调优与内存优化策略

一、Redis性能瓶颈分析

Redis虽然是内存数据库,但在高并发场景下仍可能出现性能问题:

常见性能问题:

  • 大Key导致操作阻塞
  • 热Key导致单节点压力过大
  • 内存不足触发淘汰策略
  • 持久化影响性能
  • 网络带宽成为瓶颈

二、Redis性能监控

1. INFO命令

bash 复制代码
# 查看所有信息
redis-cli INFO

# 查看内存信息
redis-cli INFO memory

# 查看统计信息
redis-cli INFO stats

# 查看客户端信息
redis-cli INFO clients

关键指标:

复制代码
# 内存使用
used_memory: 1073741824          # 已使用内存(字节)
used_memory_human: 1.00G         # 人类可读格式
used_memory_peak: 1073741824     # 内存使用峰值
mem_fragmentation_ratio: 1.2     # 内存碎片率(>1.5需关注)

# 命中率
keyspace_hits: 1000000           # 命中次数
keyspace_misses: 10000           # 未命中次数
# 命中率 = hits / (hits + misses)

# 连接数
connected_clients: 100           # 当前连接数
blocked_clients: 0               # 阻塞的客户端数

# 操作统计
total_commands_processed: 10000000  # 总命令数
instantaneous_ops_per_sec: 10000    # 当前QPS

2. MONITOR命令

bash 复制代码
# 实时监控所有命令(生产慎用,影响性能)
redis-cli MONITOR

# 输出示例
1704067200.123456 [0 127.0.0.1:12345] "GET" "user:1001"
1704067200.123457 [0 127.0.0.1:12345] "SET" "product:2001" "..."

3. SLOWLOG慢日志

bash 复制代码
# 配置慢日志阈值(微秒)
redis-cli CONFIG SET slowlog-log-slower-than 10000  # 10ms

# 查看慢日志
redis-cli SLOWLOG GET 10

# 输出示例
1) 1) (integer) 1          # 日志ID
   2) (integer) 1704067200 # 执行时间戳
   3) (integer) 15000      # 执行耗时(微秒)
   4) 1) "KEYS"            # 命令
      2) "*"

4. 大Key扫描

bash 复制代码
# 扫描大Key(不阻塞)
redis-cli --bigkeys

# 输出示例
Biggest string found so far 'product:1001' with 102400 bytes
Biggest list found so far 'order:list' with 100000 items
Biggest hash found so far 'user:hash' with 50000 fields

三、大Key问题

1. 大Key的危害

复制代码
大Key(如value > 10KB,list > 10000元素)
    ↓
操作耗时长(如DEL一个100万元素的list)
    ↓
Redis单线程阻塞
    ↓
其他命令排队等待
    ↓
响应时间飙升

2. 大Key处理方案

方案1:拆分大Key

java 复制代码
// 原来:一个大Hash存储所有用户属性
hset user:1001 name "张三" age 28 phone "138..." ...

// 优化:按属性分组拆分
hset user:1001:basic name "张三" age 28
hset user:1001:contact phone "138..." email "..."
hset user:1001:address city "北京" district "朝阳"

方案2:异步删除大Key

bash 复制代码
# Redis 4.0+ 支持异步删除
redis-cli UNLINK big_key  # 异步删除,不阻塞

# 配置自动异步删除
redis-cli CONFIG SET lazyfree-lazy-eviction yes
redis-cli CONFIG SET lazyfree-lazy-expire yes
redis-cli CONFIG SET lazyfree-lazy-server-del yes

方案3:分批删除

java 复制代码
// 分批删除大List
public void deleteLargeList(String key) {
    long size = redisTemplate.opsForList().size(key);
    int batchSize = 1000;
    
    while (size > 0) {
        // 每次删除1000个元素
        redisTemplate.opsForList().trim(key, batchSize, -1);
        size = redisTemplate.opsForList().size(key);
        
        // 避免阻塞,每批之间暂停
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    redisTemplate.delete(key);
}

// 分批删除大Hash
public void deleteLargeHash(String key) {
    ScanOptions options = ScanOptions.scanOptions().count(1000).build();
    
    try (Cursor<Map.Entry<Object, Object>> cursor = 
            redisTemplate.opsForHash().scan(key, options)) {
        
        List<Object> fields = new ArrayList<>();
        while (cursor.hasNext()) {
            fields.add(cursor.next().getKey());
            
            if (fields.size() >= 1000) {
                redisTemplate.opsForHash().delete(key, fields.toArray());
                fields.clear();
                Thread.sleep(10);
            }
        }
        
        if (!fields.isEmpty()) {
            redisTemplate.opsForHash().delete(key, fields.toArray());
        }
    }
}

四、热Key问题

1. 热Key的危害

复制代码
热Key(如秒杀商品库存)
    ↓
单个Key的QPS极高(如10万/秒)
    ↓
Redis单节点CPU打满
    ↓
其他请求响应变慢

2. 热Key检测

bash 复制代码
# 使用redis-cli --hotkeys(Redis 4.0+)
redis-cli --hotkeys

# 使用monitor统计(生产慎用)
redis-cli MONITOR | head -1000 | awk '{print $4}' | sort | uniq -c | sort -rn | head -20

3. 热Key解决方案

方案1:本地缓存

java 复制代码
@Service
public class ProductService {
    
    // 本地缓存热Key
    private final LoadingCache<Long, Product> localCache = Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(1, TimeUnit.MINUTES)
        .build(id -> getProductFromRedis(id));
    
    public Product getProduct(Long id) {
        // 热Key走本地缓存
        if (isHotKey(id)) {
            return localCache.get(id);
        }
        return getProductFromRedis(id);
    }
    
    private boolean isHotKey(Long id) {
        String countKey = "hotkey:count:" + id;
        Long count = redisTemplate.opsForValue().increment(countKey);
        redisTemplate.expire(countKey, 1, TimeUnit.SECONDS);
        return count != null && count > 1000;
    }
}

方案2:Key分散

java 复制代码
// 将热Key分散到多个Key
public String getHotValue(String key) {
    int shardCount = 10;
    int shard = ThreadLocalRandom.current().nextInt(shardCount);
    String shardKey = key + ":" + shard;
    
    String value = redisTemplate.opsForValue().get(shardKey);
    if (value == null) {
        // 从主Key获取并写入分片Key
        value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            redisTemplate.opsForValue().set(shardKey, value, 60, TimeUnit.SECONDS);
        }
    }
    
    return value;
}

五、内存优化

1. 内存淘汰策略

bash 复制代码
# 查看当前策略
redis-cli CONFIG GET maxmemory-policy

# 设置策略
redis-cli CONFIG SET maxmemory-policy allkeys-lru

策略对比:

策略 说明 适用场景
noeviction 不淘汰,写入报错 不允许丢失数据
allkeys-lru 所有Key中LRU淘汰 通用缓存
volatile-lru 有过期时间的Key中LRU淘汰 混合存储
allkeys-random 随机淘汰 均匀访问
volatile-ttl 优先淘汰TTL最短的 按时间重要性
allkeys-lfu 所有Key中LFU淘汰 热点数据

2. 内存压缩

bash 复制代码
# 开启压缩(适合大Value)
redis-cli CONFIG SET activerehashing yes

# Hash压缩(小Hash使用ziplist)
redis-cli CONFIG SET hash-max-ziplist-entries 128
redis-cli CONFIG SET hash-max-ziplist-value 64

# List压缩
redis-cli CONFIG SET list-max-ziplist-size -2  # 8kb
redis-cli CONFIG SET list-compress-depth 1

# ZSet压缩
redis-cli CONFIG SET zset-max-ziplist-entries 128
redis-cli CONFIG SET zset-max-ziplist-value 64

3. 内存碎片整理

bash 复制代码
# 查看碎片率
redis-cli INFO memory | grep mem_fragmentation_ratio

# 手动整理碎片(Redis 4.0+)
redis-cli MEMORY PURGE

# 自动整理碎片
redis-cli CONFIG SET activedefrag yes
redis-cli CONFIG SET active-defrag-ignore-bytes 100mb
redis-cli CONFIG SET active-defrag-threshold-lower 10
redis-cli CONFIG SET active-defrag-threshold-upper 100

六、持久化优化

1. RDB优化

bash 复制代码
# 关闭不必要的RDB保存点
redis-cli CONFIG SET save ""

# 或者调整保存频率
redis-cli CONFIG SET save "3600 1 300 100 60 10000"

# 使用后台保存
redis-cli CONFIG SET rdbcompression yes
redis-cli CONFIG SET rdbchecksum yes

2. AOF优化

bash 复制代码
# AOF重写配置
redis-cli CONFIG SET auto-aof-rewrite-percentage 100
redis-cli CONFIG SET auto-aof-rewrite-min-size 64mb

# AOF同步策略(性能vs安全)
redis-cli CONFIG SET appendfsync everysec  # 每秒同步(推荐)
# redis-cli CONFIG SET appendfsync always  # 每次写入同步(最安全但最慢)
# redis-cli CONFIG SET appendfsync no      # 不主动同步(最快但不安全)

# 重写时不同步(减少IO)
redis-cli CONFIG SET no-appendfsync-on-rewrite yes

七、连接池优化

java 复制代码
@Configuration
public class RedisConfig {
    
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        // 连接池配置
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxTotal(200);        // 最大连接数
        poolConfig.setMaxIdle(50);          // 最大空闲连接
        poolConfig.setMinIdle(20);          // 最小空闲连接
        poolConfig.setMaxWaitMillis(3000);  // 获取连接超时
        poolConfig.setTestOnBorrow(true);   // 借出时检测
        poolConfig.setTestOnReturn(false);  // 归还时不检测
        
        LettucePoolingClientConfiguration clientConfig = 
            LettucePoolingClientConfiguration.builder()
                .poolConfig(poolConfig)
                .commandTimeout(Duration.ofSeconds(3))
                .build();
        
        RedisStandaloneConfiguration serverConfig = 
            new RedisStandaloneConfiguration("localhost", 6379);
        
        return new LettuceConnectionFactory(serverConfig, clientConfig);
    }
}

八、Pipeline批量操作

java 复制代码
@Service
public class RedisBatchService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 使用Pipeline批量写入
    public void batchSet(Map<String, Object> data) {
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            data.forEach((key, value) -> {
                connection.set(
                    key.getBytes(),
                    JSON.toJSONBytes(value)
                );
            });
            return null;
        });
    }
    
    // 使用Pipeline批量读取
    public List<Object> batchGet(List<String> keys) {
        return redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            keys.forEach(key -> connection.get(key.getBytes()));
            return null;
        });
    }
    
    // 使用Lua脚本保证原子性
    public Long atomicIncrement(String key, long delta, long expireSeconds) {
        String script = """
            local current = redis.call('INCRBY', KEYS[1], ARGV[1])
            if current == tonumber(ARGV[1]) then
                redis.call('EXPIRE', KEYS[1], ARGV[2])
            end
            return current
            """;
        
        return redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(key),
            String.valueOf(delta),
            String.valueOf(expireSeconds)
        );
    }
}

九、总结

Redis性能调优是一个系统工程:

  • 监控先行:INFO、SLOWLOG、MONITOR
  • 大Key治理:拆分、异步删除、分批删除
  • 热Key处理:本地缓存、Key分散
  • 内存优化:淘汰策略、压缩、碎片整理
  • 持久化优化:合理配置RDB/AOF

最佳实践:

  1. 设置合理的maxmemory和淘汰策略
  2. 定期扫描大Key和热Key
  3. 使用Pipeline批量操作
  4. 监控命中率,保持在90%以上

个人观点,仅供参考

相关推荐
呆子也有梦2 小时前
游戏服务端大地图架构通俗指南:从“分区管理”到“动态调度”
服务器·后端·游戏·架构·系统架构
Flying pigs~~2 小时前
检索增强生成RAG项目tools_04:flask➕fastapi➕高并发
数据库·python·flask·大模型·fastapi·异步
minebmw72 小时前
Oracle 19.29 中 ORA-12751 错误完全解析:从通用问题到 minact-scn 场景
数据库·oracle
杰克尼2 小时前
redis(day06-多级缓存)
redis·分布式·缓存
星晨雪海2 小时前
优惠券秒杀的核心业务逻辑
java·前端·数据库
清风6666662 小时前
基于单片机的智能门控制系统设计与故障报警实现
数据库·单片机·mongodb·毕业设计·课程设计·期末大作业
SelectDB技术团队2 小时前
AI 成为主流负载后,数据基础设施将如何演进?|Apache Doris 2026 Roadmap
数据库·人工智能·apache doris·selectdb
SPC的存折2 小时前
分布式(加一键部署脚本)LNMP-Redis-Discuz5.0部署指南-小白详细版
linux·运维·服务器·数据库·redis·分布式·缓存
脑子加油站2 小时前
Redis数据库基础
数据库·redis·缓存