文章背景图

Day67-Redis高可用与缓存问题

2026-06-04
0
-
- 分钟
|

Day67 - Redis 高可用与缓存问题

本部分涵盖 Redis 高可用架构(主从、哨兵、集群)和三大缓存问题(穿透、击穿、雪崩)。


1. Redis 主从复制

1.1 主从复制原理

sequenceDiagram
    participant Master as 主库
    participant Slave as 从库
    
    Master->>Master: 接收写命令
    Master->>Master: 执行命令
    Master->>Master: 记录命令到命令缓冲区
    
    Note over Master,Slave: 增量同步
    Master->>Slave: 发送 RDB 文件
    Master->>Slave: 发送缓冲区命令
    
    Slave->>Slave: 加载 RDB
    Slave->>Slave: 执行增量命令

1.2 配置主从

# 方式1:命令行配置
redis-cli slaveof 192.168.1.100 6379

# 取消主从
redis-cli slaveof no one

# 方式2:配置文件
replicaof 192.168.1.100 6379
replica-read-only yes

1.3 主从复制流程

-- 完整重同步流程
1. slave 发送 PSYNC 命令给 master
2. master 判断是否首次同步
3. master 执行 BGSAVE 生成 RDB
4. master 发送 RDB 给 slave
5. slave 加载 RDB 数据
6. master 发送缓冲区命令
7. 完成同步

-- 增量同步
1. master 记录写命令到 replication buffer
2. master 发送 PING + 命令给 slave
3. slave 执行命令

2. 哨兵模式

2.1 哨兵架构

据《Redis哨兵模式和分片集群》(CSDN),哨兵模式通过监控主从节点状态实现自动故障转移。

graph TB
    subgraph 应用层
        App["客户端"]
    end
    
    subgraph 哨兵集群
        S1["哨兵1"]
        S2["哨兵2"]
        S3["哨兵3"]
    end
    
    subgraph Redis集群
        Master["主库<br/>Master"]
        Slave1["从库1<br/>Slave"]
        Slave2["从库2<br/>Slave"]
    end
    
    App <--> S1
    App <--> S2
    App <--> S3
    
    S1 --> Master
    S1 --> Slave1
    S1 --> Slave2
    
    S2 --> Master
    S2 --> Slave1
    S2 --> Slave2
    
    S3 --> Master
    S3 --> Slave1
    S3 --> Slave2
    
    Master --> Slave1
    Master --> Slave2

2.2 哨兵核心功能

功能 说明
监控 定期检测主从节点存活状态
通知 故障发生时通知客户端
自动故障转移 自动将从库升级为主库
配置提供者 提供当前主库地址

2.3 主观下线与客观下线

概念 说明
SDOWN(主观下线) 单个哨兵认为主节点不可达
ODOWN(客观下线) 足够多哨兵认为主节点不可达
# sentinel.conf 配置
sentinel monitor mymaster 192.168.1.100 6379 2
#                 集群名   主库IP      端口  法定票数

sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 30000

2.4 故障转移流程

graph TB
    subgraph 故障转移
        A["主库不可达"] --> B["多个哨兵确认ODOWN"]
        B --> C["投票选举领头哨兵"]
        C --> D["选择最优从库"]
        D --> E["从库升级为主库"]
        E --> F["其他从库切换主库"]
        F --> G["通知客户端新主库"]
    end

2.5 选主规则

-- 优先级选择
1. replica-priority 越大越优先
2. 复制偏移量越大(数据越新)
3. run ID 越小(启动越早)

3. Redis Cluster 集群

3.1 集群架构

据《Redis cluster specification》(Redis 官方文档),Redis Cluster 将数据分为 16384 个哈希槽,实现数据分片。

graph TB
    subgraph 集群节点
        N1["节点1<br/>Master:0-5460"]
        N2["节点2<br/>Master:5461-10922"]
        N3["节点3<br/>Master:10923-16383"]
        N4["节点4<br/>Slave1"]
        N5["节点5<br/>Slave2"]
        N6["节点6<br/>Slave3"]
    end
    
    N1 <--> N2
    N1 <--> N3
    N2 <--> N3
    
    N1 --- N4
    N2 --- N5
    N3 --- N6

3.2 哈希槽原理

# 键计算槽位
slot = CRC16(key) % 16384

# 示例
key = "user:1001"
CRC16("user:1001") = 12345
slot = 12345 % 16384 = 12345

# 分配槽位
redis-cli cluster addslots 0 1 2 ... 5460

3.3 集群配置

# 节点配置
cluster-enabled yes
cluster-node-timeout 15000
cluster-config-file nodes.conf

# 集群管理命令
redis-cli --cluster create 192.168.1.101:6379 192.168.1.102:6379 192.168.1.103:6379 \
    --cluster-replicas 1

3.4 集群 vs 哨兵

特性 哨兵模式 集群模式
数据分片 不支持 支持(16384槽)
写能力 单主库 多主库
扩容 纵向 横向
复杂度
适用场景 中小规模 大规模高并发

4. 缓存穿透

