找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2690

积分

0

好友

380

主题
发表于 7 小时前 | 查看: 0| 回复: 0

作为典型的内存数据库,Redis的所有数据都驻留在内存中。一旦服务器进程意外退出或发生宕机,内存中的数据便荡然无存。因此,一套可靠的持久化机制是Redis在生产环境中稳定运行的基石。

Redis主要提供了两种持久化方案:RDB(Redis Database)和AOF(Append Only File)。前者通过创建某个时间点的内存数据快照来实现持久化,后者则通过记录每一个写操作命令来确保数据安全。这两种机制各有侧重,在实际生产部署中,我们需要根据业务特性进行选择和配置,甚至组合使用。本文将深入剖析两者的工作原理、性能特征,并提供一套可直接部署的最佳实践指南。

一、概述

1.1 技术特点

RDB持久化特点

特性 说明
实现方式 fork子进程,将内存快照写入文件
文件格式 二进制紧凑格式,体积小
恢复速度 快,直接加载二进制数据
数据安全 可能丢失最后一次快照后的数据
性能影响 fork时可能有短暂阻塞
适用场景 容忍一定数据丢失,追求快速恢复

AOF持久化特点

特性 说明
实现方式 追加写操作命令到日志文件
文件格式 文本格式,可读性好
恢复速度 较慢,需要重放所有命令
数据安全 根据同步策略,最多丢失1秒数据
性能影响 fsync可能影响写入性能
适用场景 数据安全性要求高

Redis 7.0+ 新特性
Redis 7.0 带来了几项重要的持久化改进:

  • Multi Part AOF:将AOF文件拆分为基础文件和增量文件,使得重写操作更加高效。
  • RDB-AOF混合持久化优化:在性能和数据安全之间取得了更好的平衡。
  • AOF时间戳注解:支持按精确的时间点进行数据恢复。

1.2 适用场景

场景 推荐方案 说明
纯缓存 可不开启或仅RDB 数据可从数据源重建
会话存储 AOF everysec 通常允许丢失1秒内的会话数据
排行榜/计数器 RDB + AOF 数据较为重要,但可容忍极少量丢失
消息队列 AOF always 消息不能丢失
分布式锁 不依赖持久化 以内存状态为准,重启后锁失效是预期的
数据库替代 AOF always + 主从 需要高可用性和最高的数据安全性

1.3 环境要求

组件 版本要求 说明
Redis Server 7.2+ 推荐使用最新稳定版
操作系统 Rocky 9 / Ubuntu 24.04 内核版本 5.14+
内存 实际数据量的2倍 fork子进程需要额外的内存空间
磁盘 SSD,IOPS 3000+ 持久化需要高IO性能
文件系统 ext4/xfs 支持稀疏文件特性

二、详细步骤

2.1 准备工作

2.1.1 安装 Redis 7.2

# Rocky Linux 9 / CentOS Stream 9
# 编译安装最新版Redis
sudo dnf groupinstall -y "Development Tools"
sudo dnf install -y systemd-devel

cd /tmp
wget https://github.com/redis/redis/archive/refs/tags/7.2.4.tar.gz
tar -xzf 7.2.4.tar.gz
cd redis-7.2.4

# 编译(启用systemd支持)
make USE_SYSTEMD=yes
make test # 可选,运行测试
sudo make install PREFIX=/usr/local/redis

# 创建必要目录
sudo mkdir -p /etc/redis
sudo mkdir -p /var/lib/redis
sudo mkdir -p /var/log/redis
sudo mkdir -p /var/run/redis

# 创建redis用户
sudo useradd -r -s /sbin/nologin redis
sudo chown -R redis:redis /var/lib/redis /var/log/redis /var/run/redis

# Ubuntu 24.04
sudo apt update
sudo apt install -y redis-server
# Ubuntu仓库中的Redis版本可能较旧,建议编译安装

2.1.2 系统优化配置

# 内存配置
# 禁用透明大页(THP),避免fork延迟
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag

# 持久化配置
cat << 'EOF' | sudo tee /etc/sysctl.d/redis.conf
# 允许内存过量分配(fork需要)
vm.overcommit_memory = 1

# 网络优化
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# 文件描述符
fs.file-max = 100000
EOF

sudo sysctl -p /etc/sysctl.d/redis.conf

# 添加到rc.local确保重启后生效
cat << 'EOF' | sudo tee /etc/rc.d/rc.local
#!/bin/bash
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
EOF
sudo chmod +x /etc/rc.d/rc.local

# 用户资源限制
cat << 'EOF' | sudo tee /etc/security/limits.d/redis.conf
redis soft nofile 65535
redis hard nofile 65535
redis soft nproc 65535
redis hard nproc 65535
EOF

2.2 核心配置

2.2.1 RDB持久化配置

# /etc/redis/redis.conf - RDB相关配置

################################ SNAPSHOTTING  ################################

# RDB触发条件(满足任一条件即触发)
# 格式:save <seconds> <changes>
# 3600秒内有1次修改
save 3600 1
# 300秒内有100次修改
save 300 100
# 60秒内有10000次修改
save 60 10000

# 禁用RDB(注释所有save或使用)
# save ""

# RDB文件名
dbfilename dump.rdb

# RDB文件存储目录
dir /var/lib/redis

