Redis

从基础到高级到分布式,涵盖安装、数据结构、持久化、高可用、分布式等核心内容。以 Redis 7.x 为基准。


目录

  1. 基础篇

    • 安装与初始化

    • 基本配置

    • 数据类型与使用

    • 键管理

    • 过期策略

  2. 进阶篇

    • 持久化(RDB / AOF / 混合)

    • 事务

    • Lua 脚本

    • 管道(Pipeline)

    • 发布订阅

    • Stream

    • 地理空间(GEO)

    • HyperLogLog

    • Bitmap

  3. 性能优化篇

    • 内存优化

    • 数据结构选型

    • 大 Key 与热 Key

    • 连接池

    • 配置调优

    • 慢查询日志

  4. 高可用篇

    • 主从复制

    • 哨兵模式(Sentinel)

    • 故障切换

    • 备份与恢复

  5. 分布式篇

    • Redis Cluster

    • 分布式锁

    • 分布式限流

    • 常见分布式方案对比

  6. 常见业务场景

  7. 运维与监控


一、基础篇

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 命令

命令

说明

PING

测试连接,返回 PONG

SELECT 1

切换到数据库1(默认16个库)

DBSIZE

当前库键数量

FLUSHDB

清空当前库(慎用)

FLUSHALL

清空所有库(慎用)

INFO

查看服务器信息

CONFIG GET *

查看所有配置

MONITOR

实时监控所有命令(调试用)


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 过期策略

这一节分清两个容易混淆的问题:

  1. 过期键删除策略:TTL 到期后,Redis 何时真正删除 key。

  2. 内存淘汰策略:内存达到 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

策略说明与建议:

策略

定义

适用场景

风险

noeviction

不淘汰,写命令报错

严格一致性场景

缓存写入失败率上升

allkeys-lru

全量 key 中淘汰最近最少使用

通用缓存

无法识别长期低频但关键数据

allkeys-lfu

全量 key 中淘汰低频 key

热点明显、访问分层明显

新热点升温有短暂滞后

allkeys-random

全量随机淘汰

压测/特殊场景

命中率不可控

volatile-lru

仅 TTL key 中 LRU 淘汰

持久化 key 与缓存 key 混存

无 TTL key 不受控增长

volatile-lfu

仅 TTL key 中 LFU 淘汰

同上

同上

volatile-ttl

仅 TTL key 中优先淘汰短 TTL

TTL 设计严格场景

热点短 TTL 可能被误淘汰

volatile-random

仅 TTL key 中随机淘汰

很少使用

命中率差

生产建议

  • 纯缓存库优先:allkeys-lfu(热点更稳定)或 allkeys-lru(简单稳妥)。

  • 混合存储库(缓存 + 持久业务 key)需慎用 allkeys-*,先拆库更安全。

  • 出现突发淘汰时,先看:

    • used_memory, maxmemory

    • evicted_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:baseuser:1:extra

  • String:压缩数据,或拆分存储

  • List:分页存储

热 Key:某个 key 被极高频访问,单节点压力大。

# 监控热 key
redis-cli --hotkeys

热 Key 解决方案

  • 本地缓存(JVM 缓存 / Python 内存缓存)

  • 复制多份:hot:key:0hot: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:来源客户端。

实战分析流程(建议固化为值班手册)

  1. 先取最近 50~200 条慢日志,按命令类型聚类。

  2. 识别高风险命令:

    • 全量扫描:KEYSHGETALL(超大 hash)

    • 大范围删除:DEL 大 key(应改 UNLINK

    • 大范围聚合:ZRANGE 巨大区间、SMEMBERS 大集合

  3. 结合 --bigkeysMEMORY USAGE 判断是否数据模型问题。

  4. 若慢但无明显大 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

复制原理

  1. 从节点连接主节点,发送 PSYNC

  2. 主节点执行 BGSAVE 生成 RDB,发送给从节点

  3. 从节点加载 RDB,之后主节点持续发送命令(命令传播)

  4. 断线重连后,通过 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 操作(MSETKEYS 等),除非 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 日常维护清单

任务

频率

方式

检查内存使用

实时

INFO memory / 监控告警

检查主从延迟

实时

INFO replication

慢查询分析

每天

SLOWLOG GET

扫描大 key

每周

redis-cli --bigkeys

备份 RDB

每天

BGSAVE + 定时复制

碎片整理

按需

MEMORY PURGE 或重启

检查连接数

实时

INFO clients

Key 数量趋势

每天

DBSIZE / INFO keyspace


参考资源