Redis性能优化与监控

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测试
redis-benchmark -h localhost -p 6379 -c 50 -n 100000

# 参数说明:
# -c 50: 50个并发连接
# -n 100000: 10万次请求

# 测试特定命令
redis-benchmark -t set,get -n 100000

# 测试大value
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 # 10ms

# 查看慢查询日志
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
HGET user:1001 name
HGET user:1001 age
HGET user:1001 city

# 好的做法:一次HMGET
HMGET user:1001 name age city

#

2.4 Pipeline优化

// 批量操作使用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存储对象

// String方式(JSON序列化)
redis.opsForValue().set("user:1001", json); // 可能有冗余字段名

// Hash方式(更省空间)
Map<String, String> userMap = new HashMap<>();
userMap.put("name", "Alice");
userMap.put("age", "25");
redis.opsForHash().putAll("user:1001", userMap);

#

3.3 数据压缩

// 使用Snappy压缩value
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 合理设置过期时间

// 不要所有key设置相同的过期时间
// 添加随机偏移,避免同时过期
int ttl = 3600 + ThreadLocalRandom.current().nextInt(300);
redis.opsForValue().set(key, value, ttl, TimeUnit.SECONDS);

四、连接优化

#

4.1 使用连接池

# Lettuce连接池配置
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阻塞

# 监控fork耗时
redis-cli INFO stats | grep latest_fork_usec

# 如果fork耗时过长:
# 1. 控制Redis内存大小(建议不超过10GB)
# 2. 使用更快的存储(SSD)
# 3. 关闭透明大页
echo never > /sys/kernel/mm/transparent_hugepage/enabled

六、系统级优化

#

6.1 内核参数优化

# /etc/sysctl.conf

# TCP连接优化
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 网络优化

# 网卡中断亲和性
# 将Redis进程绑定到特定CPU核心
taskset -c 0 redis-server /etc/redis/redis.conf

#

6.3 文件描述符

# 查看当前限制
ulimit -n

# 修改限制
# /etc/security/limits.conf
redis soft nofile 65535
redis hard nofile 65535

七、监控方案

#

7.1 Redis INFO监控

# 关键监控指标
redis-cli INFO stats

# 重点指标:
# total_connections_received: 总连接数
# total_commands_processed: 总命令数
# instantaneous_ops_per_sec: 每秒操作数
# rejected_connections: 拒绝的连接数
# expired_keys: 过期的key数
# evicted_keys: 被淘汰的key数
# keyspace_hits: 命中次数
# keyspace_misses: 未命中次数

#

7.2 Prometheus + Grafana

# docker-compose.yml
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"
# prometheus.yml
scrape_configs:
- job_name: 'redis'
static_configs:
- targets: ['redis-exporter:9121']

#

7.3 关键告警规则

# redis_alerts.yml
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性能优化的核心原则:

  1. 预防为主:设计时考虑性能
  2. 监控先行:建立完善的监控体系
  3. 渐进优化:小步快跑,验证效果
  4. 全局视角:从应用到系统全面考虑

核心要点

  1. String:简单的键值对,适合缓存、计数器

  2. Hash:存储对象属性,适合用户信息、配置

  3. List:有序列表,适合消息队列、最新列表

  4. Set:无序去重,适合共同好友、抽奖

  5. ZSet:有序集合,适合排行榜、积分系统

总结

选择合适的数据结构是使用 Redis 的关键。在实际项目中,根据业务需求选择合适的类型,可以提升性能和开发效率。


   转载规则


《Redis性能优化与监控》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录