# bgsave出错时停止写入
# 生产环境建议开启,及时发现持久化问题
stop-writes-on-bgsave-error yes

# RDB文件压缩
# 开启可减小文件大小,但消耗CPU
rdbcompression yes

# RDB文件校验
# 加载时校验,防止文件损坏
rdbchecksum yes

# 删除没有持久化的副本中的RDB文件
rdb-del-sync-files no

手动触发RDB

# 同步保存(阻塞,不推荐生产使用)
redis-cli SAVE

# 异步保存(后台执行,推荐)
redis-cli BGSAVE

# 查看上次保存时间
redis-cli LASTSAVE

# 查看RDB状态
redis-cli INFO persistence | grep rdb
# rdb_changes_since_last_save:0
# rdb_bgsave_in_progress:0
# rdb_last_save_time:1705305600
# rdb_last_bgsave_status:ok
# rdb_last_bgsave_time_sec:2
# rdb_current_bgsave_time_sec:-1

2.2.2 AOF持久化配置

# /etc/redis/redis.conf - AOF相关配置

############################## APPEND ONLY MODE ###############################

# 开启AOF持久化
appendonly yes

# AOF文件名(Redis 7.0+使用目录结构)
appendfilename "appendonly.aof"

# AOF文件存储目录(在dir目录下创建子目录)
appenddirname "appendonlydir"

# AOF同步策略
# always: 每个写命令都同步,最安全但最慢
# everysec: 每秒同步一次,推荐,平衡安全和性能
# no: 由操作系统决定何时同步,最快但可能丢失较多数据
appendfsync everysec

# 重写期间是否暂停fsync
# yes: 重写期间暂停fsync,避免阻塞,但可能丢失数据
# no: 继续fsync,更安全但可能影响性能
no-appendfsync-on-rewrite no

# AOF自动重写触发条件
# 当前AOF文件大小超过上次重写后大小的100%时触发
auto-aof-rewrite-percentage 100
# AOF文件最小重写大小
auto-aof-rewrite-min-size 64mb

# 加载AOF时遇到错误的处理方式
# yes: 继续加载,记录日志
# no: 停止加载,启动失败
aof-load-truncated yes

# 开启RDB-AOF混合持久化(Redis 4.0+)
# 重写时AOF文件前半部分是RDB格式,后半部分是AOF格式
aof-use-rdb-preamble yes

# AOF时间戳(Redis 7.0+)
# 允许按时间点恢复
aof-timestamp-enabled no

手动触发AOF重写

# 手动触发AOF重写
redis-cli BGREWRITEAOF

# 查看AOF状态
redis-cli INFO persistence | grep aof
# aof_enabled:1
# aof_rewrite_in_progress:0
# aof_rewrite_scheduled:0
# aof_last_rewrite_time_sec:1
# aof_current_rewrite_time_sec:-1
# aof_last_bgrewrite_status:ok
# aof_current_size:1234567
# aof_base_size:1000000

# 查看AOF文件
ls -la /var/lib/redis/appendonlydir/
# appendonly.aof.1.base.rdb    - 基础RDB文件(混合持久化)
# appendonly.aof.1.incr.aof    - 增量AOF文件
# appendonly.aof.manifest      - 清单文件

2.2.3 RDB-AOF混合模式配置

# /etc/redis/redis.conf - 混合持久化配置(推荐生产使用)

# 开启AOF
appendonly yes

# 开启混合持久化
aof-use-rdb-preamble yes

# AOF同步策略
appendfsync everysec

# RDB配置(作为额外的备份)
save 3600 1
save 300 100
save 60 10000

# 目录配置
dir /var/lib/redis
dbfilename dump.rdb
appendfilename "appendonly.aof"
appenddirname "appendonlydir"

2.3 启动和验证

2.3.1 创建 systemd 服务

# /etc/systemd/system/redis.service
cat << 'EOF' | sudo tee /etc/systemd/system/redis.service
[Unit]
Description=Redis In-Memory Data Store
After=network.target

[Service]
Type=notify
User=redis
Group=redis
ExecStart=/usr/local/redis/bin/redis-server /etc/redis/redis.conf
ExecStop=/usr/local/redis/bin/redis-cli shutdown
Restart=always
RestartSec=3
LimitNOFILE=65535

# 安全加固
PrivateTmp=true
ProtectSystem=full
NoNewPrivileges=true
ProtectHome=true

[Install]
WantedBy=multi-user.target
EOF

# 重载systemd并启动
sudo systemctl daemon-reload
sudo systemctl enable redis
sudo systemctl start redis

# 检查状态
sudo systemctl status redis
redis-cli ping
# PONG

2.3.2 验证持久化配置

# 验证RDB
redis-cli CONFIG GET save
# 1) "save"
# 2) "3600 1 300 100 60 10000"

redis-cli CONFIG GET dbfilename
# 1) "dbfilename"
# 2) "dump.rdb"

# 验证AOF
redis-cli CONFIG GET appendonly
# 1) "appendonly"
# 2) "yes"

redis-cli CONFIG GET appendfsync
# 1) "appendfsync"
# 2) "everysec"

redis-cli CONFIG GET aof-use-rdb-preamble
# 1) "aof-use-rdb-preamble"
# 2) "yes"

