写在前面
Redis以高性能著称,但在实际生产环境中,不当的使用方式可能导致性能急剧下降。今天我们来系统学习Redis性能优化的方法,包括慢查询分析、内存优化、网络优化和大key治理。

文章目录
-
- 写在前面
- 一、慢查询分析
-
- [1.1 什么是慢查询](#1.1 什么是慢查询)
- [1.2 SLOWLOG配置](#1.2 SLOWLOG配置)
- [1.3 查看慢查询日志](#1.3 查看慢查询日志)
- [1.4 常见慢查询命令](#1.4 常见慢查询命令)
- [1.5 慢查询优化案例](#1.5 慢查询优化案例)
- 二、内存优化
-
- [2.1 内存使用分析](#2.1 内存使用分析)
- [2.2 内存碎片问题](#2.2 内存碎片问题)
- [2.3 对象编码优化](#2.3 对象编码优化)
- [2.4 内存优化技巧](#2.4 内存优化技巧)
- 三、网络优化
-
- [3.1 Pipeline管道](#3.1 Pipeline管道)
- [3.2 Lua脚本优化](#3.2 Lua脚本优化)
- [3.3 连接池优化](#3.3 连接池优化)
- [3.4 网络配置优化](#3.4 网络配置优化)
- 四、大key治理
-
- [4.1 什么是大key](#4.1 什么是大key)
- [4.2 大key的危害](#4.2 大key的危害)
- [4.3 大key发现](#4.3 大key发现)
- [4.4 大key删除](#4.4 大key删除)
- [4.5 大key预防](#4.5 大key预防)
- 五、踩坑提醒
-
- [5.1 KEYS命令在生产环境禁用](#5.1 KEYS命令在生产环境禁用)
- [5.2 避免使用SELECT切换数据库](#5.2 避免使用SELECT切换数据库)
- [5.3 避免大量key同时过期](#5.3 避免大量key同时过期)
- [5.4 避免使用阻塞命令](#5.4 避免使用阻塞命令)
- [5.5 常见性能问题汇总](#5.5 常见性能问题汇总)
- 六、性能监控
-
- [6.1 关键监控指标](#6.1 关键监控指标)
- [6.2 监控命令](#6.2 监控命令)
- [6.3 监控工具](#6.3 监控工具)
- 七、面试高频考点
-
- [7.1 如何排查Redis性能问题?](#7.1 如何排查Redis性能问题?)
- [7.2 Redis内存优化有哪些方法?](#7.2 Redis内存优化有哪些方法?)
- [7.3 为什么KEYS命令在生产环境禁用?](#7.3 为什么KEYS命令在生产环境禁用?)
- [7.4 如何处理大key?](#7.4 如何处理大key?)
- 八、参考资料
- 九、互动话题
一、慢查询分析
1.1 什么是慢查询
实际场景:某天运维收到告警,Redis响应时间飙升,需要排查是哪些命令导致的。
慢查询定义:执行时间超过指定阈值的命令。
1.2 SLOWLOG配置
redis
# 查看慢查询配置
CONFIG GET slowlog-*
# 设置慢查询阈值(微秒)
CONFIG SET slowlog-log-slower-than 10000
# 设置慢查询日志最大条数
CONFIG SET slowlog-max-len 128
配置说明:
| 配置项 | 说明 | 默认值 |
|---|---|---|
| slowlog-log-slower-than | 慢查询阈值(微秒) | 10000 |
| slowlog-max-len | 慢查询日志最大条数 | 128 |
1.3 查看慢查询日志
redis
# 查看慢查询日志
SLOWLOG GET
# 查看最近10条慢查询
SLOWLOG GET 10
# 查看慢查询日志条数
SLOWLOG LEN
# 清空慢查询日志
SLOWLOG RESET
慢查询日志格式:
1) 1) (integer) 1 # 日志ID
2) (integer) 1703123456 # 执行时间戳
3) (integer) 15000 # 执行耗时(微秒)
4) 1) "KEYS" # 执行的命令
2) "user:*"
5) "127.0.0.1:54321" # 客户端地址
6) "client-name" # 客户端名称
1.4 常见慢查询命令
踩坑提醒:以下命令在生产环境慎用或禁用。
| 命令 | 时间复杂度 | 说明 | 替代方案 |
|---|---|---|---|
| KEYS | O(N) | 遍历所有key | SCAN |
| HGETALL | O(N) | 获取所有字段 | HSCAN |
| SMEMBERS | O(N) | 获取所有成员 | SSCAN |
| LRANGE | O(N) | 获取列表范围 | 分批获取 |
| DEL | O(N) | 删除大key | UNLINK |
| FLUSHALL | O(N) | 清空所有数据 | 慎用 |
1.5 慢查询优化案例
实际场景:KEYS命令导致Redis阻塞。
问题代码:
redis
# 危险:遍历所有key
KEYS user:*
优化方案:
redis
# 使用SCAN命令分批遍历
SCAN 0 MATCH user:* COUNT 100
# 返回:下一个游标和匹配的key列表
Java代码示例:
java
public List<String> scanKeys(String pattern) {
List<String> keys = new ArrayList<>();
ScanOptions options = ScanOptions.scanOptions()
.match(pattern)
.count(100)
.build();
Cursor<String> cursor = redisTemplate.scan(options);
while (cursor.hasNext()) {
keys.add(cursor.next());
}
return keys;
}
二、内存优化
2.1 内存使用分析
经验之谈:定期分析Redis内存使用情况,及时发现内存问题。
redis
# 查看内存使用情况
INFO memory
# 关键指标:
# used_memory: 已使用内存
# used_memory_rss: 系统分配内存
# used_memory_peak: 内存使用峰值
# mem_fragmentation_ratio: 内存碎片率
内存相关指标:
| 指标 | 说明 | 理想值 |
|---|---|---|
| used_memory | Redis分配的内存总量 | - |
| used_memory_rss | 操作系统分配给Redis的内存 | - |
| mem_fragmentation_ratio | 内存碎片率(rss/used) | 1.0-1.5 |
| used_memory_peak | 内存使用峰值 | - |
2.2 内存碎片问题
踩坑提醒:内存碎片率过高会浪费内存,过低可能导致OOM。
内存碎片产生原因:
- 频繁的内存分配和释放
- 数据大小不一致
- Redis的内存分配器(jemalloc)
内存碎片率分析:
| 碎片率 | 说明 | 建议 |
|---|---|---|
| < 1.0 | Redis使用了swap | 检查系统内存 |
| 1.0-1.5 | 正常 | 无需处理 |
| > 1.5 | 碎片较多 | 考虑整理 |
内存碎片整理:
redis
# 开启自动碎片整理
CONFIG SET activedefrag yes
# 设置碎片整理阈值
CONFIG SET active-defrag-ignore-bytes 100mb
CONFIG SET active-defrag-threshold-lower 10
2.3 对象编码优化
面试高频考点:Redis会根据数据类型和大小自动选择编码方式,了解编码有助于优化内存。
数据类型编码表:
| 数据类型 | 编码方式 | 条件 | 内存效率 |
|---|---|---|---|
| String | int | 整数且范围在long内 | 最高 |
| String | embstr | 字符串长度≤44字节 | 高 |
| String | raw | 字符串长度>44字节 | 中 |
| Hash | ziplist | 元素数≤512,值≤64字节 | 高 |
| Hash | hashtable | 不满足ziplist条件 | 中 |
| List | quicklist | 3.2+版本默认 | 高 |
| Set | intset | 全是整数,元素数≤512 | 高 |
| Set | hashtable | 不满足intset条件 | 中 |
| ZSet | skiplist | 元素数≤128,值≤64字节 | 高 |
| ZSet | skiplist+dict | 不满足条件 | 中 |
查看编码方式:
redis
# 查看key的编码方式
OBJECT ENCODING mykey
2.4 内存优化技巧
实际场景:存储100万个用户信息,如何优化内存占用?
优化方案对比:
| 方案 | 内存占用 | 说明 |
|---|---|---|
| String存储JSON | 高 | 每个key都有元数据开销 |
| Hash存储 | 中 | 多个字段共享一个key |
| Hash + ziplist | 低 | 小对象使用压缩编码 |
优化示例:
redis
# 方案1:String存储(不推荐)
SET user:1 '{"id":1,"name":"张三","age":25}'
SET user:2 '{"id":2,"name":"李四","age":30}'
# 方案2:Hash存储(推荐)
HSET user:1 id 1 name "张三" age 25
HSET user:2 id 2 name "李四" age 30
# 方案3:压缩Hash(最优)
# 控制字段数量和大小,使用ziplist编码
HSET user:1 id 1 name "张三" age 25
# 确保元素数≤512,值长度≤64字节
内存优化建议:
| 优化项 | 建议 |
|---|---|
| Key长度 | 尽量短,使用缩写 |
| 数据结构 | 选择合适的编码方式 |
| 过期策略 | 设置合理的过期时间 |
| 数据压缩 | 使用压缩编码 |
| 大key拆分 | 避免存储大对象 |
三、网络优化
3.1 Pipeline管道
实际场景:需要批量插入10000条数据,逐条执行太慢,如何优化?
Pipeline原理:
传统方式:
Client → Redis → Client → Redis → Client → Redis
(每次请求都需要网络往返)
Pipeline方式:
Client → Redis → Redis → Redis → Client
(一次网络往返执行多个命令)
Pipeline性能对比:
| 方式 | 10000次SET耗时 |
|---|---|
| 逐条执行 | 约10秒 |
| Pipeline | 约0.1秒 |
| 提升 | 100倍 |
Java实现Pipeline:
java
public void batchInsert(Map<String, String> data) {
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
data.forEach((key, value) -> {
connection.set(key.getBytes(), value.getBytes());
});
return null;
});
}
Pipeline注意事项:
| 注意点 | 说明 |
|---|---|
| 批量大小 | 控制每次Pipeline的命令数量(建议100-1000) |
| 原子性 | Pipeline中的命令不是原子的 |
| 返回值 | 注意返回值的顺序 |
3.2 Lua脚本优化
经验之谈:将多个命令封装成Lua脚本,减少网络往返。
lua
-- 扣减库存Lua脚本
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) > 0 then
redis.call('DECR', KEYS[1])
return 1
else
return 0
end
Java调用Lua脚本:
java
String script =
"local stock = redis.call('GET', KEYS[1]) " +
"if tonumber(stock) > 0 then " +
" redis.call('DECR', KEYS[1]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
RedisScript<Long> redisScript = RedisScript.of(script, Long.class);
Long result = redisTemplate.execute(redisScript,
Collections.singletonList("stock:product:123"));
3.3 连接池优化
踩坑提醒:连接池配置不当可能导致连接泄漏或性能下降。
Jedis连接池配置:
java
JedisPoolConfig config = new JedisPoolConfig();
// 最大连接数
config.setMaxTotal(200);
// 最大空闲连接数
config.setMaxIdle(50);
// 最小空闲连接数
config.setMinIdle(10);
// 获取连接最大等待时间(毫秒)
config.setMaxWaitMillis(3000);
// 连接有效性检查
config.setTestOnBorrow(true);
config.setTestOnReturn(false);
config.setTestWhileIdle(true);
JedisPool pool = new JedisPool(config, "127.0.0.1", 6379);
Lettuce连接池配置:
java
RedisClient client = RedisClient.create("redis://127.0.0.1:6379");
StatefulRedisConnection<String, String> connection = client.connect();
// Lettuce使用Netty,单个连接即可支持高并发
// 如需连接池,使用ConnectionPoolSupport
GenericObjectPool<StatefulRedisConnection<String, String>> pool =
ConnectionPoolSupport.createGenericObjectPool(
() -> client.connect(),
new GenericObjectPoolConfig()
);
连接池配置建议:
| 参数 | 建议值 | 说明 |
|---|---|---|
| maxTotal | 200 | 最大连接数 |
| maxIdle | 50 | 最大空闲连接 |
| minIdle | 10 | 最小空闲连接 |
| maxWaitMillis | 3000 | 获取连接超时 |
| testOnBorrow | true | 获取时检查连接 |
3.4 网络配置优化
Redis配置优化:
conf
# 绑定IP
bind 0.0.0.0
# 保护模式
protected-mode no
# TCP端口
port 6379
# TCP连接队列
tcp-backlog 511
# TCP保活
tcp-keepalive 300
# 最大客户端连接数
maxclients 10000
# 输出缓冲区限制
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
四、大key治理
4.1 什么是大key
踩坑提醒:大key是Redis性能杀手,必须及时发现和处理。
大key定义:
| 类型 | 大key标准 |
|---|---|
| String | value > 10KB |
| Hash | 元素数 > 5000 |
| List | 元素数 > 5000 |
| Set | 元素数 > 5000 |
| ZSet | 元素数 > 5000 |
4.2 大key的危害
| 危害 | 说明 |
|---|---|
| 内存占用大 | 单个key占用过多内存 |
| 网络阻塞 | 传输大key阻塞网络 |
| 过期阻塞 | 删除大key阻塞主线程 |
| 迁移阻塞 | 大key迁移影响性能 |
4.3 大key发现
方法1:使用redis-cli --bigkeys
shell
redis-cli --bigkeys
# 输出示例:
# -------- summary -------
# Sampled 100000 keys in the keyspace!
# Biggest string found 'user:10000' has 10240 bytes
# Biggest hash found 'session:5000' has 10000 fields
方法2:使用MEMORY USAGE命令
redis
# 查看key占用内存
MEMORY USAGE mykey
# 返回字节数
# 分析多个key
MEMORY USAGE user:1
MEMORY USAGE user:2
方法3:使用RDB分析工具
shell
# 使用rdb-tools分析
rdb --command json /var/lib/redis/dump.rdb > dump.json
4.4 大key删除
踩坑提醒:直接使用DEL删除大key会阻塞Redis主线程。
安全删除大key:
redis
# String类型:使用UNLINK(异步删除)
UNLINK bigkey
# Hash类型:分批删除
HSCAN bigkey 0 COUNT 100
HDEL bigkey field1 field2 ...
# List类型:分批删除
LTRIM bigkey 0 -101
LTRIM bigkey 0 -101
# 直到删除完毕
# Set类型:分批删除
SSCAN bigkey 0 COUNT 100
SREM bigkey member1 member2 ...
# ZSet类型:分批删除
ZREMRANGEBYRANK bigkey 0 99
Java代码示例:
java
// 安全删除Hash大key
public void deleteBigHash(String key) {
ScanOptions options = ScanOptions.scanOptions().count(100).build();
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() >= 100) {
redisTemplate.opsForHash().delete(key, fields.toArray());
fields.clear();
}
}
if (!fields.isEmpty()) {
redisTemplate.opsForHash().delete(key, fields.toArray());
}
}
4.5 大key预防
| 预防措施 | 说明 |
|---|---|
| 拆分大key | 将大key拆分成多个小key |
| 控制元素数量 | Hash/Set/List元素数控制在5000以内 |
| 控制value大小 | String类型value控制在10KB以内 |
| 监控告警 | 定期扫描大key并告警 |
| 代码审查 | 代码层面禁止写入大key |
大key拆分示例:
redis
# 拆分前:一个大Hash
HSET user:1000:profile name "张三" age 25 city "北京" ...
# 拆分后:多个小Hash
HSET user:1000:basic name "张三" age 25
HSET user:1000:address city "北京" province "北京"
HSET user:1000:contact phone "13800138000"
五、踩坑提醒
5.1 KEYS命令在生产环境禁用
踩坑提醒:KEYS命令会阻塞Redis,生产环境绝对禁止使用!
替代方案:
redis
# 使用SCAN替代KEYS
SCAN 0 MATCH user:* COUNT 100
5.2 避免使用SELECT切换数据库
redis
# 不推荐:使用多个数据库
SELECT 0
SELECT 1
SELECT 2
# 推荐:使用key前缀区分
SET user:1:name "张三"
SET order:1:id "12345"
5.3 避免大量key同时过期
java
// 不推荐:相同过期时间
redisTemplate.expire("key1", 3600, TimeUnit.SECONDS);
redisTemplate.expire("key2", 3600, TimeUnit.SECONDS);
// 推荐:随机过期时间
Random random = new Random();
int baseExpire = 3600;
int randomExpire = random.nextInt(600);
redisTemplate.expire("key1", baseExpire + randomExpire, TimeUnit.SECONDS);
5.4 避免使用阻塞命令
| 命令 | 说明 | 替代方案 |
|---|---|---|
| KEYS | 遍历所有key | SCAN |
| HGETALL | 获取所有字段 | HSCAN |
| SMEMBERS | 获取所有成员 | SSCAN |
| BLPOP | 阻塞弹出 | LPOP + 轮询 |
5.5 常见性能问题汇总
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 响应慢 | 慢查询 | 分析SLOWLOG |
| 内存高 | 内存碎片 | 开启碎片整理 |
| CPU高 | 大key操作 | 拆分大key |
| 网络阻塞 | 大value | 压缩或拆分 |
| 连接超时 | 连接池配置不当 | 优化连接池 |
六、性能监控
6.1 关键监控指标
| 指标 | 说明 | 告警阈值 |
|---|---|---|
| used_memory | 内存使用量 | > 80% maxmemory |
| mem_fragmentation_ratio | 内存碎片率 | > 1.5 |
| connected_clients | 客户端连接数 | > maxclients * 0.8 |
| blocked_clients | 阻塞客户端数 | > 10 |
| instantaneous_ops_per_sec | 当前QPS | 监控趋势 |
| slowlog_len | 慢查询数量 | > 0 告警 |
| expired_keys | 过期key数量 | 监控趋势 |
| evicted_keys | 驱逐key数量 | > 0 告警 |
6.2 监控命令
redis
# 查看服务器信息
INFO
# 查看内存信息
INFO memory
# 查看统计信息
INFO stats
# 查看客户端连接
CLIENT LIST
# 查看慢查询
SLOWLOG GET 10
# 实时监控命令
MONITOR
6.3 监控工具
| 工具 | 说明 |
|---|---|
| Redis-cli | 官方命令行工具 |
| RedisInsight | 官方可视化工具 |
| Prometheus + Grafana | 监控告警平台 |
| Redis Exporter | Redis指标导出器 |
| Redis-stat | 实时监控工具 |
七、面试高频考点
7.1 如何排查Redis性能问题?
答案:
- 查看慢查询日志
redis
SLOWLOG GET 10
- 检查内存使用
redis
INFO memory
- 检查大key
shell
redis-cli --bigkeys
- 检查客户端连接
redis
CLIENT LIST
- 检查命令执行情况
redis
INFO stats
- 使用MONITOR实时监控
redis
MONITOR
7.2 Redis内存优化有哪些方法?
答案:
-
选择合适的数据结构:使用编码效率高的数据类型
-
控制key长度:使用简短的key名
-
使用压缩编码:ziplist、intset等
-
设置过期时间:及时清理无用数据
-
开启内存碎片整理:activedefrag
-
避免大key:拆分大key
-
使用Hash替代String:多个字段存储在一起
7.3 为什么KEYS命令在生产环境禁用?
答案:
-
时间复杂度O(N):需要遍历所有key
-
阻塞主线程:Redis单线程模型,KEYS执行期间无法处理其他请求
-
影响服务可用性:大量key时可能导致服务不可用
替代方案:使用SCAN命令分批遍历
7.4 如何处理大key?
答案:
-
发现大key:
- redis-cli --bigkeys
- MEMORY USAGE命令
- RDB分析工具
-
删除大key:
- 使用UNLINK异步删除
- 分批删除(HSCAN + HDEL)
-
预防大key:
- 拆分大key
- 控制元素数量
- 代码审查
八、参考资料
九、互动话题
- 你在生产环境遇到过Redis性能问题吗?是如何排查和解决的?
- 你们团队是如何监控Redis的?使用了哪些工具?
- 对于大key治理,你有什么好的经验和建议?
欢迎在评论区分享你的实战经验!
下期预告:Day15我们将进行Redis面试高频考点汇总,全面回顾Redis核心知识点,为面试做好准备。