用Redis好几年了,整理下踩过的坑和实践经验。主要是键值怎么设计、哪些命令要小心、以及集群怎么选。
Key命名规范
推荐格式:业务名:表名:id
好例子:
1 | ugc:video:1 |
设计原则:
| 原则 | 说明 | 示例 |
|---|---|---|
| 可读性 | 业务名前缀防冲突 | user:{uid}:profile |
| 简洁性 | 短Key省内存 | u:{uid}:fr:m:{mid} |
| 规范性 | 别用特殊字符 | 不用空格、换行、引号 |
长度优化对比:
1 | # 优化前 |
Value设计
拒绝BigKey
BigKey会堵网卡、拖慢查询。建议:
- String类型:控制在10KB以内
- Hash/List/Set/ZSet:元素别超5000个
错误示范:
1 | # 200万个元素的list,作死 |
渐进式删除:
1 | HSCAN user:1:profile 0 COUNT 100 |
选对数据类型
低效做法:
1 | SET user:1:name tom |
高效做法:
1 | HMSET user:1 name tom age 19 favor football |
Key过期设置
Redis不是垃圾桶,建议都给Key设过期时间:
1 | EXPIRE session:token123 3600 |
命令使用注意
O(N)命令要小心
| 命令 | 复杂度 | 替代方案 |
|---|---|---|
| HGETALL | O(N) | HSCAN |
| LRANGE | O(S+N) | 分批获取 |
| SMEMBERS | O(N) | SSCAN |
| ZRANGE | O(log(N)+M) | ZSCAN |
| SINTER | O(N*M) | 分批计算 |
危险命令禁用
生产环境禁掉这些:
1 | rename-command KEYS "" |
用SCAN代替KEYS:
1 | SCAN 0 MATCH user:* COUNT 100 |
批量操作
原生批量(原子):
1 | MGET key1 key2 key3 |
Pipeline(非原子,省RTT):
1 | pipeline |
| 特性 | 原生批量 | Pipeline |
|---|---|---|
| 原子性 | 是 | 否 |
| 命令混合 | 不支持 | 支持 |
| 客户端支持 | 必须支持 | 需双向支持 |
集群架构选型
方案1:主从复制
1 | Master(写) |
优点:
- 读写分离,读性能提升
- Slave可级联,分担Master压力
- 同步期间客户端还能查
缺点:
- Master挂了要手动切
- 主从延迟有数据一致性问题
- 不能在线扩容
方案2:哨兵模式
1 | Sentinel1 |
优点:
- 主从自动切换
- Sentinel集群保障监控高可用
缺点:
- 还是不能在线扩容
- 配置复杂了点
- 写能力还是单节点限制
方案3:Redis Cluster
1 | ┌─────┐ ┌─────┐ ┌─────┐ |
特点:
- 节点互联,PING-PONG机制
- 故障检测需半数节点确认
- 客户端直连,无代理层
- 16384个slot均匀分布
数据路由:
1 | CLUSTER KEYSLOT user:10086 # 看Key在哪个slot |
开发规范
Lua脚本注意
Cluster模式下Lua有特殊要求:
1 | -- 所有Key必须通过KEYS数组传递 |
事务使用
Redis事务不支持回滚,功能较弱。建议:
- 复杂事务用Lua脚本代替
- 集群版要求事务的Key在同一slot
- 可用
WATCH做乐观锁
1 | MULTI |
连接池配置(Node.js)
1 | const redis = require('redis'); |
监控运维
1 | SLOWLOG GET 10 # 慢查询 |
内存优化策略:
- ziplist:小数据量Hash/List用压缩编码
- intset:整数集合优化存储
- 合理设置TTL,防内存溢出
选型建议
| 场景 | 推荐架构 |
|---|---|
| 读多写少,能容忍手动切换 | 主从复制 |
| 需要高可用自动切换 | 哨兵模式 |
| 数据量大,需要水平扩展 | Cluster |
几点经验:
- Key命名要规范、短、不冲突
- 别搞BigKey,Hash比String省内存
- 生产环境禁掉KEYS、FLUSHALL
- Cluster用hashtag保证Key在同一slot
- 做好监控,关注内存和慢查询