# 查看持久化信息
redis-cli INFO persistence

2.3.3 持久化测试

# 写入测试数据
redis-cli SET test:key "Hello Redis Persistence"
redis-cli HSET user:1001 name "John" age 25 city "Beijing"
redis-cli LPUSH mylist a b c d e

# 触发RDB保存
redis-cli BGSAVE
# Background saving started

# 等待完成
sleep 2
redis-cli LASTSAVE

# 检查RDB文件
ls -la /var/lib/redis/dump.rdb

# 检查AOF文件
ls -la /var/lib/redis/appendonlydir/

# 模拟重启测试
sudo systemctl restart redis

# 验证数据恢复
redis-cli GET test:key
# "Hello Redis Persistence"
redis-cli HGETALL user:1001
# 1) "name"
# 2) "John"
# 3) "age"
# 4) "25"
# 5) "city"
# 6) "Beijing"

三、示例代码和配置

3.1 完整配置示例

3.1.1 生产环境配置模板

# /etc/redis/redis.conf - 生产环境完整配置

################################## NETWORK #####################################

bind 0.0.0.0
port 6379
tcp-backlog 65535
timeout 0
tcp-keepalive 300

################################## SECURITY ###################################

requirepass YourStrongPassword@2024
# ACL配置(Redis 6.0+)
# user default on >YourStrongPassword@2024 ~* &* +@all

################################# GENERAL #####################################

daemonize no
supervised systemd
pidfile /var/run/redis/redis.pid
loglevel notice
logfile /var/log/redis/redis.log
databases 16

################################ SNAPSHOTTING  ################################

# RDB持久化配置
save 3600 1
save 300 100
save 60 10000

stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
rdb-del-sync-files no
dir /var/lib/redis

################################# REPLICATION #################################

# 如果是从库,配置主库地址
# replicaof <masterip> <masterport>
# masterauth <master-password>

replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync yes
repl-diskless-sync-delay 5

############################## APPEND ONLY MODE ###############################

# AOF持久化配置
appendonly yes
appendfilename "appendonly.aof"
appenddirname "appendonlydir"

# 生产环境推荐everysec
appendfsync everysec

no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes

# 开启混合持久化
aof-use-rdb-preamble yes

################################## MEMORY MANAGEMENT ##########################

maxmemory 8gb
maxmemory-policy allkeys-lru
maxmemory-samples 5

################################## LAZY FREEING ###############################

lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
replica-lazy-flush yes
lazyfree-lazy-user-del yes
lazyfree-lazy-user-flush yes

############################## ADVANCED CONFIG ################################

hash-max-listpack-entries 512
hash-max-listpack-value 64
list-max-listpack-size -2
list-compress-depth 0
set-max-intset-entries 512
set-max-listpack-entries 128
set-max-listpack-value 64
zset-max-listpack-entries 128
zset-max-listpack-value 64
activerehashing yes

############################# SLOW LOG ########################################

slowlog-log-slower-than 10000
slowlog-max-len 1000

############################# LATENCY MONITOR #################################

latency-monitor-threshold 100

############################# EVENT NOTIFICATION ##############################

notify-keyspace-events ""

############################### CLIENTS ########################################

maxclients 10000

################################ THREADED I/O #################################

io-threads 4
io-threads-do-reads yes

3.1.2 不同场景配置示例

场景一:纯缓存(允许数据丢失)

# 纯缓存场景,追求性能
# RDB低频保存,不开启AOF

save 3600 1

appendonly no

maxmemory 16gb
maxmemory-policy allkeys-lru

场景二:会话存储(允许丢失1秒数据)

# 会话存储,平衡性能和数据安全

save 900 1
save 300 10
save 60 10000

appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes

maxmemory 4gb
maxmemory-policy volatile-lru

场景三:数据库替代(不允许数据丢失)

# 作为数据库使用,最高数据安全

save 60 1

appendonly yes
appendfsync always
aof-use-rdb-preamble yes

# 禁用重写期间暂停fsync
no-appendfsync-on-rewrite no

maxmemory 32gb
maxmemory-policy noeviction

3.2 实际应用案例

3.2.1 案例一:电商购物车服务

需求分析

  • 数据量:100万用户,平均每用户10个商品
  • 数据特点:写多读多,数据需要持久化
  • 可用性:允许重启后丢失最多1分钟数据
  • 性能要求:写入QPS 50000+

配置方案

# 购物车Redis配置
maxmemory 8gb
maxmemory-policy volatile-ttl

# RDB配置(低频,作为备份)
save 300 100
save 60 10000

# AOF配置
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 256mb

# 性能优化
io-threads 4
io-threads-do-reads yes
lazyfree-lazy-eviction yes

应用代码示例

import redis
import json
from typing import Dict, List