4.1 问题描述

据《Redis 缓存三大核心问题:缓存穿透/击穿/雪崩》(阿里云开发者社区),缓存穿透指查询一个不存在的数据,导致请求直达数据库。

graph LR
    R["请求"] --> C["缓存"]
    C -->|"miss"| D["数据库"]
    D -->|"无数据"| R
    R -->|"重复请求"| D

4.2 解决方案

-- 方案1:布隆过滤器
-- 在缓存层前加布隆过滤器,快速判断数据是否存在

-- 方案2:空值缓存
-- 将空结果也缓存,设置较短过期时间
SET product:9999 "" EX 60

-- 方案3:参数校验
-- 业务层拦截非法参数
IF id <= 0 THEN RETURN null
# Python 示例
def get_product(product_id):
    # 1. 参数校验
    if product_id <= 0:
        return None
    
    # 2. 布隆过滤器判断
    if not bloom_filter.contains(product_id):
        return None  # 一定不存在
    
    # 3. 查缓存
    product = redis.get(f"product:{product_id}")
    if product:
        return product
    
    # 4. 查数据库
    product = db.query("SELECT * FROM products WHERE id=?", product_id)
    
    # 5. 缓存结果
    if product:
        redis.setex(f"product:{product_id}", 3600, json.dumps(product))
    else:
        # 缓存空值,避免穿透
        redis.setex(f"product:{product_id}", 60, "")
    
    return product

5. 缓存击穿

5.1 问题描述

缓存击穿指热点 Key 过期的瞬间,大量请求涌入数据库。

graph LR
    R["请求"] --> C["缓存热点key<br/>过期瞬间"]
    C -->|"击穿"| D["数据库"]
    D --> R

5.2 解决方案

-- 方案1:互斥锁
-- 只允许一个线程重建缓存

-- 方案2:热点数据永不过期
-- 通过逻辑过期代替物理过期

-- 方案3:过期时间随机化
SET hot_key value EX 3600
PEXPIREAT hot_key timestamp + random(0, 600)
# 互斥锁实现
import redis
import json
import time

lock_key = f"lock:product:{product_id}"
cache_key = f"product:{product_id}"

# 尝试获取锁
lock = redis.set(lock_key, "1", nx=True, ex=10)

if lock:
    try:
        # 查数据库
        product = db.query("SELECT * FROM products WHERE id=?", product_id)
        
        # 更新缓存
        redis.setex(cache_key, 3600, json.dumps(product))
    finally:
        redis.delete(lock_key)
else:
    # 等待后重试
    time.sleep(0.1)
    return redis.get(cache_key)

6. 缓存雪崩

6.1 问题描述

大量缓存同时过期,导致大量请求直接访问数据库。

graph LR
    K1["key1过期"] & K2["key2过期"] & K3["key3过期"] --> D["数据库崩溃"]

6.2 解决方案

-- 方案1:过期时间随机化
SET key1 value EX 3600
SET key2 value EX 3700
SET key3 value EX 3500

-- 方案2:多级缓存
-- L1: 本地缓存 1分钟
-- L2: Redis缓存 1小时

-- 方案3:服务降级
-- 限流、熔断保护数据库
# 多级缓存实现
class CacheService:
    def get_product(self, product_id):
        local_key = f"local:product:{product_id}"
        
        # 1. 查本地缓存
        product = local_cache.get(local_key)
        if product:
            return product
        
        # 2. 查Redis缓存
        redis_key = f"redis:product:{product_id}"
        product = redis.get(redis_key)
        if product:
            # 回填本地缓存
            local_cache.setex(local_key, 60, product)
            return product
        
        # 3. 查数据库
        product = db.query(product_id)
        
        # 4. 回填缓存
        redis.setex(redis_key, 3600, product)
        local_cache.setex(local_key, 60, product)
        
        return product

7. 缓存问题对比总结

问题 原因 核心方案
缓存穿透 查询不存在的数据 布隆过滤器、空值缓存
缓存击穿 热点 Key 过期 互斥锁、逻辑过期
缓存雪崩 大量 Key 同时过期 随机过期、多级缓存、限流熔断

8. Redis 内存管理

8.1 内存淘汰策略

# 配置
maxmemory 2gb
maxmemory-policy allkeys-lru

# 策略说明
noeviction         # 不淘汰(默认)
allkeys-lru        # 所有key LRU淘汰
allkeys-random     # 所有key随机淘汰
volatile-lru       # 已过期key LRU淘汰
volatile-random    # 已过期key随机淘汰
volatile-ttl       # 已过期key TTL淘汰

8.2 内存优化

# 合理使用数据结构
HSET user:1 name "zhang" age "25"  # Hash 优于 String

# 压缩列表优化
hash-max-ziplist-entries 512
hash-max-ziplist-value 64

# 过期键处理
# 惰性删除:访问时检查并删除
# 定期删除:每隔一段时间检查
原创

Day67-Redis高可用与缓存问题

本文链接: Day67-Redis高可用与缓存问题

本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

评论交流

文章目录