Redis 缓存与高可用架构

说明: 本文为配置思路与示例整理,不代表作者已在自己的服务器上逐项验证全部命令。执行涉及公网暴露、账户权限、数据删除或服务重启的操作前,请先备份,并结合官方文档与实际环境核验。

引言

在现代 Web 应用架构中,Redis 几乎是不可或缺的组件。无论是用作数据库缓存、会话存储、消息队列,还是分布式锁,Redis 凭借其极高的性能和丰富的数据结构,成为了后端开发者的"瑞士军刀"。

然而,很多开发者对 Redis 的使用停留在 SETGET,对其持久化机制、内存管理、集群架构等核心知识了解甚少。本文将从实际生产经验出发,带你全面掌握 Redis 的核心知识。

一、Redis 安装与基础配置

1.1 Docker 方式安装

使用 Docker 是最快的上手方式:

BASH
# 单机模式启动
docker run -d \
  --name redis-server \
  -p 6379:6379 \
  -v redis-data:/data \
  -e REDIS_PASSWORD=your_secure_password \
  redis:7-alpine redis-server --appendonly yes --requirepass your_secure_password

1.2 关键配置文件

生产环境中,建议使用自定义配置文件。以下是最重要的配置项:

CONF
# redis-production.conf

# 绑定地址(生产环境只绑定内网)
bind 0.0.0.0

# 端口
port 6379

# 设置密码(必须设置!)
requirepass CHANGE_ME_WITH_A_LONG_RANDOM_SECRET

# 最大内存限制(建议设为物理内存的 70-80%)
maxmemory 4gb

# 内存淘汰策略
maxmemory-policy allkeys-lru

# 持久化 - AOF
appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 持久化 - RDB
save 900 1
save 300 10
save 60 10000

# 禁用危险命令
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command CONFIG "CONFIG_9f8a7b6c"

1.3 为什么禁用危险命令?

FLUSHALLFLUSHDB 会清空所有数据,在生产环境中一旦误操作就是灾难。通过 rename-command 将其重命名为空字符串即可禁用。

二、Redis 数据结构详解

Redis 不是简单的 Key-Value 存储,它提供了 5 种核心数据结构,每种都有特定的应用场景。

2.1 String — 缓存与计数器

BASH
# 基础操作
SET user:1001:name "张三"
GET user:1001:name

# 设置过期时间(缓存的核心)
SET cache:homepage "html_content..." EX 3600

# 原子计数器(点赞、浏览量)
INCR article:1001:views          # 浏览量 +1
INCRBY article:1001:likes 5      # 点赞 +5
DECR article:1001:views          # 浏览量 -1

# 批量操作(减少网络往返)
MSET config:max_retries 3 config:timeout 5000 config:retry_delay 100
MGET config:max_retries config:timeout config:retry_delay

实际应用场景:文章浏览量统计

PYTHON
import redis

r = redis.Redis(host='localhost', port=6379, password='your_password')

def record_page_view(article_id):
    """记录页面浏览量,带去重(同一IP每天只计一次)"""
    pipe = r.pipeline()
    pipe.incr(f"article:{article_id}:views")
    pipe.expire(f"article:{article_id}:views", 86400 * 30)  # 30天过期
    pipe.execute()
    return r.get(f"article:{article_id}:views")

2.2 Hash — 对象存储

BASH
# 存储用户信息(比多个 String key 更高效)
HSET user:1001 name "张三" email "zhangsan@example.com" age 28
HGET user:1001 name
HGETALL user:1001

# 只更新部分字段(不会覆盖其他字段)
HSET user:1001 age 29

# 判断字段是否存在
HEXISTS user:1001 email

# 给数值字段加减(原子操作)
HINCRBY user:1001 age 1

实际应用场景:购物车

BASH
# 添加商品到购物车
HSET cart:user:1001 item:5001 2      # 商品5001,数量2
HSET cart:user:1001 item:5002 1      # 商品5002,数量1

# 修改数量
HINCRBY cart:user:1001 item:5001 1   # 数量 +1

# 获取整个购物车
HGETALL cart:user:1001

# 删除商品
HDEL cart:user:1001 item:5002

# 设置购物车过期(7天未结算自动清空)
EXPIRE cart:user:1001 604800

2.3 List — 消息队列与最新动态

BASH
# 添加消息(左侧为新消息)
LPUSH notifications:user:1001 "你有一条新评论"
LPUSH notifications:user:1001 "你的文章被点赞了"

# 获取最新 10 条通知
LRANGE notifications:user:1001 0 9

# 消息队列模式:生产者/消费者
LPUSH task:queue '{"task":"send_email","to":"user@example.com"}'  # 生产者
BRPOP task:queue 30  # 消费者(阻塞等待30秒)

# 控制列表长度(只保留最近100条)
LTRIM notifications:user:1001 0 99

2.4 Set — 去重与关系运算

BASH
# 标签系统
SADD article:1001:tags "python" "redis" "tutorial"
SADD article:1002:tags "python" "docker" "tutorial"