class ShoppingCart:
    def __init__(self, redis_host: str = 'localhost', redis_port: int = 6379):
        self.redis = redis.Redis(
            host=redis_host,
            port=redis_port,
            password='YourStrongPassword@2024',
            decode_responses=True
        )
        self.cart_prefix = 'cart:'
        self.cart_ttl = 86400 * 7  # 7天过期

    def add_item(self, user_id: str, product_id: str, quantity: int, product_info: Dict):
        """添加商品到购物车"""
        cart_key = f"{self.cart_prefix}{user_id}"
        item = {
            'product_id': product_id,
            'quantity': quantity,
            'name': product_info.get('name'),
            'price': product_info.get('price'),
            'image': product_info.get('image')
        }
        # 使用HSET存储,支持单个商品更新
        self.redis.hset(cart_key, product_id, json.dumps(item))
        self.redis.expire(cart_key, self.cart_ttl)

    def remove_item(self, user_id: str, product_id: str):
        """从购物车删除商品"""
        cart_key = f"{self.cart_prefix}{user_id}"
        self.redis.hdel(cart_key, product_id)

    def update_quantity(self, user_id: str, product_id: str, quantity: int):
        """更新商品数量"""
        cart_key = f"{self.cart_prefix}{user_id}"
        item_json = self.redis.hget(cart_key, product_id)
        if item_json:
            item = json.loads(item_json)
            item['quantity'] = quantity
            self.redis.hset(cart_key, product_id, json.dumps(item))

    def get_cart(self, user_id: str) -> List[Dict]:
        """获取购物车"""
        cart_key = f"{self.cart_prefix}{user_id}"
        items = self.redis.hgetall(cart_key)
        return [json.loads(v) for v in items.values()]

    def clear_cart(self, user_id: str):
        """清空购物车"""
        cart_key = f"{self.cart_prefix}{user_id}"
        self.redis.delete(cart_key)

    def get_total(self, user_id: str) -> float:
        """计算购物车总价"""
        cart = self.get_cart(user_id)
        return sum(item['price'] * item['quantity'] for item in cart)

3.2.2 案例二:排行榜服务

需求分析

  • 实时排行榜,数据不能丢失
  • 写入频繁(每次游戏结束更新分数)
  • 读取频繁(排行榜查询)

配置方案

# 排行榜Redis配置
maxmemory 4gb
maxmemory-policy noeviction

# RDB配置
save 300 1
save 60 1000

# AOF配置(严格模式)
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
no-appendfsync-on-rewrite no

应用代码示例

import redis
from typing import List, Tuple

class Leaderboard:
    def __init__(self):
        self.redis = redis.Redis(
            host='localhost',
            port=6379,
            password='YourStrongPassword@2024',
            decode_responses=True
        )

    def update_score(self, board_name: str, user_id: str, score: float):
        """更新用户分数"""
        key = f"leaderboard:{board_name}"
        # ZADD会自动处理新增和更新
        self.redis.zadd(key, {user_id: score})

    def increment_score(self, board_name: str, user_id: str, increment: float):
        """增加用户分数"""
        key = f"leaderboard:{board_name}"
        return self.redis.zincrby(key, increment, user_id)

    def get_rank(self, board_name: str, user_id: str) -> int:
        """获取用户排名(从0开始)"""
        key = f"leaderboard:{board_name}"
        # 倒序排名(分数高的排前面)
        rank = self.redis.zrevrank(key, user_id)
        return rank + 1 if rank is not None else None

    def get_score(self, board_name: str, user_id: str) -> float:
        """获取用户分数"""
        key = f"leaderboard:{board_name}"
        return self.redis.zscore(key, user_id)

    def get_top_n(self, board_name: str, n: int = 10) -> List[Tuple[str, float]]:
        """获取Top N"""
        key = f"leaderboard:{board_name}"
        return self.redis.zrevrange(key, 0, n - 1, withscores=True)

    def get_around_me(self, board_name: str, user_id: str, count: int = 5) -> List[Tuple[str, float, int]]:
        """获取用户排名前后的玩家"""
        key = f"leaderboard:{board_name}"
        rank = self.redis.zrevrank(key, user_id)
        if rank is None:
            return []

        start = max(0, rank - count)
        end = rank + count

        # 获取范围内的用户
        results = self.redis.zrevrange(key, start, end, withscores=True)
        return [(user, score, start + i + 1) for i, (user, score) in enumerate(results)]

    def get_total_count(self, board_name: str) -> int:
        """获取总人数"""
        key = f"leaderboard:{board_name}"
        return self.redis.zcard(key)

# 使用示例
leaderboard = Leaderboard()

# 更新分数
leaderboard.update_score('daily', 'user:1001', 15000)
leaderboard.update_score('daily', 'user:1002', 18000)
leaderboard.update_score('daily', 'user:1003', 12000)

# 增加分数
leaderboard.increment_score('daily', 'user:1001', 500)

# 获取排名
print(leaderboard.get_rank('daily', 'user:1001'))

# 获取Top 10
print(leaderboard.get_top_n('daily', 10))

3.2.3 案例三:分布式限流器

需求分析

  • API限流,数据安全性要求不高
  • 写入频繁
  • 对性能敏感

配置方案

# 限流器Redis配置
maxmemory 2gb
maxmemory-policy volatile-ttl

# 仅RDB,低频保存
save 900 1

# 不开启AOF(限流数据可丢失)
appendonly no

应用代码示例

import redis
import time
from typing import Tuple

