引言
Redis 作为高性能的内存数据库,在现代应用架构中扮演着重要角色。然而,不当的使用方式可能导致性能问题、内存浪费甚至系统故障。本文基于阿里云 Redis 开发规范,结合实战经验,系统梳理 Redis 键值设计、命令使用、性能优化等方面的最佳实践,帮助开发者构建稳定高效的 Redis 应用。
键值设计规范
1. Key 命名规范
可读性和可管理性
Key 命名应遵循业务名:表名:id 的格式,使用冒号分隔,便于识别和管理。
1 2 3 4 5 6
| 推荐格式:业务名:表名:id
示例: ugc:video:1 # UGC业务视频内容 shop:product:10086 # 电商商品信息 user:profile:12345 # 用户资料
|
简洁性原则
在保证语义清晰的前提下,控制 key 的长度。过长的 key 会增加内存占用。
1 2 3 4 5
| ❌ 不推荐: user:{uid}:friends:messages:{mid}
✅ 推荐简化为: u:{uid}:fr:m:{mid}
|
Key 长度对内存的影响:
| Key 数量 |
平均 Key 长度 |
内存占用估算 |
| 100万 |
50字节 |
~50MB |
| 100万 |
20字节 |
~20MB |
| 1000万 |
50字节 |
~500MB |
禁止特殊字符
强制规范: Key 中不得包含空格、换行、单双引号以及其他转义字符。
1 2 3 4 5 6 7
| ❌ 错误示例: "user: name:123" # 包含空格 user\nprofile\:456 # 包含换行和转义
✅ 正确示例: user:name:123 user:profile:456
|
2. Value 设计规范
拒绝 BigKey
强制规范: 控制 Value 大小,防止网卡流量瓶颈和慢查询。
| 数据类型 |
限制 |
| String |
10KB 以内 |
| Hash |
元素个数 5000 以内 |
| List |
元素个数 5000 以内 |
| Set |
元素个数 5000 以内 |
| ZSet |
元素个数 5000 以内 |
BigKey 的危害:
1 2 3 4 5 6 7 8 9
| 内存影响: - 单个 BigKey 可能占用大量内存 - 导致 Redis 内存碎片 - 影响 RDB/AOF 持久化效率
性能影响: - 读取 BigKey 造成网络拥塞 - 阻塞 Redis 主线程 - 触发 OOM 风险
|
渐进式删除 BigKey:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| del big:hash
redis-cli --big-keys
HSCAN big:hash 0 COUNT 100 HDEL big:hash field1 field2 ...
LTRIM big:list 0 -10001 LPOP big:list 1000
SSCAN big:set 0 COUNT 100 SREM big:set member1 member2 ...
ZSCAN big:zset 0 COUNT 100 ZREM big:zset member1 member2 ...
|
特别注意: 设置过期时间的 BigKey 自动删除时,会触发阻塞。
1 2 3 4 5
| 反例: - 200万个元素的 ZSet 设置1小时过期 - 过期时触发 DEL 操作 - 造成主线程阻塞数秒 - 不会出现在慢查询中(可用 latency 监控)
|
选择合适的数据类型
1 2 3 4 5 6 7 8 9 10 11 12
| ❌ 反例:使用多个 String 存储对象属性 set user:1:name tom set user:1:age 19 set user:1:favor football
✅ 正例:使用 Hash 存储对象 hmset user:1 name tom age 19 favor football
优势: - 内存更紧凑 - 支持字段级别的原子操作 - 减少 Key 数量
|
内存编码优化配置:
1 2 3 4 5 6 7 8 9 10
| hash-max-ziplist-entries 512 hash-max-ziplist-value 64
zset-max-ziplist-entries 128 zset-max-ziplist-value 64
list-max-ziplist-size -2
|
控制 Key 生命周期
Redis 不是垃圾桶,需要合理设置过期时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| expire session:12345 3600
expire key $((3600 + RANDOM % 300))
redis-cli --scan --pattern "*" | while read key; do ttl=$(redis-cli ttl "$key") if [ "$ttl" -eq -1 ]; then object_freq=$(redis-cli object freq "$key" 2>/dev/null || echo 0) if [ "$object_freq" -eq 0 ]; then echo "Cold key: $key" fi fi done
|
命令使用规范
1. O(N) 命令注意事项
危险命令列表:
| 命令 |
时间复杂度 |
风险 |
| KEYS |
O(N) |
全表扫描,阻塞服务 |
| FLUSHALL |
O(N) |
清空所有数据 |
| FLUSHDB |
O(N) |
清空当前库 |
| HGETALL |
O(N) |
大 Hash 时性能差 |
| LRANGE |
O(S+N) |
大 List 时性能差 |
| SMEMBERS |
O(N) |
大 Set 时性能差 |
| ZRANGE |
O(logN+S) |
范围大时性能差 |
| SINTER |
O(N*M) |
多集合交集计算 |
替代方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| keys user:*
scan 0 match user:* count 1000
hgetall big:hash
hscan big:hash 0 count 100
lrange big:list 0 -1
lrange big:list 0 99 lrange big:list 100 199
|
2. 禁用高危命令
1 2 3 4 5 6 7 8
| rename-command FLUSHALL "" rename-command FLUSHDB "" rename-command KEYS "" rename-command DEBUG ""
rename-command CONFIG "CONFIG_8f4a2b9c"
|
3. 多数据库使用建议
不推荐过度依赖 Redis 多数据库:
1 2 3 4 5 6 7 8
| 缺点: 1. 多数据库是单线程处理,多业务会相互干扰 2. 很多客户端支持不完善 3. 增加运维复杂度
推荐方案: - 使用 Key 前缀区分业务 - 或部署独立 Redis 实例
|
4. 批量操作提升效率
原生命令
1 2 3 4 5 6 7 8 9
| mget key1 key2 key3 key4 key5
mset key1 value1 key2 value2 key3 value3
hmget user:1 name age gender hmset user:1 name tom age 20
|
Pipeline 管道
1 2 3 4 5 6 7 8 9 10 11
| import redis
r = redis.Redis() pipe = r.pipeline()
for i in range(1000): pipe.set(f"key:{i}", f"value:{i}")
pipe.execute()
|
注意区别:
| 特性 |
原生命令 |
Pipeline |
| 原子性 |
是 |
否(打包发送) |
| 命令类型 |
相同 |
可混合 |
| 服务端支持 |
是 |
需客户端支持 |
| 适用场景 |
相同操作 |
批量不同操作 |
建议批量大小: 每次 500 个以内(根据元素大小调整)
5. 事务使用建议
Redis 事务的局限:
1 2 3
| - 不支持回滚 - 集群版本要求 Key 在同一 Slot - 事务期间其他命令可能被阻塞
|
集群版本事务解决方案(Hashtag):
1 2 3 4 5 6 7 8
| MULTI SET {user}:1:name tom SET {user}:1:age 20 SET {user}:1:city beijing EXEC
|
6. Lua 脚本使用规范
集群版本 Lua 要求:
1 2 3 4 5 6 7 8 9 10 11
| local key1 = "user:" .. KEYS[1] .. ":name" redis.call("SET", key1, ARGV[1])
redis.call("SET", "user:1:name", "tom") redis.call("SET", "order:100:name", "order1")
redis.call("SET", KEYS[1], ARGV[1]) redis.call("HSET", KEYS[2], "field", ARGV[2])
|
7. Monitor 命令使用
1 2 3 4 5 6 7 8
| redis-cli monitor
redis-cli monitor | grep "SET\|GET"
redis-cli slowlog get 10
|
性能优化建议
1. 内存优化
1 2 3 4 5 6 7
| redis-cli --big-keys redis-cli memory usage keyname
maxmemory 4gb maxmemory-policy allkeys-lru
|
2. 持久化优化
1 2 3 4 5 6 7 8 9 10 11
| save 900 1 save 300 10 save 60 10000
appendonly yes appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
|
3. 连接池配置
1 2 3 4 5 6 7
| JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(100); poolConfig.setMaxIdle(50); poolConfig.setMinIdle(10); poolConfig.setMaxWaitMillis(3000); poolConfig.setTestOnBorrow(true);
|
集群使用规范
Key 分布策略
1 2 3 4 5 6 7 8 9 10 11 12
|
user:{1001}:profile user:{1001}:settings user:{1001}:orders
order:{2001}:info order:{2001}:items order:{2001}:payment
|
读写分离
1 2 3 4 5 6 7 8
| JedisCluster jedisCluster = new JedisCluster(nodes, poolConfig);
String value = jedisCluster.get("key");
jedisCluster.set("key", "value");
|
监控与运维
关键监控指标
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| redis-cli info clients | grep connected_clients
redis-cli info memory | grep used_memory
redis-cli info stats | grep keyspace
redis-cli slowlog get 10
redis-cli --latency-history -i 1
|
告警阈值建议
| 指标 |
告警阈值 |
严重阈值 |
| 内存使用率 |
80% |
90% |
| 连接数使用率 |
80% |
95% |
| 命中率 |
< 90% |
< 80% |
| 慢查询数量 |
> 10/分钟 |
> 50/分钟 |
| 主从延迟 |
> 1秒 |
> 5秒 |
总结
Redis 高效使用核心要点:
- 键值设计:规范命名、控制大小、合理过期
- 命令使用:避免 O(N) 大操作、禁用高危命令
- 数据类型:选择合适类型、利用编码优化
- 批量操作:使用 Pipeline、控制批量大小
- 集群规范:Hash Tag 保证数据局部性
- 监控运维:关键指标监控、及时告警
遵循这些规范,可以构建高性能、稳定可靠的 Redis 应用。