# 判断是否包含某个标签
SISMEMBER article:1001:tags "redis"

# 获取文章的所有标签
SMEMBERS article:1001:tags

# 交集:两篇文章共同的标签
SINTER article:1001:tags article:1002:tags  # → python, tutorial

# 并集:两篇文章的所有标签(去重)
SUNION article:1001:tags article:1002:tags

# 差集:文章1有但文章2没有的标签
SDIFF article:1001:tags article:1002:tags  # → redis

实际应用场景:用户关注系统

BASH
# 用户A关注了用户B和用户C
SADD user:A:following user:B user:C

# 用户B的粉丝
SADD user:B:fans user:A

# 判断A是否关注了B
SISMEMBER user:A:following user:B

# 共同关注(社交推荐的基础)
SINTER user:A:following user:B:following

2.5 Sorted Set — 排行榜

BASH
# 排行榜(score 为分数,自动排序)
ZADD leaderboard 1500 "player:001"
ZADD leaderboard 2300 "player:002"
ZADD leaderboard 1800 "player:003"

# 获取 Top 10(分数从高到低)
ZREVRANGE leaderboard 0 9 WITHSCORES

# 获取某玩家排名(从0开始,所以+1)
ZREVRANK leaderboard "player:002"  # → 1(第2名)

# 增加分数
ZINCRBY leaderboard 200 "player:001"  # 分数 +200

# 获取分数在某个范围的玩家
ZRANGEBYSCORE leaderboard 1000 2000

# 统计某个分数段的玩家数量
ZCOUNT leaderboard 1000 2000

三、持久化机制:数据安全的基石

Redis 的持久化有两种方式,理解它们的区别对生产部署至关重要。

3.1 RDB(快照)

RDB 在指定时间间隔将内存数据生成快照保存到磁盘。

工作原理

BASH
# RDB 配置
save 900 1      # 900秒内至少1次修改,触发保存
save 300 10     # 300秒内至少10次修改,触发保存
save 60 10000   # 60秒内至少10000次修改,触发保存

# 触发手动保存
BGSAVE          # 后台异步保存(推荐)
SAVE            # 同步保存(会阻塞,不推荐)

# RDB 文件配置
dbfilename dump.rdb
dir /data

优缺点分析

维度 RDB
数据安全性 可能丢失最后一次快照后的数据
恢复速度 快(直接加载二进制文件)
文件大小 小(压缩的二进制格式)
CPU 开销 低(fork 子进程)

3.2 AOF(追加日志)

AOF 记录每一个写操作命令,重启时重放命令恢复数据。

BASH
# AOF 配置
appendonly yes
appendfsync always    # 每次写操作都同步(最安全,性能最差)
appendfsync everysec  # 每秒同步一次(推荐,最多丢1秒数据)
appendfsync no        # 由操作系统决定何时同步(性能最好,风险最高)

# AOF 重写(压缩 AOF 文件)
auto-aof-rewrite-percentage 100    # AOF 文件增长 100% 时触发重写
auto-aof-rewrite-min-size 64mb     # 最小触发重写文件大小

3.3 生产环境推荐策略

BASH
# 推荐同时开启 RDB 和 AOF
appendonly yes
appendfsync everysec

save 900 1
save 300 10

# Redis 7.0+ 支持混合持久化(推荐)
aof-use-rdb-preamble yes
# AOF 文件开头是 RDB 快照,后面追加 AOF 日志
# 兼顾了恢复速度和数据安全性

四、内存管理与性能优化

4.1 内存分析

BASH
# 查看内存使用情况
INFO memory

# 关键指标解读
# used_memory: Redis 分配的内存总量
# used_memory_rss: 操作系统分配的物理内存
# mem_fragmentation_ratio: 内存碎片率
#   > 1.5: 碎片严重,考虑重启或开启碎片整理
#   < 1: 使用了 swap,性能严重下降!

# 分析大 Key
redis-cli --bigkeys

# 查看某个 Key 的内存占用
MEMORY USAGE user:1001

4.2 内存优化技巧

BASH
# 1. 使用 Hash 代替多个 String(省内存约 50%)
# 差的写法
SET user:1001:name "张三"
SET user:1001:email "test@example.com"
SET user:1001:age 28
# 好的写法
HSET user:1001 name "张三" email "test@example.com" age 28

# 2. 使用压缩列表优化小 Hash
hash-max-ziplist-entries 128
hash-max-ziplist-value 64

# 3. 合理设置过期时间(避免无用数据占用内存)
EXPIRE session:abc123 1800

# 4. 使用 UNLINK 替代 DEL(异步删除大 Key,避免阻塞)
UNLINK large:hash:key

4.3 慢查询分析

BASH
# 开启慢查询日志
SLOWLOG LOG 100     # 查看最近100条慢查询
SLOWLOG LEN          # 慢查询日志长度
SLOWLOG RESET        # 清空慢查询日志

# 配置慢查询阈值
slowlog-log-slower-than 10000   # 超过 10ms 记录(微秒单位)
slowlog-max-len 128             # 最多保留 128 条