class RateLimiter:
    def __init__(self):
        self.redis = redis.Redis(
            host='localhost',
            port=6379,
            password='YourStrongPassword@2024'
        )

    def is_allowed_fixed_window(self, key: str, limit: int, window_seconds: int) -> Tuple[bool, int]:
        """
        固定窗口限流
        :param key: 限流key(如 'ratelimit:api:/users:user_1001')
        :param limit: 窗口内允许的请求数
        :param window_seconds: 窗口时间(秒)
        :return: (是否允许, 剩余配额)
        """
        current = self.redis.incr(key)
        if current == 1:
            self.redis.expire(key, window_seconds)

        remaining = max(0, limit - current)
        return current <= limit, remaining

    def is_allowed_sliding_window(self, key: str, limit: int, window_seconds: int) -> Tuple[bool, int]:
        """
        滑动窗口限流(基于Sorted Set)
        """
        now = time.time()
        window_start = now - window_seconds

        pipe = self.redis.pipeline()
        # 移除窗口外的记录
        pipe.zremrangebyscore(key, 0, window_start)
        # 添加当前请求
        pipe.zadd(key, {f"{now}": now})
        # 获取当前窗口内的请求数
        pipe.zcard(key)
        # 设置过期时间
        pipe.expire(key, window_seconds + 1)

        results = pipe.execute()
        current_count = results[2]

        if current_count > limit:
            # 超过限制,移除刚才添加的请求
            self.redis.zrem(key, f"{now}")
            return False, 0

        return True, limit - current_count

    def is_allowed_token_bucket(self, key: str, capacity: int, refill_rate: float) -> Tuple[bool, float]:
        """
        令牌桶算法
        :param key: 限流key
        :param capacity: 桶容量
        :param refill_rate: 每秒补充令牌数
        :return: (是否允许, 当前令牌数)
        """
        now = time.time()
        bucket_key = f"{key}:bucket"
        last_time_key = f"{key}:last_time"

        # 使用Lua脚本保证原子性
        lua_script = """
        local bucket_key = KEYS[1]
        local last_time_key = KEYS[2]
        local capacity = tonumber(ARGV[1])
        local refill_rate = tonumber(ARGV[2])
        local now = tonumber(ARGV[3])

        local tokens = tonumber(redis.call('GET', bucket_key) or capacity)
        local last_time = tonumber(redis.call('GET', last_time_key) or now)

        local elapsed = now - last_time
        local new_tokens = math.min(capacity, tokens + elapsed * refill_rate)

        if new_tokens >= 1 then
            new_tokens = new_tokens - 1
            redis.call('SET', bucket_key, new_tokens)
            redis.call('SET', last_time_key, now)
            redis.call('EXPIRE', bucket_key, 3600)
            redis.call('EXPIRE', last_time_key, 3600)
            return {1, new_tokens}
        else
            return {0, new_tokens}
        end
        """

        result = self.redis.eval(lua_script, 2, bucket_key, last_time_key, capacity, refill_rate, now)
        return result[0] == 1, result[1]

# 使用示例
limiter = RateLimiter()

# 固定窗口:每分钟100次请求
allowed, remaining = limiter.is_allowed_fixed_window('api:/users:1001', 100, 60)
print(f"Allowed: {allowed}, Remaining: {remaining}")

# 滑动窗口:每分钟100次请求
allowed, remaining = limiter.is_allowed_sliding_window('api:/users:1002', 100, 60)

# 令牌桶:容量100,每秒补充10个
allowed, tokens = limiter.is_allowed_token_bucket('api:/users:1003', 100, 10)

四、最佳实践和注意事项

4.1 最佳实践

4.1.1 性能优化

RDB性能优化

# 1. 调整save触发频率
# 根据数据变更频率和可接受的数据丢失量调整
save 3600 1     # 低频保存
save 300 100    # 中频保存
save 60 10000   # 高频变更时触发

# 2. 优化fork性能
# 确保禁用THP
echo never > /sys/kernel/mm/transparent_hugepage/enabled

# 3. 使用SSD存储
# RDB写入是顺序写,SSD能显著提升性能

# 4. 监控fork时间
redis-cli INFO persistence | grep rdb_last_bgsave_time_sec

AOF性能优化

# 1. 选择合适的fsync策略
appendfsync everysec    # 推荐,平衡性能和安全

# 2. 配置自动重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 3. 重写期间暂停fsync(高性能场景)
no-appendfsync-on-rewrite yes    # 可能丢失数据,谨慎使用

# 4. 使用混合持久化
aof-use-rdb-preamble yes    # 加速AOF恢复

IO线程优化(Redis 6.0+)

# 开启IO线程
io-threads 4
io-threads-do-reads yes

# 线程数建议:CPU核数-2,不超过8

4.1.2 安全加固

# 1. 设置密码
requirepass YourStrongPassword@2024

# 2. 绑定地址
bind 127.0.0.1 192.168.1.100

# 3. 禁用危险命令
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command DEBUG ""
rename-command KEYS "KEYS_ADMIN_ONLY"

# 4. ACL配置(Redis 6.0+)
# 创建只读用户
user readonly on >password ~* &* -@all +@read

# 5. 保护模式
protected-mode yes

4.1.3 高可用设计

# 主从复制配置
# 从库配置
replicaof 192.168.1.100 6379
masterauth YourStrongPassword@2024

# 从库只读
replica-read-only yes

# 无盘复制(减少磁盘IO)
repl-diskless-sync yes
repl-diskless-sync-delay 5

