Redis性能优化与监控
Redis 的五种数据结构各有特色,用对了才能发挥它的优势。很多人只用到了 String 和 Hash,却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发,讲什么时候用什么类型。
一、性能基准
#
1.1 Redis性能指标
| 指标 |
优秀 |
良好 |
需优化 |
| QPS |
> 100000 |
50000-100000 |
< 50000 |
| 平均延迟 |
< 1ms |
1-5ms |
> 5ms |
| P99延迟 |
< 5ms |
5-20ms |
> 20ms |
| 内存使用率 |
< 70% |
70-85% |
> 85% |
| 缓存命中率 |
> 95% |
90-95% |
< 90% |
#
1.2 测试性能
redis-benchmark -h localhost -p 6379 -c 50 -n 100000
redis-benchmark -t set,get -n 100000
redis-benchmark -t set,get -d 1024 -n 100000
|
二、命令优化
#
2.1 避免慢查询
redis-cli CONFIG GET slowlog*
redis-cli CONFIG SET slowlog-log-slower-than 10000
redis-cli SLOWLOG GET 10
redis-cli SLOWLOG RESET
|
#
2.2 避免阻塞命令
| 命令 |
问题 |
替代方案 |
| KEYS |
全表扫描,O(n) |
SCAN |
| FLUSHALL/FLUSHDB |
删除所有数据 |
FLUSHALL ASYNC |
| DEL 大Key |
阻塞 |
UNLINK |
| HGETALL 大Hash |
返回大量数据 |
HSCAN |
| SMEMBERS 大Set |
返回大量数据 |
SSCAN |
| ZRANGE 大ZSet |
范围查询大 |
限制范围 |
| LRANGE 大List |
范围查询大 |
限制范围 |
#
2.3 使用高效命令
MSET key1 value1 MSET key2 value2 MSET key3 value3
MSET key1 value1 key2 value2 key3 value3
HGET user:1001 name HGET user:1001 age HGET user:1001 city
HMGET user:1001 name age city
|
#
2.4 Pipeline优化
redis.executePipelined((RedisCallback<Object>) connection -> { for (int i = 0; i < 1000; i++) { connection.stringCommands().set( ("key" + i).getBytes(), ("value" + i).getBytes() ); } return null; });
|
三、内存优化
#
3.1 数据编码优化
# 控制ziplist编码的使用条件 hash-max-ziplist-entries 512 hash-max-ziplist-value 64
list-max-ziplist-size -2 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64
|
#
3.2 使用Hash存储对象
redis.opsForValue().set("user:1001", json);
Map<String, String> userMap = new HashMap<>(); userMap.put("name", "Alice"); userMap.put("age", "25"); redis.opsForHash().putAll("user:1001", userMap);
|
#
3.3 数据压缩
public String compress(String data) { try { return Base64.getEncoder().encodeToString(Snappy.compress(data.getBytes())); } catch (IOException e) { throw new RuntimeException(e); } }
public String decompress(String compressed) { try { return new String(Snappy.uncompress(Base64.getDecoder().decode(compressed))); } catch (IOException e) { throw new RuntimeException(e); } }
|
#
3.4 合理设置过期时间
int ttl = 3600 + ThreadLocalRandom.current().nextInt(300); redis.opsForValue().set(key, value, ttl, TimeUnit.SECONDS);
|
四、连接优化
#
4.1 使用连接池
spring: redis: lettuce: pool: max-active: 50 max-idle: 20 min-idle: 5 max-wait: 3000ms
|
#
4.2 避免频繁创建连接
public void badPractice() { Jedis jedis = new Jedis("localhost", 6379); jedis.set("key", "value"); jedis.close(); }
@Autowired private StringRedisTemplate redis;
public void goodPractice() { redis.opsForValue().set("key", "value"); }
|
#
4.3 Cluster模式下的连接优化
@Bean public LettuceConnectionFactory redisConnectionFactory() { RedisClusterConfiguration config = new RedisClusterConfiguration(); ClientOptions clientOptions = ClientOptions.builder() .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(3))) .build(); ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder() .enablePeriodicRefresh(Duration.ofSeconds(30)) .enableAllAdaptiveRefreshTriggers() .build(); ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder() .topologyRefreshOptions(topologyRefreshOptions) .build(); LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .clientOptions(clusterClientOptions) .build(); return new LettuceConnectionFactory(config, clientConfig); }
|
五、持久化优化
#
5.1 RDB优化
# 减少RDB频率(如果数据可重建) save 900 1 save 300 10 save 60 10000
# 使用无磁盘复制 repl-diskless-sync yes
|
#
5.2 AOF优化
# 使用everysec(平衡性能和安全) appendfsync everysec
# 开启AOF重写 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
# 使用混合持久化(Redis 4.0+) aof-use-rdb-preamble yes
|
#
5.3 避免fork阻塞
redis-cli INFO stats | grep latest_fork_usec
echo never > /sys/kernel/mm/transparent_hugepage/enabled
|
六、系统级优化
#
6.1 内核参数优化
net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 65535
vm.overcommit_memory = 1
echo never > /sys/kernel/mm/transparent_hugepage/enabled
|
#
6.2 网络优化
taskset -c 0 redis-server /etc/redis/redis.conf
|
#
6.3 文件描述符
ulimit -n
redis soft nofile 65535 redis hard nofile 65535
|
七、监控方案
#
7.1 Redis INFO监控
#
7.2 Prometheus + Grafana
version: '3' services: redis-exporter: image: oliver006/redis_exporter:latest environment: - REDIS_ADDR=redis://redis:6379 ports: - "9121:9121" prometheus: image: prom/prometheus:latest volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml ports: - "9090:9090" grafana: image: grafana/grafana:latest ports: - "3000:3000"
|
scrape_configs: - job_name: 'redis' static_configs: - targets: ['redis-exporter:9121']
|
#
7.3 关键告警规则
groups: - name: redis rules: - alert: RedisDown expr: redis_up == 0 for: 1m labels: severity: critical annotations: summary: "Redis实例宕机" - alert: RedisHighMemoryUsage expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.85 for: 5m labels: severity: warning annotations: summary: "Redis内存使用率超过85%" - alert: RedisHighConnections expr: redis_connected_clients / redis_config_maxclients > 0.8 for: 5m labels: severity: warning annotations: summary: "Redis连接数超过80%" - alert: RedisCacheHitRateLow expr: rate(redis_keyspace_hits_total[5m]) / (rate(redis_keyspace_hits_total[5m]) + rate(redis_keyspace_misses_total[5m])) < 0.9 for: 10m labels: severity: warning annotations: summary: "Redis缓存命中率低于90%" - alert: RedisReplicationLag expr: redis_master_link_up == 1 and redis_master_last_io_seconds_ago > 10 for: 5m labels: severity: warning annotations: summary: "Redis复制延迟超过10秒"
|
#
7.4 自定义监控
@Component public class RedisMetrics { @Autowired private StringRedisTemplate redis; @Autowired private MeterRegistry meterRegistry; @Scheduled(fixedRate = 60000) public void collectMetrics() { Properties info = redis.execute((RedisCallback<Properties>) connection -> connection.serverCommands().info("stats")); if (info != null) { String hits = info.getProperty("keyspace_hits"); String misses = info.getProperty("keyspace_misses"); if (hits != null && misses != null) { long total = Long.parseLong(hits) + Long.parseLong(misses); double hitRate = total > 0 ? Double.parseDouble(hits) / total : 0; meterRegistry.gauge("redis.cache.hit.rate", hitRate); } } } }
|
八、性能排查流程
发现性能问题 │ ├── 查看QPS和延迟 │ ├── QPS低 → 检查应用连接池 │ └── QPS正常但延迟高 → 继续 │ ├── 查看慢查询日志 │ ├── 有慢查询 → 优化命令 │ └── 无慢查询 → 继续 │ ├── 查看内存使用 │ ├── 内存高 → 优化内存/淘汰策略 │ └── 内存正常 → 继续 │ ├── 查看持久化状态 │ ├── fork耗时高 → 优化fork │ └── 正常 → 继续 │ ├── 查看网络 │ ├── 带宽饱和 → 优化网络/压缩 │ └── 正常 → 继续 │ └── 查看系统资源 ├── CPU高 → 优化命令复杂度 ├── 磁盘IO高 → 优化持久化 └── 网络延迟高 → 优化网络
|
九、总结
| 优化方向 |
具体措施 |
效果 |
| 命令优化 |
避免慢查询,使用批量命令 |
显著提升 |
| 内存优化 |
ziplist编码,Hash存储对象 |
节省内存 |
| 连接优化 |
连接池,避免频繁创建 |
提升吞吐 |
| 持久化优化 |
合理配置RDB/AOF |
减少阻塞 |
| 系统优化 |
内核参数,关闭THP |
稳定性能 |
Redis性能优化的核心原则:
- 预防为主:设计时考虑性能
- 监控先行:建立完善的监控体系
- 渐进优化:小步快跑,验证效果
- 全局视角:从应用到系统全面考虑
核心要点
String:简单的键值对,适合缓存、计数器
Hash:存储对象属性,适合用户信息、配置
List:有序列表,适合消息队列、最新列表
Set:无序去重,适合共同好友、抽奖
ZSet:有序集合,适合排行榜、积分系统
总结
选择合适的数据结构是使用 Redis 的关键。在实际项目中,根据业务需求选择合适的类型,可以提升性能和开发效率。