一、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
最佳实践:
- 设置合理的maxmemory和淘汰策略
- 定期扫描大Key和热Key
- 使用Pipeline批量操作
- 监控命中率,保持在90%以上
个人观点,仅供参考