# Sentinel配置实现自动故障转移
# /etc/redis/sentinel.conf
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel auth-pass mymaster YourStrongPassword@2024
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

4.2 注意事项

4.2.1 常见错误

错误场景 原因 解决方案
BGSAVE失败 内存不足无法fork 增加内存或启用vm.overcommit_memory=1
AOF重写失败 磁盘空间不足 清理磁盘空间,调整重写阈值
恢复后数据丢失 只有RDB,丢失最后一次快照后数据 开启AOF或缩短RDB保存间隔
AOF文件损坏 写入中断 使用redis-check-aof修复
RDB文件损坏 写入中断 使用redis-check-rdb检查
启动时间过长 AOF文件过大 开启混合持久化,手动重写AOF

4.2.2 AOF 文件修复

# 检查AOF文件
redis-check-aof --fix /var/lib/redis/appendonlydir/appendonly.aof.1.incr.aof

# 如果使用Multi Part AOF(Redis 7.0+)
# 检查manifest文件
cat /var/lib/redis/appendonlydir/appendonly.aof.manifest

# 手动修复损坏的AOF
# 1. 备份原文件
cp -r /var/lib/redis/appendonlydir /var/lib/redis/appendonlydir.bak

# 2. 运行修复工具
redis-check-aof --fix /var/lib/redis/appendonlydir/appendonly.aof.1.incr.aof

# 3. 重启Redis
systemctl restart redis

4.2.3 RDB与AOF对比表

对比项 RDB AOF
持久化方式 定时快照 命令追加
文件格式 二进制 文本(可读)
文件大小 小(压缩) 大(命令累积)
恢复速度 慢(需重放命令)
数据安全 可能丢失数分钟数据 最多丢失1秒(everysec)
写入性能 fork有短暂阻塞 fsync可能影响
CPU消耗 压缩消耗CPU 重写消耗CPU
适用场景 备份、容灾、快速恢复 数据安全性要求高

五、故障排查和监控

5.1 故障排查

5.1.1 日志分析

# 查看Redis日志
tail -100 /var/log/redis/redis.log

# 筛选持久化相关日志
grep -E "RDB|AOF|BGSAVE|rewrite" /var/log/redis/redis.log | tail -50

# 常见日志含义
# Background saving started by pid xxx - RDB保存开始
# Background saving terminated with success - RDB保存成功
# Background AOF rewrite started by pid xxx - AOF重写开始
# Background AOF rewrite terminated with success - AOF重写成功
# Can't save in background: fork: Cannot allocate memory - 内存不足

5.1.2 持久化状态检查

# 完整的持久化状态
redis-cli INFO persistence

# 关键指标解读
# rdb_changes_since_last_save: 上次RDB后的修改次数
# rdb_bgsave_in_progress: 是否正在进行RDB
# rdb_last_save_time: 上次RDB成功的时间戳
# rdb_last_bgsave_status: 上次RDB状态
# rdb_last_bgsave_time_sec: 上次RDB耗时

# aof_enabled: AOF是否开启
# aof_rewrite_in_progress: 是否正在重写
# aof_current_size: 当前AOF文件大小
# aof_base_size: 上次重写后的大小

# 检查内存使用
redis-cli INFO memory

# 关键指标
# used_memory: 当前使用内存
# used_memory_rss: 操作系统分配的内存
# mem_fragmentation_ratio: 内存碎片率

5.1.3 fork 性能分析

# 查看fork延迟
redis-cli INFO stats | grep latest_fork_usec
# latest_fork_usec: fork耗时(微秒)

# 监控fork时间趋势
redis-cli DEBUG sleep 0  # 触发INFO更新
redis-cli INFO stats | grep fork

# fork时间过长的原因:
# 1. 内存过大
# 2. 透明大页开启
# 3. 系统负载高

# 解决方案:
# 1. 拆分实例,减少单实例内存
# 2. 禁用透明大页
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 3. 升级到更快的存储

5.2 性能监控

5.2.1 关键监控指标

指标 说明 告警阈值
rdb_last_save_time 上次RDB时间 超过设定周期的2倍
rdb_last_bgsave_status RDB状态 ok
rdb_last_bgsave_time_sec RDB耗时 >60秒
aof_last_rewrite_time_sec AOF重写耗时 >120秒
aof_current_size AOF文件大小 >5GB
latest_fork_usec fork耗时 >1000000(1秒)
used_memory_rss 内存使用 >maxmemory*2

5.2.2 Prometheus 监控配置

# prometheus/redis_exporter配置
# docker-compose.yml
version: '3'
services:
  redis-exporter:
    image: oliver006/redis_exporter:latest
    environment:
      - REDIS_ADDR=redis://redis:6379
      - REDIS_PASSWORD=YourStrongPassword@2024
    ports:
      - "9121:9121"