五、高可用架构

5.1 主从复制

BASH
# 从节点配置
replicaof 192.168.1.100 6379
masterauth YourPassword123!

# 查看复制状态
INFO replication

5.2 Redis Sentinel(哨兵模式)

Sentinel 实现自动故障转移,适合中小规模生产环境。

CONF
# sentinel.conf
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel auth-pass mymaster YourPassword123!
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
BASH
# 启动 Sentinel
docker run -d --name redis-sentinel \
  -v /path/to/sentinel.conf:/etc/redis/sentinel.conf \
  redis:7-alpine redis-sentinel /etc/redis/sentinel.conf

# 查看 Sentinel 状态
redis-cli -p 26379 SENTINEL masters
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster

5.3 Redis Cluster(集群模式)

Cluster 适合大规模数据和高并发场景。

BASH
# 创建 6 节点集群(3主3从)
# 每个节点配置
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000

# 创建集群
redis-cli --cluster create \
  node1:6379 node2:6379 node3:6379 \
  node4:6379 node5:6379 node6:6379 \
  --cluster-replicas 1

# 查看集群状态
redis-cli -c CLUSTER INFO
redis-cli -c CLUSTER NODES

# 检查集群健康
redis-cli --cluster check node1:6379

5.4 三种模式对比

特性 主从复制 Sentinel Cluster
数据分片
自动故障转移
适合数据量 < 16GB < 32GB 无上限
适合 QPS < 10万 < 10万 百万级
运维复杂度

六、分布式锁实现

分布式锁是 Redis 最经典的高级用法之一。

PYTHON
import redis
import time
import uuid


class RedisLock:
    def __init__(self, redis_client, lock_key, timeout=10, acquire_timeout=5):
        self.redis = redis_client
        self.lock_key = f"lock:{lock_key}"
        self.timeout = timeout
        self.acquire_timeout = acquire_timeout
        self.lock_value = str(uuid.uuid4())

    def acquire(self):
        """获取锁"""
        end_time = time.time() + self.acquire_timeout
        while time.time() < end_time:
            if self.redis.set(self.lock_key, self.lock_value, nx=True, ex=self.timeout):
                return True
            time.sleep(0.01)
        return False

    def release(self):
        """释放锁(Lua脚本保证原子性)"""
        lua_script = """
        if redis.call('get', KEYS[1]) == ARGV[1] then
            return redis.call('del', KEYS[1])
        else
            return 0
        end
        """
        return self.redis.eval(lua_script, 1, self.lock_key, self.lock_value)


# 使用示例
r = redis.Redis(host='localhost', port=6379, password='your_password')

lock = RedisLock(r, "order:1001:process")
if lock.acquire():
    try:
        # 执行业务逻辑
        print("处理订单中...")
        time.sleep(2)
    finally:
        lock.release()
else:
    print("获取锁失败,其他进程正在处理")

关键要点

  • nx=True 保证只在 key 不存在时设置(原子操作)
  • ex=timeout 设置过期时间防止死锁
  • 释放锁时用 Lua 脚本保证"判断+删除"的原子性
  • 每个锁都有唯一 value,防止误删其他进程的锁

七、监控与运维

7.1 关键监控指标

BASH
# 实时监控命令
redis-cli INFO stats

# 关注以下指标:
# - instantaneous_ops_per_sec: 每秒操作数(QPS)
# - hit_rate 需要自行计算: keyspace_hits / (keyspace_hits + keyspace_misses)
#   命中率 < 80% 通常说明缓存键设计、TTL、容量或热点数据策略需要复盘
# - expired_keys: 过期 key 数量
# - evicted_keys: 被淘汰的 key 数量(出现说明内存不够)
# - connected_clients: 当前连接数
# - blocked_clients: 阻塞的客户端数

7.2 Prometheus + Grafana 监控

YAML
# docker-compose.yml(监控栈)
version: '3.8'
services:
  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    ports:
      - "6379:6379"

  redis-exporter:
    image: oliver006/redis_exporter:latest
    environment:
      REDIS_ADDR: redis:6379
      REDIS_PASSWORD: ${REDIS_PASSWORD}
    ports:
      - "9121:9121"

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"

总结

Redis 的强大远不止简单的缓存,它是一个功能丰富的数据结构服务器。掌握本文的内容,你将能够:

  1. 选择合适的数据结构:String 用于计数器,Hash 用于对象,Set 用于去重,Sorted Set 用于排行榜
  2. 保证数据安全:合理配置 RDB + AOF 混合持久化
  3. 优化内存使用:分析大 Key,使用压缩列表,设置合理的过期策略
  4. 构建高可用架构:根据规模选择 Sentinel 或 Cluster
  5. 正确实现分布式锁:Lua 脚本保证原子性,唯一 value 防误删

Redis 不难学,但用好它需要对数据结构和应用场景有深入理解。希望本文能帮助你在生产环境中更自信地使用 Redis。

评论