Redis¶
从基础到高级到分布式,涵盖安装、数据结构、持久化、高可用、分布式等核心内容。以 Redis 7.x 为基准。
目录¶
一、基础篇¶
1.1 安装与初始化¶
# Ubuntu/Debian
sudo apt install redis-server
# 启动 & 开机自启
sudo systemctl start redis
sudo systemctl enable redis
# 源码编译安装(推荐,版本可控)
wget https://download.redis.io/redis-stable.tar.gz
tar xzf redis-stable.tar.gz && cd redis-stable
make && make install
# 启动
redis-server # 默认配置启动
redis-server /etc/redis/redis.conf # 指定配置文件
# 客户端连接
redis-cli
redis-cli -h 127.0.0.1 -p 6379 -a password
redis-cli -n 1 # 选择数据库1
常用 CLI 命令
命令 |
说明 |
|---|---|
|
测试连接,返回 PONG |
|
切换到数据库1(默认16个库) |
|
当前库键数量 |
|
清空当前库(慎用) |
|
清空所有库(慎用) |
|
查看服务器信息 |
|
查看所有配置 |
|
实时监控所有命令(调试用) |
1.2 基本配置¶
# redis.conf 核心配置
# 网络
bind 127.0.0.1 # 监听地址,生产建议绑定内网 IP
port 6379
protected-mode yes # 保护模式,无密码时禁止外网访问
timeout 300 # 客户端空闲超时(秒)
# 认证
requirepass yourpassword
# 数据库数量
databases 16
# 内存
maxmemory 4gb # 最大内存限制
maxmemory-policy allkeys-lru # 内存淘汰策略(见优化篇)
# 持久化
save 3600 1
save 300 100
save 60 10000
appendonly no # AOF 默认关闭
# 日志
loglevel notice
logfile /var/log/redis/redis.log
# 后台运行
daemonize yes
1.3 数据类型与使用¶
这一节按工程实践拆成「定义 + 适用场景 + 增删改查 + 注意点」。
String(字符串)¶
定义:最通用类型,字符串/整数/浮点/二进制都可放,单 value 最大 512MB。
典型场景:缓存对象 JSON、计数器、分布式锁 token、短期配置。
# 增(Create)
SET user:1 '{"name":"alice"}'
SET session:1 "token" EX 1800
SET lock:order:1001 "uuid-abc" NX PX 30000
# 查(Read)
GET user:1
MGET user:1 user:2
STRLEN user:1
# 改(Update)
SET user:1 '{"name":"alice","vip":1}' XX
GETSET user:1 '{"name":"alice","vip":2}'
APPEND user:1 ',"level":3'
# 删(Delete)
DEL user:1
UNLINK user:2
# 原子计数
INCR pv:home
INCRBY counter 10
DECRBY counter 2
INCRBYFLOAT price 1.25
Hash(哈希)¶
定义:key 下再存 field-value,适合对象局部更新。
典型场景:用户画像、商品信息、会话属性。
# 增
HSET user:1 name "Alice" age 25 city "Shanghai"
HSETNX user:1 phone "13800000000"
# 查
HGET user:1 name
HMGET user:1 name age
HGETALL user:1
HEXISTS user:1 city
HLEN user:1
# 改
HSET user:1 age 26
HINCRBY user:1 login_count 1
# 删
HDEL user:1 city
DEL user:1
List(列表)¶
定义:有序可重复,适合队列/时间线。
典型场景:异步任务队列、最近访问记录、消息缓冲。
# 增
LPUSH queue:email task1 task2
RPUSH queue:email task3
# 查
LRANGE queue:email 0 -1
LLEN queue:email
LINDEX queue:email 0
# 改
LSET queue:email 0 task1_retry
LINSERT queue:email BEFORE task2 task1_5
# 删
LPOP queue:email
RPOP queue:email
LREM queue:email 1 task3
# 阻塞消费
BLPOP queue:email 5
BRPOP queue:email 5
Set(集合)¶
定义:无序去重集合。
典型场景:标签系统、共同好友、去重集合。
# 增
SADD tags:user:1 "python" "redis" "backend"
# 查
SMEMBERS tags:user:1
SISMEMBER tags:user:1 "python"
SCARD tags:user:1
# 改(集合本质多为增删;更新通常是先删后加)
SREM tags:user:1 "backend"
SADD tags:user:1 "ai"
# 删
SREM tags:user:1 "python"
DEL tags:user:1
# 集合运算
SINTER tags:user:1 tags:user:2
SUNION tags:user:1 tags:user:2
SDIFF tags:user:1 tags:user:2
Sorted Set(有序集合 ZSet)¶
定义:member 唯一,按 score 排序。
典型场景:排行榜、延迟任务、优先级队列。
# 增
ZADD rank:game 100 "Alice" 90 "Bob" 95 "Charlie"
# 查
ZSCORE rank:game "Alice"
ZRANK rank:game "Alice"
ZREVRANK rank:game "Alice"
ZRANGE rank:game 0 -1 WITHSCORES
ZREVRANGE rank:game 0 9 WITHSCORES
ZRANGEBYSCORE rank:game 90 100 WITHSCORES
ZCOUNT rank:game 90 100
# 改
ZINCRBY rank:game 5 "Bob"
# 删
ZREM rank:game "Charlie"
ZREMRANGEBYSCORE rank:game -inf 59
选型速记
单值缓存/计数器:
String对象字段更新频繁:
Hash队列:
List(简单)或Stream(可靠)去重关系:
Set排名/定时任务:
ZSet
1.4 键管理¶
# 查找
KEYS user:* # 模糊匹配(生产禁用,阻塞)
SCAN 0 MATCH user:* COUNT 100 # 非阻塞迭代(推荐)
# 信息
TYPE key # 查看类型
OBJECT ENCODING key # 查看底层编码
OBJECT IDLETIME key # 多久没访问(秒)
DEBUG OBJECT key # 详细信息
# 过期
EXPIRE key 60 # 设置过期(秒)
PEXPIRE key 60000 # 毫秒
EXPIREAT key 1735689600 # 指定时间戳
TTL key # 剩余秒数(-1永不过期,-2已不存在)
PERSIST key # 移除过期时间
# 其他
DEL key1 key2 # 删除(同步)
UNLINK key1 key2 # 删除(异步,大key推荐)
RENAME key newkey # 重命名
EXISTS key # 是否存在
COPY key destkey # 复制
MOVE key 1 # 移动到数据库1
1.5 过期策略¶
这一节分清两个容易混淆的问题:
过期键删除策略:TTL 到期后,Redis 何时真正删除 key。
内存淘汰策略:内存达到
maxmemory后,Redis 为了继续写入淘汰哪些 key。
过期键删除策略(TTL 到期后的删除机制)¶
惰性删除(lazy):访问该 key 时才判断是否过期并删除。
优点:CPU 开销低。
风险:不被访问的过期 key 会短时间占内存。
定期删除(active expire):后台周期抽样扫描带 TTL 的 key 并删除。
优点:持续回收“沉默过期 key”。
风险:扫描强度太高会抬升 CPU。
Redis 实际是两者结合。对于 TTL 很多的业务,建议关注这两个指标:
INFO stats
# expired_keys 已删除过期键总数
# evicted_keys 因内存淘汰删除的键总数
可调参数(Redis 7):
# 过期扫描努力程度,1~10,越大越激进(默认 1)
active-expire-effort 1
# 异步删除,减少主线程阻塞
lazyfree-lazy-expire yes
内存淘汰策略(maxmemory 打满后的淘汰机制)¶
先设置内存上限:
maxmemory 8gb
maxmemory-policy allkeys-lfu
策略说明与建议:
策略 |
定义 |
适用场景 |
风险 |
|---|---|---|---|
|
不淘汰,写命令报错 |
严格一致性场景 |
缓存写入失败率上升 |
|
全量 key 中淘汰最近最少使用 |
通用缓存 |
无法识别长期低频但关键数据 |
|
全量 key 中淘汰低频 key |
热点明显、访问分层明显 |
新热点升温有短暂滞后 |
|
全量随机淘汰 |
压测/特殊场景 |
命中率不可控 |
|
仅 TTL key 中 LRU 淘汰 |
持久化 key 与缓存 key 混存 |
无 TTL key 不受控增长 |
|
仅 TTL key 中 LFU 淘汰 |
同上 |
同上 |
|
仅 TTL key 中优先淘汰短 TTL |
TTL 设计严格场景 |
热点短 TTL 可能被误淘汰 |
|
仅 TTL key 中随机淘汰 |
很少使用 |
命中率差 |
生产建议
纯缓存库优先:
allkeys-lfu(热点更稳定)或allkeys-lru(简单稳妥)。混合存储库(缓存 + 持久业务 key)需慎用
allkeys-*,先拆库更安全。出现突发淘汰时,先看:
used_memory,maxmemoryevicted_keys增长速率大 key / 热 key 分布(
--bigkeys/--hotkeys)
二、进阶篇¶
2.1 持久化¶
持久化的核心不是“开不开”,而是如何在 性能、恢复时间、可接受数据丢失窗口(RPO) 之间做权衡。
RDB(快照)¶
机制:按条件触发
BGSAVE,fork 子进程生成快照。优点:恢复快、文件紧凑、适合备份与灾备传输。
风险:两次快照间可能丢数据(分钟级)。
工程注意:fork 会触发写时复制(COW),内存紧张时可能放大内存压力。
# redis.conf
save 3600 1 # 3600秒内至少1个key变化则快照
save 300 100
save 60 10000
dbfilename dump.rdb
dir /var/lib/redis
BGSAVE # 后台异步保存(fork子进程)
SAVE # 同步保存(阻塞,慎用)
LASTSAVE # 上次保存时间戳
INFO persistence
AOF(追加日志)¶
机制:每条写命令追加到 AOF,重启时回放。
优点:数据更完整,RPO 可压到秒级。
风险:文件更大,重写与回放成本高于 RDB。
关键参数:
appendfsync决定 fsync 策略与性能/安全权衡。
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec # always / everysec / no
# AOF 重写(压缩日志文件)
auto-aof-rewrite-percentage 100 # 文件增长100%时触发
auto-aof-rewrite-min-size 64mb # 最小触发大小
# Redis 7.0 多文件 AOF
aof-use-rdb-preamble yes # 混合持久化
BGREWRITEAOF # 手动触发 AOF 重写
INFO persistence
混合持久化(推荐)¶
aof-use-rdb-preamble yes # 开启混合模式
appendonly yes
重启恢复时:先加载 RDB 快照部分,再回放增量 AOF 命令,兼顾恢复速度和数据完整性。
故障恢复与一致性建议¶
# 检查 AOF 文件
redis-check-aof --fix appendonly.aof
# 检查 RDB 文件
redis-check-rdb dump.rdb
线上建议:主库开 AOF(
everysec)+ 定时 RDB 备份。备份建议:RDB/AOF 文件异地存储 + 定期恢复演练(只备份不演练等于没备份)。
高写入业务建议评估磁盘类型与
appendfsync,避免 IO 抖动放大尾延迟。
三种方式对比
RDB |
AOF |
混合 |
|
|---|---|---|---|
数据安全 |
低(分钟级丢失) |
高(秒级丢失) |
高 |
恢复速度 |
快 |
慢 |
快 |
文件大小 |
小 |
大 |
中 |
对性能影响 |
低(fork) |
低(everysec) |
低 |
生产推荐 |
可选 |
可选 |
✅ 推荐 |
2.2 事务¶
Redis 事务不支持回滚,仅保证命令顺序执行、原子提交。
MULTI # 开启事务
SET k1 v1
SET k2 v2
INCR counter
EXEC # 提交执行
DISCARD # 放弃事务
# WATCH 乐观锁(CAS)
WATCH balance # 监视 key
MULTI
DECRBY balance 100
EXEC # 若 balance 被其他客户端修改,返回 nil(失败)
生产中复杂原子操作推荐用 Lua 脚本 代替事务。
2.3 Lua 脚本¶
Lua 脚本在 Redis 中原子执行,不会被其他命令打断。
# 执行脚本
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalue
# 加载脚本(返回 SHA1)
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
EVALSHA sha1值 1 mykey
# 清除缓存脚本
SCRIPT FLUSH
-- 分布式锁释放(原子操作)
local val = redis.call('GET', KEYS[1])
if val == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
2.4 管道(Pipeline)¶
将多个命令打包一次性发送,减少网络往返次数,大幅提升批量操作性能。
import redis
r = redis.Redis()
# 不用 pipeline:N 次网络请求
for i in range(1000):
r.set(f'key:{i}', i)
# 用 pipeline:1 次网络请求
pipe = r.pipeline()
for i in range(1000):
pipe.set(f'key:{i}', i)
pipe.execute()
Pipeline 不是原子的,命令之间可能被插入其他命令。需要原子性用 Lua 脚本。
2.5 发布订阅(Pub/Sub)¶
# 订阅频道
SUBSCRIBE news sports
# 模式订阅
PSUBSCRIBE news.*
# 发布消息
PUBLISH news "breaking news content"
# 查看订阅信息
PUBSUB CHANNELS # 所有活跃频道
PUBSUB NUMSUB news # 频道订阅数
Pub/Sub 消息不持久化,消费者离线会丢消息。需要持久化消息队列用 Stream。
2.6 Stream(消息流)¶
Redis 5.0 引入,类似 Kafka,支持持久化、消费组、消息确认。
# 生产消息
XADD mystream * name "Alice" action "login" # * 自动生成 ID
XADD mystream 1000-0 name "Bob" action "logout" # 指定 ID
# 消费消息
XREAD COUNT 10 STREAMS mystream 0 # 从头读取
XREAD COUNT 10 BLOCK 0 STREAMS mystream $ # 阻塞等待新消息
# 消费组
XGROUP CREATE mystream grp1 $ MKSTREAM # 创建消费组(流不存在时自动创建)
XREADGROUP GROUP grp1 consumer1 COUNT 10 STREAMS mystream > # 读取未分配消息
XACK mystream grp1 消息ID # 确认消费
# 查看
XLEN mystream # 消息数量
XRANGE mystream - + # 查看所有消息
XPENDING mystream grp1 - + 10 # 查看未确认消息
2.7 地理空间(GEO)¶
# 添加地理位置(经度, 纬度, 成员名)
GEOADD cities 116.40 39.90 "Beijing"
GEOADD cities 121.47 31.23 "Shanghai"
# 计算两点距离
GEODIST cities Beijing Shanghai km # 返回千米
# 获取坐标
GEOPOS cities Beijing
# 附近的成员
GEOSEARCH cities FROMMEMBER Beijing BYRADIUS 1000 km ASC COUNT 5
# 获取 GeoHash
GEOHASH cities Beijing
2.8 HyperLogLog¶
用极小内存(约 12KB)估算大量数据的基数(不重复元素数量),误差约 0.81%。
PFADD uv:20250101 user1 user2 user3 user1 # 添加
PFCOUNT uv:20250101 # 估算基数(去重后约3)
PFMERGE uv:total uv:20250101 uv:20250102 # 合并多个
# 典型场景:UV 统计、独立访客数
2.9 Bitmap¶
本质是字符串,按位操作,极省内存(存 1 亿用户签到仅需约 12MB)。
SETBIT sign:user:1 20250101 1 # 用户1在 20250101 签到
GETBIT sign:user:1 20250101 # 查询是否签到
BITCOUNT sign:user:1 # 签到总天数
# 统计范围内的 1 的个数
BITCOUNT sign:user:1 0 30
# 位运算(求多个 bitmap 的交集/并集)
BITOP AND destkey key1 key2 # 同时签到的用户
BITOP OR destkey key1 key2
# 查找第一个 0 或 1 的位置
BITPOS sign:user:1 0 # 第一个未签到的天
三、性能优化篇¶
3.1 内存优化¶
# 查看内存使用
INFO memory
MEMORY USAGE key # 单个 key 占用的内存(字节)
MEMORY DOCTOR # 内存问题诊断
# 内存碎片率
# mem_fragmentation_ratio > 1.5 说明碎片严重
# 解决:重启 或 CONFIG SET activedefrag yes(在线整理)
# 开启主动碎片整理(Redis 4.0+)
activedefrag yes
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
3.2 数据结构选型¶
Redis 底层会根据数据量自动切换编码,了解后可主动优化:
数据类型 |
少量数据(压缩) |
大量数据 |
|---|---|---|
String |
embstr(≤44字节) |
raw |
Hash |
listpack(≤128字段,值≤64字节) |
hashtable |
List |
listpack(≤128元素,值≤64字节) |
quicklist |
Set |
listpack / intset |
hashtable |
ZSet |
listpack(≤128元素,值≤64字节) |
skiplist |
# 调整阈值(让更多数据用压缩格式,节省内存)
hash-max-listpack-entries 128
hash-max-listpack-value 64
zset-max-listpack-entries 128
list-max-listpack-size -2 # 每个 quicklist 节点最大 8KB
3.3 大 Key 与热 Key¶
大 Key:String 超过 10KB,集合类型超过 10000 个元素。
# 扫描大 key(非阻塞)
redis-cli --bigkeys
# 查找大 key(更精细)
redis-cli --memkeys
# 删除大 key(用 UNLINK 异步删除,避免阻塞)
UNLINK bigkey
大 Key 解决方案
Hash/Set/ZSet:拆分为多个小 key(
user:1:base、user:1:extra)String:压缩数据,或拆分存储
List:分页存储
热 Key:某个 key 被极高频访问,单节点压力大。
# 监控热 key
redis-cli --hotkeys
热 Key 解决方案
本地缓存(JVM 缓存 / Python 内存缓存)
复制多份:
hot:key:0、hot:key:1… 访问时随机选Redis Cluster 读从节点
3.4 连接池¶
# Python redis-py 连接池
import redis
pool = redis.ConnectionPool(
host='localhost',
port=6379,
password='yourpassword',
max_connections=50,
decode_responses=True
)
r = redis.Redis(connection_pool=pool)
// Java Jedis 连接池
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(50);
config.setMaxIdle(10);
config.setMinIdle(5);
JedisPool pool = new JedisPool(config, "localhost", 6379);
3.5 配置调优¶
# 网络
tcp-backlog 511
tcp-keepalive 300
# 线程(Redis 6.0+ 支持 IO 多线程)
io-threads 4 # CPU 核数,不超过8
io-threads-do-reads yes
# 内存
maxmemory 8gb
maxmemory-policy allkeys-lru
# 惰性删除(异步)
lazyfree-lazy-eviction yes # 淘汰时异步删除
lazyfree-lazy-expire yes # 过期时异步删除
lazyfree-lazy-server-del yes # Redis 内部 DEL 场景异步删除
lazyfree-lazy-user-del yes # 用户执行 DEL 时也异步删除(UNLINK 效果)
# 快照
rdbcompression yes # RDB 压缩
rdbchecksum yes # RDB 校验和
# 禁用高危命令
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command KEYS ""
rename-command CONFIG "CONFIG_SAFE_CMD"
3.6 慢查询日志¶
慢查询日志记录的是 命令在 Redis 主线程执行的耗时,不包含网络收发耗时。
它是定位 CPU 型慢命令、错误命令模型(如 KEYS *)和大 key 操作的第一入口。
# 配置(命令执行时间超过阈值记录,单位微秒)
CONFIG SET slowlog-log-slower-than 10000 # 10ms
CONFIG SET slowlog-max-len 128
# 查看慢查询
SLOWLOG GET 10 # 最近10条
SLOWLOG LEN # 慢查询数量
SLOWLOG RESET # 清空
慢日志字段常见含义(SLOWLOG GET 返回):
id:慢日志 ID。timestamp:执行时间。duration:耗时(微秒)。command:完整命令参数。client_addr/client_name:来源客户端。
实战分析流程(建议固化为值班手册)¶
先取最近 50~200 条慢日志,按命令类型聚类。
识别高风险命令:
全量扫描:
KEYS、HGETALL(超大 hash)大范围删除:
DEL大 key(应改UNLINK)大范围聚合:
ZRANGE巨大区间、SMEMBERS大集合
结合
--bigkeys、MEMORY USAGE判断是否数据模型问题。若慢但无明显大 key,排查 AOF 重写、fork、磁盘抖动和 CPU 抢占。
阈值建议¶
开发/测试:
1000~5000微秒(更敏感)。生产:
5000~20000微秒(按业务基线调)。建议配合监控告警:
slowlog_len持续增长 + P99 延迟升高联动告警。
四、高可用篇¶
4.1 主从复制¶
# 从节点配置
replicaof master_ip 6379
masterauth master_password
replica-read-only yes # 从节点只读
# 主节点
min-replicas-to-write 1 # 至少1个从节点同步才允许写(可选)
min-replicas-max-lag 10 # 从节点最大延迟秒数
# 运行时设置
REPLICAOF master_ip 6379
REPLICAOF NO ONE # 解除主从,提升为主节点
# 查看复制状态
INFO replication
复制原理
从节点连接主节点,发送
PSYNC主节点执行
BGSAVE生成 RDB,发送给从节点从节点加载 RDB,之后主节点持续发送命令(命令传播)
断线重连后,通过
repl_backlog实现部分重同步
4.2 哨兵模式(Sentinel)¶
哨兵监控主从节点,自动故障转移。
# sentinel.conf
sentinel monitor mymaster 127.0.0.1 6379 2 # 监控主节点,2个哨兵同意才故障转移
sentinel auth-pass mymaster yourpassword
sentinel down-after-milliseconds mymaster 30000 # 30秒无响应判定主观下线
sentinel failover-timeout mymaster 180000 # 故障转移超时
sentinel parallel-syncs mymaster 1 # 故障转移时同时同步的从节点数
# 启动哨兵
redis-sentinel /etc/redis/sentinel.conf
# 或
redis-server /etc/redis/sentinel.conf --sentinel
# 查看哨兵状态
redis-cli -p 26379 SENTINEL masters
redis-cli -p 26379 SENTINEL slaves mymaster
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
哨兵工作原理
哨兵1 哨兵2 哨兵3
↓ ↓ ↓
监控主节点心跳
主节点无响应 → 主观下线(单个哨兵判断)
超过 quorum 数量哨兵同意 → 客观下线
哨兵选举 Leader → Leader 执行故障转移
选举新主节点 → 通知其他从节点和客户端
4.3 故障切换¶
手动故障转移
# 强制切换主节点(哨兵模式)
redis-cli -p 26379 SENTINEL failover mymaster
客户端连接哨兵(Python)
from redis.sentinel import Sentinel
sentinel = Sentinel([
('sentinel1', 26379),
('sentinel2', 26379),
('sentinel3', 26379),
], socket_timeout=0.1)
master = sentinel.master_for('mymaster', password='yourpassword')
slave = sentinel.slave_for('mymaster', password='yourpassword')
master.set('key', 'value')
slave.get('key')
4.4 备份与恢复¶
# RDB 备份(复制 dump.rdb 即可)
BGSAVE
cp /var/lib/redis/dump.rdb /backup/dump-$(date +%Y%m%d).rdb
# AOF 备份
cp /var/lib/redis/appendonly.aof /backup/
# 恢复:停止 Redis,替换数据文件,重启即可
sudo systemctl stop redis
cp /backup/dump.rdb /var/lib/redis/dump.rdb
sudo systemctl start redis
# 定时备份脚本
0 2 * * * redis-cli BGSAVE && sleep 5 && cp /var/lib/redis/dump.rdb /backup/dump-$(date +\%Y\%m\%d).rdb
五、分布式篇¶
5.1 Redis Cluster¶
Redis 官方分布式方案,数据自动分片到多个节点。
核心概念
共 16384 个哈希槽,每个 key 根据 CRC16 映射到对应槽
每个主节点负责一部分槽,从节点做备份
客户端请求错误节点时,节点返回 MOVED 指令重定向
# 创建集群(3主3从)
redis-cli --cluster create \
127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \
127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \
--cluster-replicas 1
# 查看集群状态
redis-cli -p 7001 CLUSTER INFO
redis-cli -p 7001 CLUSTER NODES
# 添加节点
redis-cli --cluster add-node new_ip:port existing_ip:port
# 重新分片
redis-cli --cluster reshard 127.0.0.1:7001
# 删除节点
redis-cli --cluster del-node 127.0.0.1:7001 node_id
Cluster 配置
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000 # 节点超时时间(ms)
cluster-require-full-coverage no # 部分节点故障时是否继续服务
注意事项
不支持多 key 操作(
MSET、KEYS等),除非 key 在同一槽使用 Hash Tag 强制 key 落到同一槽:
{user:1}:name、{user:1}:age不支持
SELECT,只有 db0
5.2 分布式锁¶
import redis
import uuid
import time
r = redis.Redis()
def acquire_lock(lock_name, expire=30):
identifier = str(uuid.uuid4())
result = r.set(lock_name, identifier, nx=True, ex=expire)
return identifier if result else None
def release_lock(lock_name, identifier):
lua = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
return r.eval(lua, 1, lock_name, identifier)
# 使用
lock_id = acquire_lock("order:1001")
if lock_id:
try:
# 业务处理
pass
finally:
release_lock("order:1001", lock_id)
Redlock 算法(多节点强一致)
向 N 个独立 Redis 节点加锁
成功节点数 > N/2 + 1 才算加锁成功
释放时向所有节点释放
5.3 分布式限流¶
固定窗口
-- Lua 脚本实现原子计数限流
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = redis.call('INCR', key)
if current == 1 then
redis.call('EXPIRE', key, 60) -- 第一次设置过期
end
if current > limit then
return 0 -- 超出限制
else
return 1 -- 允许
end
滑动窗口(ZSet 实现)
def is_allowed(user_id, limit=100, window=60):
key = f"rate:{user_id}"
now = time.time()
pipe = r.pipeline()
pipe.zremrangebyscore(key, 0, now - window) # 删除窗口外的记录
pipe.zadd(key, {str(uuid.uuid4()): now}) # 添加当前请求
pipe.zcard(key) # 统计窗口内请求数
pipe.expire(key, window)
results = pipe.execute()
return results[2] <= limit
令牌桶(Redis + Lua)
local key = KEYS[1]
local capacity = tonumber(ARGV[1]) -- 桶容量
local rate = tonumber(ARGV[2]) -- 每秒填充速率
local now = tonumber(ARGV[3])
local bucket = redis.call('HMGET', key, 'tokens', 'last_time')
local tokens = tonumber(bucket[1]) or capacity
local last_time = tonumber(bucket[2]) or now
-- 计算新增令牌
local delta = math.max(0, now - last_time) * rate
tokens = math.min(capacity, tokens + delta)
if tokens >= 1 then
tokens = tokens - 1
redis.call('HMSET', key, 'tokens', tokens, 'last_time', now)
return 1
else
return 0
end
5.4 常见分布式方案对比¶
方案 |
类型 |
适用场景 |
优点 |
缺点 |
|---|---|---|---|---|
主从复制 |
主从 |
读写分离 |
简单,原生支持 |
手动切换,单主写 |
Sentinel |
高可用 |
自动故障转移 |
自动切换 |
不能水平扩展 |
Cluster |
分片+高可用 |
海量数据、高并发 |
水平扩展 |
运维复杂,多key受限 |
Codis |
代理分片 |
兼容老客户端 |
对客户端透明 |
已不活跃 |
Twemproxy |
代理分片 |
简单分片 |
轻量 |
功能有限 |
六、常见业务场景¶
6.1 缓存¶
def get_user(user_id):
key = f"user:{user_id}"
# 先查缓存
data = r.get(key)
if data:
return json.loads(data)
# 缓存未命中,查数据库
user = db.query(f"SELECT * FROM users WHERE id = {user_id}")
if user:
r.setex(key, 3600, json.dumps(user)) # 缓存1小时
return user
缓存三大问题
问题 |
定义 |
典型现象 |
常用方案 |
|---|---|---|---|
缓存穿透 |
请求的 key 在缓存和 DB 都不存在 |
DB 被无效请求打满 |
布隆过滤器 + 缓存空值 + 参数校验 |
缓存击穿 |
单个热点 key 失效瞬间大量并发回源 |
某个接口 RT 与 DB QPS 突刺 |
互斥重建(singleflight)+ 热点永不过期异步刷新 |
缓存雪崩 |
大批 key 同时失效或 Redis 故障 |
多接口同时回源,级联故障 |
TTL 随机抖动 + 多级缓存 + 限流降级 + 预热 |
1)缓存穿透(不存在数据被反复打)¶
定义:请求命中不存在的业务 ID,缓存 miss 后每次都穿到 DB。
解决策略:
布隆过滤器提前拦截不可能存在的 key。
miss 后缓存空值(短 TTL,如 30~120 秒)。
参数合法性校验(ID 格式、范围、签名)。
2)缓存击穿(热点 key 过期瞬间)¶
定义:某个热点 key 到期时,瞬时并发全部回源 DB。
解决策略:
互斥锁重建:只允许一个线程回源,其它线程短暂等待/快速失败。
热点逻辑过期:value 内含逻辑时间,后台异步刷新,前台可短时返回旧值。
对核心热点做主动续期与预热。
3)缓存雪崩(系统性失效)¶
定义:大量 key 同时失效,或 Redis 集群不可用,导致全量回源。
解决策略:
TTL 加随机值:
base_ttl + rand(0, jitter)。多级缓存:本地缓存(进程内)+ Redis + DB。
降级限流:限并发回源、熔断非核心接口、兜底默认值。
Redis 高可用:主从 + Sentinel/Cluster,避免单点。
缓存一致性补充(高级场景常问)¶
优先用 Cache-Aside:先更新 DB,再删除缓存(而不是更新缓存)。
为降低并发下脏读窗口,可配合延迟双删(按场景谨慎使用)。
强一致要求极高时,不要把 Redis 当唯一真相源。
6.2 排行榜¶
# 实时排行榜(ZSet)
ZADD leaderboard 1500 "Alice"
ZADD leaderboard 1200 "Bob"
ZINCRBY leaderboard 100 "Bob" # Bob 加100分
ZREVRANGE leaderboard 0 9 WITHSCORES # 前10名
# 获取用户排名
ZREVRANK leaderboard "Bob"
6.3 消息队列¶
Redis 做 MQ 时,不同模型能力差异很大:
模型 |
优点 |
局限 |
适用 |
|---|---|---|---|
List + BRPOP |
简单、吞吐高 |
无消费确认、失败重试弱 |
轻量异步任务 |
Pub/Sub |
极低延迟广播 |
离线即丢、不可追溯 |
实时通知 |
Stream + Consumer Group |
可持久化、可 ACK、可重投 |
语义更复杂 |
可靠任务队列 |
List 简易队列(轻量场景)¶
# 生产
LPUSH queue:task '{"id":1,"type":"email"}'
# 消费
BRPOP queue:task 0
Stream 可靠队列(生产推荐)¶
# 1) 生产消息
XADD mq:order * event create_order order_id 1001
# 2) 创建消费组(仅首次)
XGROUP CREATE mq:order workers 0 MKSTREAM
# 3) 消费新消息
XREADGROUP GROUP workers consumer-1 COUNT 10 BLOCK 2000 STREAMS mq:order >
# 4) 成功后 ACK
XACK mq:order workers 1712345678901-0
# 5) 查看待确认消息(PEL)
XPENDING mq:order workers
失败重试与“死信”思路¶
消费失败不 ACK,消息留在 Pending Entries List(PEL)。
超时后用
XCLAIM/XAUTOCLAIM转移给新消费者重试。重试次数超过阈值可转存到
mq:order:dead(死信流)做人工补偿。
幂等建议(必须)¶
不能假设“只消费一次”,要按“至少一次”设计。
使用业务唯一 ID(如
order_id)做幂等去重,处理前先判重。
6.4 Session 存储¶
# 存储 session
session_id = str(uuid.uuid4())
r.setex(f"session:{session_id}", 1800, json.dumps({"user_id": 1, "role": "admin"}))
# 读取 session
data = r.get(f"session:{session_id}")
# 续期
r.expire(f"session:{session_id}", 1800)
6.5 布隆过滤器(防缓存穿透)¶
# 需要 RedisBloom 模块(Redis Stack 内置)
BF.RESERVE bf_users 0.01 1000000 # 误判率1%,预估100万元素
BF.ADD bf_users "user:1"
BF.EXISTS bf_users "user:1" # 存在返回1,不存在返回0
BF.MADD bf_users "user:2" "user:3"
七、运维与监控¶
7.1 INFO 命令¶
INFO # 全部信息
INFO server # 服务器信息
INFO clients # 客户端连接
INFO memory # 内存使用
INFO stats # 统计信息(命中率等)
INFO replication # 主从复制状态
INFO cpu # CPU 使用
INFO keyspace # 各库 key 数量
# 关键指标
# used_memory_human:已用内存
# mem_fragmentation_ratio:内存碎片率(>1.5 需关注)
# connected_clients:当前连接数
# keyspace_hits / keyspace_misses:缓存命中率
# instantaneous_ops_per_sec:每秒操作数
7.2 监控工具¶
工具 |
说明 |
|---|---|
redis-cli --stat |
实时统计信息 |
redis-cli --latency |
延迟监控 |
RedisInsight |
官方 GUI,功能全面 |
Prometheus + redis_exporter |
指标采集,配合 Grafana |
RedisMon |
实时命令监控 |
redis-cli --stat # 实时统计
redis-cli --latency # 延迟检测
redis-cli --latency-history # 延迟历史
redis-cli --bigkeys # 扫描大 key
redis-cli --hotkeys # 扫描热 key(需 maxmemory-policy 为 LFU)
7.3 安全加固¶
# redis.conf
requirepass strongpassword123 # 设置密码
bind 127.0.0.1 192.168.1.10 # 只监听内网
protected-mode yes
rename-command FLUSHALL "" # 禁用危险命令
rename-command FLUSHDB ""
rename-command KEYS ""
rename-command DEBUG ""
rename-command CONFIG "SAFE_CONFIG"
# 使用 ACL(Redis 6.0+)
ACL SETUSER appuser on >password ~cache:* +GET +SET +DEL
ACL LIST
ACL WHOAMI
7.4 日常维护清单¶
任务 |
频率 |
方式 |
|---|---|---|
检查内存使用 |
实时 |
|
检查主从延迟 |
实时 |
|
慢查询分析 |
每天 |
|
扫描大 key |
每周 |
|
备份 RDB |
每天 |
|
碎片整理 |
按需 |
|
检查连接数 |
实时 |
|
Key 数量趋势 |
每天 |
|
参考资源¶
Redis 命令参考:https://redis.io/commands/
RedisInsight(GUI):https://redis.io/insight/
Redis University:https://university.redis.com/