# prometheus告警规则
# /etc/prometheus/rules/redis_persistence.yml
groups:
  - name: redis_persistence
    rules:
      - alert: RedisRDBFailed
        expr: redis_rdb_last_save_timestamp_seconds < (time() - 7200)
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Redis RDB保存超时"
          description: "实例{{ $labels.instance }}超过2小时未成功保存RDB"

      - alert: RedisRDBSaveError
        expr: redis_rdb_last_bgsave_status != 1
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Redis RDB保存失败"
          description: "实例{{ $labels.instance }} RDB保存失败"

      - alert: RedisAOFRewriteInProgress
        expr: redis_aof_rewrite_in_progress == 1
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Redis AOF重写时间过长"
          description: "实例{{ $labels.instance }} AOF重写超过10分钟"

      - alert: RedisForkTimeHigh
        expr: redis_latest_fork_usec > 1000000
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Redis fork耗时过长"
          description: "实例{{ $labels.instance }} fork耗时{{ $value }}微秒"

      - alert: RedisMemoryHigh
        expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.9
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Redis内存使用率过高"
          description: "实例{{ $labels.instance }}内存使用率{{ $value | humanizePercentage }}"

5.2.3 监控脚本

#!/bin/bash
# 文件:/opt/redis/scripts/persistence_monitor.sh
# 功能:Redis持久化监控脚本

REDIS_CLI="redis-cli -a YourStrongPassword@2024"
ALERT_URL="https://alert.example.com/api/alert"

# 获取持久化状态
get_persistence_info() {
  $REDIS_CLI INFO persistence 2>/dev/null
}

# 检查RDB状态
check_rdb() {
  local info=$(get_persistence_info)

  # 检查RDB状态
  local status=$(echo "$info" | grep "rdb_last_bgsave_status" | cut -d: -f2 | tr -d '\r')
  if [ "$status" != "ok" ]; then
    send_alert "critical" "Redis RDB last save failed"
    return 1
  fi

  # 检查RDB时间
  local last_save=$(echo "$info" | grep "rdb_last_save_time" | cut -d: -f2 | tr -d '\r')
  local now=$(date +%s)
  local diff=$((now - last_save))

  if [ $diff -gt 7200 ]; then
    send_alert "warning" "Redis RDB save is ${diff}s old"
    return 1
  fi

  return 0
}

# 检查AOF状态
check_aof() {
  local info=$(get_persistence_info)

  # 检查AOF是否开启
  local enabled=$(echo "$info" | grep "aof_enabled" | cut -d: -f2 | tr -d '\r')
  if [ "$enabled" != "1" ]; then
    return 0  # AOF未开启,跳过检查
  fi

  # 检查AOF状态
  local status=$(echo "$info" | grep "aof_last_bgrewrite_status" | cut -d: -f2 | tr -d '\r')
  if [ "$status" != "ok" ]; then
    send_alert "warning" "Redis AOF last rewrite failed"
    return 1
  fi

  # 检查AOF文件大小
  local size=$(echo "$info" | grep "aof_current_size" | cut -d: -f2 | tr -d '\r')
  local size_gb=$((size / 1024 / 1024 / 1024))
  if [ $size_gb -gt 5 ]; then
    send_alert "warning" "Redis AOF size is ${size_gb}GB"
    return 1
  fi

  return 0
}

# 检查fork时间
check_fork() {
  local fork_usec=$($REDIS_CLI INFO stats 2>/dev/null | grep "latest_fork_usec" | cut -d: -f2 | tr -d '\r')

  if [ -n "$fork_usec" ] && [ $fork_usec -gt 1000000 ]; then
    send_alert "warning" "Redis fork time is ${fork_usec}us"
    return 1
  fi

  return 0
}

# 发送告警
send_alert() {
  local level=$1
  local message=$2

  curl -s -X POST "$ALERT_URL" \
    -H "Content-Type: application/json" \
    -d "{\"level\":\"$level\",\"message\":\"$message\",\"timestamp\":\"$(date -Iseconds)\"}"

  echo "[$(date)] [$level] $message"
}

# 主程序
main() {
  echo "Starting Redis persistence check..."

  check_rdb
  check_aof
  check_fork

  echo "Check completed"
}

main

5.3 备份与恢复

5.3.1 RDB 备份策略

#!/bin/bash
# 文件:/opt/redis/scripts/rdb_backup.sh
# 功能:RDB备份脚本

REDIS_CLI="redis-cli -a YourStrongPassword@2024"
REDIS_DATA_DIR="/var/lib/redis"
BACKUP_DIR="/backup/redis/rdb"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7

mkdir -p $BACKUP_DIR

# 触发BGSAVE
echo "Triggering BGSAVE..."
$REDIS_CLI BGSAVE

# 等待完成
while [ "$($REDIS_CLI LASTSAVE)" == "$(cat /tmp/last_save 2>/dev/null)" ]; do
  sleep 1
done
$REDIS_CLI LASTSAVE > /tmp/last_save

# 复制RDB文件
echo "Copying RDB file..."
cp ${REDIS_DATA_DIR}/dump.rdb ${BACKUP_DIR}/dump_${DATE}.rdb

# 压缩
gzip ${BACKUP_DIR}/dump_${DATE}.rdb

# 清理旧备份
find $BACKUP_DIR -name "*.rdb.gz" -mtime +$RETENTION_DAYS -delete

echo "RDB backup completed: ${BACKUP_DIR}/dump_${DATE}.rdb.gz"

5.3.2 AOF 备份策略

#!/bin/bash
# 文件:/opt/redis/scripts/aof_backup.sh
# 功能:AOF备份脚本

REDIS_DATA_DIR="/var/lib/redis"
AOF_DIR="${REDIS_DATA_DIR}/appendonlydir"
BACKUP_DIR="/backup/redis/aof"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=3

