Redis使用规范与架构选型记录

用Redis好几年了,整理下踩过的坑和实践经验。主要是键值怎么设计、哪些命令要小心、以及集群怎么选。

Key命名规范

推荐格式:业务名:表名:id

好例子:

1
2
3
ugc:video:1
user:profile:10086
cache:session:token123

设计原则:

原则 说明 示例
可读性 业务名前缀防冲突 user:{uid}:profile
简洁性 短Key省内存 u:{uid}:fr:m:{mid}
规范性 别用特殊字符 不用空格、换行、引号

长度优化对比:

1
2
3
4
5
# 优化前
user:{uid}:friends:messages:{mid}

# 优化后
u:{uid}:fr:m:{mid}

Value设计

拒绝BigKey

BigKey会堵网卡、拖慢查询。建议:

  • String类型:控制在10KB以内
  • Hash/List/Set/ZSet:元素别超5000个

错误示范:

1
2
# 200万个元素的list,作死
LPUSH big_list item1 item2 ... (2 million items)

渐进式删除:

1
2
3
HSCAN user:1:profile 0 COUNT 100
SSCAN tags:article 0 COUNT 100
ZSCAN rankings:game 0 COUNT 100

选对数据类型

低效做法:

1
2
3
SET user:1:name tom
SET user:1:age 19
SET user:1:favor football

高效做法:

1
HMSET user:1 name tom age 19 favor football

Key过期设置

Redis不是垃圾桶,建议都给Key设过期时间:

1
2
EXPIRE session:token123 3600
EXPIRE cache:user:10086 3500 # 错开100秒,防集中过期

命令使用注意

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
2
3
rename-command KEYS ""
rename-command FLUSHALL ""
rename-command FLUSHDB ""

用SCAN代替KEYS:

1
SCAN 0 MATCH user:* COUNT 100

批量操作

原生批量(原子):

1
2
MGET key1 key2 key3
MSET key1 v1 key2 v2 key3 v3

Pipeline(非原子,省RTT):

1
2
3
4
5
pipeline
GET key1
GET key2
GET key3
exec
特性 原生批量 Pipeline
原子性
命令混合 不支持 支持
客户端支持 必须支持 需双向支持

集群架构选型

方案1:主从复制

1
2
3
4
    Master(写)

┌────┴────┐
Slave1 Slave2(读)

优点:

  • 读写分离,读性能提升
  • Slave可级联,分担Master压力
  • 同步期间客户端还能查

缺点:

  • Master挂了要手动切
  • 主从延迟有数据一致性问题
  • 不能在线扩容

方案2:哨兵模式

1
2
3
4
5
6
7
8
9
10
Sentinel1
Sentinel2 ──┐
Sentinel3 │ 监控

┌─────────┐
│ Master │◄──┐
└────┬────┘ │
│ │ 故障转移
┌────┴────┐ │
Slave1 Slave2┘

优点:

  • 主从自动切换
  • Sentinel集群保障监控高可用

缺点:

  • 还是不能在线扩容
  • 配置复杂了点
  • 写能力还是单节点限制

方案3:Redis Cluster

1
2
3
4
5
6
7
8
9
10
  ┌─────┐     ┌─────┐     ┌─────┐
│ M1 │◄───►│ M2 │◄───►│ M3 │
│S1a │ │S2a │ │S3a │
└─────┘ └─────┘ └─────┘
│ │ │
┌─────┐ ┌─────┐ ┌─────┐
│S1b │ │S2b │ │S3b │
└─────┘ └─────┘ └─────┘

Slot: 0-5460 | 5461-10922 | 10923-16383

特点:

  • 节点互联,PING-PONG机制
  • 故障检测需半数节点确认
  • 客户端直连,无代理层
  • 16384个slot均匀分布

数据路由:

1
2
CLUSTER KEYSLOT user:10086    # 看Key在哪个slot
CLUSTER SLOTS # 看slot分布

开发规范

Lua脚本注意

Cluster模式下Lua有特殊要求:

1
2
3
4
5
6
-- 所有Key必须通过KEYS数组传递
local key1 = KEYS[1]
local key2 = KEYS[2]

-- 所有Key必须在同一个slot
-- 用hashtag:user:{10086}:profile 和 user:{10086}:order

事务使用

Redis事务不支持回滚,功能较弱。建议:

  • 复杂事务用Lua脚本代替
  • 集群版要求事务的Key在同一slot
  • 可用WATCH做乐观锁
1
2
3
4
MULTI
INCR counter
EXPIRE counter 3600
EXEC

连接池配置(Node.js)

1
2
3
4
5
6
7
8
9
10
11
12
const redis = require('redis');

const client = redis.createClient({
host: '127.0.0.1',
port: 6379,
password: 'yourpassword',
db: 0,
retry_max_delay: 5000,
max_attempts: 3,
connect_timeout: 60000,
lazyConnect: true
});

监控运维

1
2
3
4
SLOWLOG GET 10        # 慢查询
INFO memory # 内存情况
DBSIZE # Key数量
MONITOR # 实时监控(慎用)

内存优化策略:

  • ziplist:小数据量Hash/List用压缩编码
  • intset:整数集合优化存储
  • 合理设置TTL,防内存溢出

选型建议

场景 推荐架构
读多写少,能容忍手动切换 主从复制
需要高可用自动切换 哨兵模式
数据量大,需要水平扩展 Cluster

几点经验:

  1. Key命名要规范、短、不冲突
  2. 别搞BigKey,Hash比String省内存
  3. 生产环境禁掉KEYS、FLUSHALL
  4. Cluster用hashtag保证Key在同一slot
  5. 做好监控,关注内存和慢查询