Redis 最佳实践总结
经过多个项目的实战积累,我总结了 Redis 在生产环境中的最佳实践。本文涵盖架构设计、性能优化、运维监控、故障处理等方面,帮助你构建稳定高效的 Redis 系统。
一、架构设计最佳实践
1.1 选型建议
单机版:
- 适用场景:开发测试、小规模应用
- 数据量:< 10GB
- QPS:< 5 万
- 风险:单点故障
主从复制:
- 适用场景:读多写少、数据备份
- 优势:读写分离、故障恢复
- 注意:主节点故障需手动切换
哨兵模式:
- 适用场景:高可用要求
- 优势:自动故障转移
- 推荐:3 哨兵 + 1 主 2 从
Cluster 集群:
- 适用场景:大数据量、高并发
- 优势:水平扩展、自动分片
- 推荐:3 主 3 从(最少)
1.2 内存规划
内存分配建议:
┌─────────────────────────────────────┐
│ 总内存:16GB │
├─────────────────────────────────────┤
│ 数据区:12GB (75%) │
│ - 实际数据存储 │
├─────────────────────────────────────┤
│ 缓冲区:2GB (12.5%) │
│ - 客户端输入/输出缓冲 │
│ - 复制缓冲 │
├─────────────────────────────────────┤
│ 预留:2GB (12.5%) │
│ - 内存碎片 │
│ - fork 开销 │
└─────────────────────────────────────┘
配置示例:
# 最大内存(预留 25%)
maxmemory 12gb
# 内存淘汰策略(推荐 allkeys-lru)
maxmemory-policy allkeys-lru
# 淘汰比例(默认 5%)
maxmemory-samples 10
1.3 Key 设计规范
命名规范:
# 推荐格式
业务名:模块名:ID:字段
user:profile:12345:name
order:detail:202511050001:status
# 使用冒号分隔(Redis 内部优化)
# 避免使用特殊字符:空格、换行、引号
# 统一前缀(便于管理)
app:user:*
app:order:*
app:cache:*
长度控制:
# Key 长度建议
# 推荐:10-50 字符
# 上限:512 MB(但不建议过长)
# 过长的 key 浪费内存
# user:profile:information:detail:content:12345 (45 字符)
# 优化:user:profile:12345:detail (25 字符)
过期时间设置:
// 避免同时过期
// 错误:所有 key 在同一时间过期
redis.setex("cache:key", 3600, value);
// 正确:添加随机时间
int expireTime = 3600 + random.nextInt(300); // 3600-3900 秒
redis.setex("cache:key", expireTime, value);
1.4 数据类型选择
| 场景 | 推荐类型 | 说明 |
|---|---|---|
| 简单缓存 | String | 最基本、性能最好 |
| 计数器 | String (INCR) | 原子操作 |
| 排行榜 | ZSet | 自动排序 |
| 消息队列 | List/Stream | 先进先出 |
| 购物车 | Hash | 字段操作 |
| 标签系统 | Set | 去重、交集 |
| 签到统计 | Bitmap | 节省空间 |
| UV 统计 | HyperLogLog | 基数统计 |
| 地理位置 | GEO | 附近的人 |
二、性能优化最佳实践
2.1 批量操作
Pipeline 批量读写:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import java.util.*;
public class PipelineExample {
private Jedis jedis;
public PipelineExample(Jedis jedis) {
this.jedis = jedis;
}
/**
* 批量写入(Pipeline)
*/
public void batchSet(Map<String, String> data) {
try (Pipeline pipeline = jedis.pipelined()) {
for (Map.Entry<String, String> entry : data.entrySet()) {
pipeline.set(entry.getKey(), entry.getValue());
}
pipeline.sync(); // 一次性发送
}
}
/**
* 批量读取(Pipeline)
*/
public Map<String, String> batchGet(List<String> keys) {
Map<String, String> result = new HashMap<>();
try (Pipeline pipeline = jedis.pipelined()) {
List<Response<String>> responses = new ArrayList<>();
for (String key : keys) {
responses.add(pipeline.get(key));
}
pipeline.sync();
for (int i = 0; i < keys.size(); i++) {
result.put(keys.get(i), responses.get(i).get());
}
}
return result;
}
}
// 性能对比
// 逐个写入 10000 条:~10 秒
// Pipeline 写入 10000 条:~0.5 秒
// 提升:20 倍
MGet/MSet 批量操作:
// 批量读取(MGET)
List<String> values = jedis.mget("key1", "key2", "key3");
// 批量写入(MSET)
jedis.mset("key1", "value1", "key2", "value2", "key3", "value3");
// 注意:MGET/MSET 是原子操作,Pipeline 不是
2.2 连接池优化
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisPoolExample {
public static JedisPool createOptimizedPool() {
JedisPoolConfig config = new JedisPoolConfig();
// 最大连接数(根据并发调整)
config.setMaxTotal(50);
// 最大空闲连接
config.setMaxIdle(20);
// 最小空闲连接
config.setMinIdle(5);
// 获取连接最大等待时间(毫秒)
config.setMaxWaitMillis(3000);
// 空闲连接检查
config.setTestOnBorrow(false); // 性能考虑
config.setTestWhileIdle(true);
config.setTimeBetweenEvictionRunsMillis(30000);
// 连接超时
int timeout = 5000;
return new JedisPool(config, "127.0.0.1", 6379, timeout, "password");
}
}
2.3 命令优化
避免的命令:
# KEYS 命令(阻塞主线程)
# 错误:KEYS user:*
# 正确:SCAN 0 MATCH user:* COUNT 1000
# HGETALL(大 Hash 时慢)
# 错误:HGETALL large:hash
# 正确:HSCAN large:hash 0 COUNT 100
# SMEMBERS(大 Set 时慢)
# 错误:SMEMBERS large:set
# 正确:SSCAN large:set 0 COUNT 100
# LRANGE 0 -1(大 List 时慢)
# 错误:LRANGE large:list 0 -1
# 正确:LRANGE large:list 0 100
推荐的替代方案:
// 使用 SCAN 代替 KEYS
public List<String> scanKeys(String pattern) {
List<String> keys = new ArrayList<>();
String cursor = ScanParams.SCAN_POINTER_START;
ScanParams scanParams = new ScanParams();
scanParams.match(pattern);
scanParams.count(1000);
while (true) {
ScanResult<String> result = jedis.scan(cursor, scanParams);
keys.addAll(result.getResult());
cursor = result.getCursor();
if (ScanParams.SCAN_POINTER_START.equals(cursor)) {
break;
}
}
return keys;
}
// 使用 HSCAN 代替 HGETALL
public Map<String, String> scanHash(String key) {
Map<String, String> result = new HashMap<>();
String cursor = ScanParams.SCAN_POINTER_START;
ScanParams scanParams = new ScanParams();
scanParams.count(1000);
while (true) {
ScanResult<Map.Entry<String, String>> result = jedis.hscan(key, cursor, scanParams);
for (Map.Entry<String, String> entry : result.getResult()) {
// 处理 entry
}
cursor = result.getCursor();
if (ScanParams.SCAN_POINTER_START.equals(cursor)) {
break;
}
}
return result;
}
2.4 Lua 脚本优化
使用 Lua 减少网络往返:
-- 限流脚本
-- KEYS[1]: rate_limit_key
-- ARGV[1]: max_count
-- ARGV[2]: window_size
local key = KEYS[1]
local max_count = tonumber(ARGV[1])
local window_size = tonumber(ARGV[2])
local current = redis.call('GET', key)
if current == false then
redis.call('SET', key, 1, 'EX', window_size)
return 1
end
current = tonumber(current)
if current < max_count then
redis.call('INCR', key)
return 1
else
return 0
end
// Java 调用 Lua 脚本
public class RateLimiter {
private Jedis jedis;
private String scriptSha;
public RateLimiter(Jedis jedis) {
this.jedis = jedis;
// 预加载脚本
String script = loadScript();
this.scriptSha = jedis.scriptLoad(script);
}
public boolean tryAcquire(String key, int maxCount, int windowSize) {
Object result = jedis.evalsha(
scriptSha,
Collections.singletonList(key),
Arrays.asList(String.valueOf(maxCount), String.valueOf(windowSize))
);
return (Long) result == 1;
}
}
三、运维监控最佳实践
3.1 关键指标监控
#!/bin/bash
# redis_monitor.sh
REDIS_HOST="127.0.0.1"
REDIS_PORT="6379"
ALERT_EMAIL="admin@example.com"
# 获取 INFO 信息
INFO=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO)
# 关键指标
USED_MEMORY=$(echo "$INFO" | grep "used_memory:" | cut -d: -f2 | tr -d '\r')
USED_MEMORY_PEAK=$(echo "$INFO" | grep "used_memory_peak:" | cut -d: -f2 | tr -d '\r')
CONNECTED_CLIENTS=$(echo "$INFO" | grep "connected_clients:" | cut -d: -f2 | tr -d '\r')
BLOCKED_CLIENTS=$(echo "$INFO" | grep "blocked_clients:" | cut -d: -f2 | tr -d '\r')
OPS_PER_SEC=$(echo "$INFO" | grep "instantaneous_ops_per_sec:" | cut -d: -f2 | tr -d '\r')
REJECTED_CONNECTIONS=$(echo "$INFO" | grep "rejected_connections:" | cut -d: -f2 | tr -d '\r')
# 告警检查
echo "=== Redis 监控 ==="
echo "内存使用:$(($USED_MEMORY / 1024 / 1024)) MB"
echo "内存峰值:$(($USED_MEMORY_PEAK / 1024 / 1024)) MB"
echo "连接数:$CONNECTED_CLIENTS"
echo "阻塞连接:$BLOCKED_CLIENTS"
echo "QPS: $OPS_PER_SEC"
echo "拒绝连接:$REJECTED_CONNECTIONS"
# 内存告警(> 80%)
MAX_MEMORY=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET maxmemory | tail -1)
if [ -n "$MAX_MEMORY" ] && [ "$MAX_MEMORY" != "0" ]; then
USAGE_PERCENT=$((USED_MEMORY * 100 / MAX_MEMORY))
if [ $USAGE_PERCENT -gt 80 ]; then
echo "⚠️ 内存使用率超过 80%: ${USAGE_PERCENT}%"
fi
fi
# 连接数告警
if [ $CONNECTED_CLIENTS -gt 1000 ]; then
echo "⚠️ 连接数过多:$CONNECTED_CLIENTS"
fi
# 阻塞连接告警
if [ $BLOCKED_CLIENTS -gt 100 ]; then
echo "⚠️ 阻塞连接过多:$BLOCKED_CLIENTS"
fi
3.2 慢查询日志
# 慢查询配置
# 记录超过 10ms 的命令
slowlog-log-slower-than 10000
# 最多保留 128 条
slowlog-max-len 128
# 查看慢查询
redis-cli SLOWLOG GET 10
# 清空慢查询
redis-cli SLOWLOG RESET
# 慢查询数量
redis-cli SLOWLOG LEN
3.3 定期巡检
每日检查:
# 1. 检查服务状态
redis-cli ping
# 2. 检查内存使用
redis-cli INFO memory | grep used_memory_human
# 3. 检查连接数
redis-cli INFO clients | grep connected_clients
# 4. 检查持久化状态
redis-cli INFO persistence | grep -E "rdb_last_bgsave_status|aof_enabled"
# 5. 检查复制状态
redis-cli INFO replication
每周检查:
# 1. 扫描大 Key
redis-cli --bigkeys
# 2. 分析内存
redis-cli MEMORY STATS
# 3. 检查慢查询
redis-cli SLOWLOG GET 128
# 4. 检查客户端连接
redis-cli CLIENT LIST
每月检查:
# 1. 性能基准测试
redis-benchmark -q -t set,get -n 100000
# 2. 备份恢复测试
# - 备份数据
# - 恢复验证
# 3. 故障演练
# - 主从切换
# - 哨兵故障转移
四、安全最佳实践
4.1 访问控制
# 设置密码
requirepass YourStrongPassword123!
# 禁用危险命令
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG ""
rename-command KEYS ""
rename-command DEBUG ""
rename-command SHUTDOWN ""
# 绑定内网 IP
bind 192.168.1.100
# 修改默认端口
port 6380
4.2 网络安全
# 防火墙配置
# 只允许应用服务器访问
iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 6379 -j ACCEPT
iptables -A INPUT -p tcp --dport 6379 -j DROP
# 或使用安全组(云服务)
4.3 数据加密
# Redis 6.0+ TLS 配置
tls-port 6379
port 0 # 禁用非 TLS 端口
tls-cert-file /etc/redis/tls/redis.crt
tls-key-file /etc/redis/tls/redis.key
tls-ca-cert-file /etc/redis/tls/ca.crt
# 客户端连接
redis-cli --tls -h redis.example.com -p 6379 \
--cert /etc/redis/tls/redis.crt \
--key /etc/redis/tls/redis.key \
--cacert /etc/redis/tls/ca.crt
五、故障处理最佳实践
5.1 常见问题处理
问题 1:内存溢出
# 症状:Redis 拒绝写入,返回 OOM 错误
# 解决步骤:
# 1. 检查内存使用
redis-cli INFO memory
# 2. 查看内存淘汰策略
redis-cli CONFIG GET maxmemory-policy
# 3. 临时增加内存
redis-cli CONFIG SET maxmemory 16gb
# 4. 分析大 Key
redis-cli --bigkeys
# 5. 清理数据
redis-cli --scan --pattern "cache:*" | xargs redis-cli unlink
问题 2:CPU 过高
# 症状:CPU 使用率持续 100%
# 解决步骤:
# 1. 查看慢查询
redis-cli SLOWLOG GET 10
# 2. 检查是否有 KEYS 等危险命令
redis-cli MONITOR | grep -E "KEYS|SMEMBERS|HGETALL"
# 3. 查看客户端连接
redis-cli CLIENT LIST
# 4. 终止问题客户端
redis-cli CLIENT KILL ADDR 192.168.1.100:12345
问题 3:连接数过多
# 症状:连接数接近上限,新连接被拒绝
# 解决步骤:
# 1. 查看连接数
redis-cli INFO clients
# 2. 查看客户端列表
redis-cli CLIENT LIST
# 3. 检查应用连接池配置
# - 是否未关闭连接
# - 连接池大小是否合理
# 4. 临时增加最大连接数
redis-cli CONFIG SET maxclients 20000
问题 4:主从同步失败
# 症状:从节点无法同步主节点
# 解决步骤:
# 1. 检查主从状态
redis-cli INFO replication
# 2. 检查网络连通性
telnet <master-ip> 6379
# 3. 检查主节点是否可写
redis-cli -h <master-ip> SET __test__ 1
# 4. 重新配置主从
redis-cli -h <slave-ip> SLAVEOF <master-ip> 6379
# 5. 检查磁盘空间
df -h
5.2 故障转移演练
哨兵故障转移:
# 1. 模拟主节点故障
redis-cli -h <master-ip> DEBUG sleep 1000
# 2. 观察哨兵日志
tail -f /var/log/redis/sentinel.log
# 3. 检查新主节点
redis-cli -h <slave-ip> INFO replication
# 4. 恢复原主节点
# 原主节点会自动变为从节点
# 5. 验证数据完整性
redis-cli keys "*" | wc -l
六、总结
6.1 核心要点
-
架构设计
- 根据业务规模选择合适架构
- 预留足够的内存和连接数
- 设计合理的 Key 命名规范
-
性能优化
- 使用 Pipeline 批量操作
- 避免使用 KEYS 等危险命令
- 合理配置连接池
-
运维监控
- 监控关键指标(内存、连接、QPS)
- 定期巡检(大 Key、慢查询)
- 建立告警机制
-
安全保障
- 设置强密码
- 禁用危险命令
- 使用内网访问
-
故障处理
- 建立标准处理流程
- 定期故障演练
- 完善备份恢复机制
6.2 检查清单
上线前检查:
- 内存配置合理(maxmemory)
- 持久化配置开启
- 密码认证设置
- 危险命令禁用
- 连接池配置优化
- 监控告警配置
- 备份策略制定
日常运维检查:
- 服务状态正常
- 内存使用正常
- 连接数正常
- 无慢查询
- 持久化正常
- 复制正常
参考资料
- Redis 官方最佳实践
- Redis 运维指南
- 《Redis 深度历险:核心原理与应用实践》