mkdir -p $BACKUP_DIR

# 备份AOF目录
echo "Backing up AOF directory..."
tar -czf ${BACKUP_DIR}/aof_${DATE}.tar.gz -C ${REDIS_DATA_DIR} appendonlydir

# 清理旧备份
find $BACKUP_DIR -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete

echo "AOF backup completed: ${BACKUP_DIR}/aof_${DATE}.tar.gz"

5.3.3 恢复流程

#!/bin/bash
# 文件:/opt/redis/scripts/restore.sh
# 功能:Redis数据恢复脚本

BACKUP_TYPE=$1 # rdb 或 aof
BACKUP_FILE=$2 # 备份文件路径
REDIS_DATA_DIR="/var/lib/redis"

case $BACKUP_TYPE in
  rdb)
    echo "Restoring from RDB..."

    # 停止Redis
    systemctl stop redis

    # 备份当前数据
    mv ${REDIS_DATA_DIR}/dump.rdb ${REDIS_DATA_DIR}/dump.rdb.bak.$(date +%s)

    # 解压并恢复
    if [[ $BACKUP_FILE == *.gz ]]; then
      gunzip -c $BACKUP_FILE > ${REDIS_DATA_DIR}/dump.rdb
    else
      cp $BACKUP_FILE ${REDIS_DATA_DIR}/dump.rdb
    fi

    chown redis:redis ${REDIS_DATA_DIR}/dump.rdb

    # 启动Redis
    systemctl start redis
    ;;

  aof)
    echo "Restoring from AOF..."

    # 停止Redis
    systemctl stop redis

    # 备份当前AOF
    mv ${REDIS_DATA_DIR}/appendonlydir ${REDIS_DATA_DIR}/appendonlydir.bak.$(date +%s)

    # 解压并恢复
    tar -xzf $BACKUP_FILE -C ${REDIS_DATA_DIR}

    chown -R redis:redis ${REDIS_DATA_DIR}/appendonlydir

    # 启动Redis
    systemctl start redis
    ;;

  *)
    echo "Usage: $0 [rdb|aof] <backup_file>"
    exit 1
    ;;
esac

# 验证恢复
sleep 2
redis-cli -a YourStrongPassword@2024 PING
redis-cli -a YourStrongPassword@2024 DBSIZE

echo "Restore completed"

六、总结

6.1 技术要点回顾

  1. RDB适合快速恢复和备份:二进制格式紧凑,加载快,但可能丢失最后一次快照后的数据。
  2. AOF适合数据安全性要求高的场景:记录每个写命令,最多丢失1秒数据(everysec策略)。
  3. 混合持久化是最佳选择:结合RDB的快速恢复和AOF的数据安全,是生产环境推荐配置。
  4. fork是持久化的性能瓶颈:大内存实例fork可能导致短暂阻塞,需要禁用THP并监控fork时间。
  5. 定期备份和监控是保障:持久化文件需要定期备份到远程存储,监控持久化状态。

6.2 进阶学习方向

  • Redis Cluster持久化:集群模式下的持久化策略。
  • Redis on Flash:SSD扩展内存,减少持久化压力。
  • Redis Streams持久化:消息流的持久化特性。
  • Redis企业版持久化:Active-Active地理分布式复制。

掌握Redis持久化的原理与实践,是构建稳定、可靠中间件服务的关键一步。希望本文提供的配置指南和最佳实践能帮助你在生产环境中更好地驾驭Redis。如果你想深入探讨更多系统架构与性能优化的话题,欢迎访问云栈社区与广大开发者交流。

附录

A. 命令速查表

命令 说明
BGSAVE 后台保存RDB
SAVE 同步保存RDB(阻塞)
LASTSAVE 上次成功保存时间戳
BGREWRITEAOF 后台重写AOF
CONFIG GET save 查看RDB保存配置
CONFIG SET appendonly yes 动态开启AOF
DEBUG RELOAD 重载RDB(测试用)
INFO persistence 查看持久化状态

B. 配置参数详解

参数 默认值 说明
save "3600 1 300 100 60 10000" RDB触发条件
stop-writes-on-bgsave-error yes BGSAVE失败时停止写入
rdbcompression yes RDB压缩
rdbchecksum yes RDB校验
appendonly no 开启AOF
appendfsync everysec AOF同步策略
no-appendfsync-on-rewrite no 重写期间暂停fsync
auto-aof-rewrite-percentage 100 自动重写触发比例
auto-aof-rewrite-min-size 64mb 自动重写最小文件大小
aof-use-rdb-preamble yes 混合持久化

C. 术语表

术语 英文 说明
RDB Redis Database Redis数据库快照文件
AOF Append Only File 追加只写文件
BGSAVE Background Save 后台保存
fork - 创建子进程
COW Copy On Write 写时复制
fsync File Sync 文件同步到磁盘
Rewrite - AOF重写
THP Transparent Huge Pages 透明大页
PITR Point In Time Recovery 时间点恢复



上一篇:TRAE Skills快速上手指南:从概念理解到实战应用
下一篇:解决容器权限冲突:Redis 8.4与Redis Insight 3.0挂载目录实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-25 18:21 , Processed in 0.324700 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表