本文目录导读:

针对Python数据API的备份策略,核心目标是在数据丢失(误删、损坏、被黑)或系统故障时,能够快速恢复服务。
下面是一套从策略设计到代码实现的完整方案,涵盖最常用的三种场景:JSON/CSV文件、SQLite/MySQL数据库、Redis缓存。
第一阶段:策略设计(先定规则再写代码)
| 维度 | 推荐方案(针对小型/中型API) | 说明 |
|---|---|---|
| 备份频率 | 全量备份:每天一次(凌晨低峰期) 增量/差异备份:每1-6小时一次 |
减少存储压力,同时保证RPO(恢复点目标)在1小时内 |
| 保留周期 | 最近7天全量 + 最近24小时增量 | 节省存储空间,满足大多数回滚需求 |
| 存储位置 | 本地磁盘 + 云存储(对象存储) | 避免单点故障(如硬盘损坏) |
| 验证机制 | 备份完成后自动校验MD5/SHA256 | 防止静默损坏 |
| 恢复演练 | 每月至少一次自动化恢复测试 | 确保备份真的可用 |
第二阶段:具体实现(Python脚本)
以下代码覆盖文件型数据、SQLite、MySQL、Redis四种常见API后端。
通用工具函数(备份文件、验证、清理旧备份)
import os
import shutil
import hashlib
import datetime
import schedule
import time
BACKUP_ROOT = "/data/backups" # 备份根目录
RETENTION_DAYS = 7 # 保留天数
def calculate_md5(file_path):
"""计算文件MD5用于校验"""
hash_md5 = hashlib.md5()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def clean_old_backups(backup_type_dir):
"""删除指定目录下超过保留天数的备份"""
now = time.time()
for f in os.listdir(backup_type_dir):
full_path = os.path.join(backup_type_dir, f)
if os.path.isfile(full_path):
# 从文件名解析日期,假设格式为: data_backup_20231001_120000.tar.gz
parts = f.split('_')
if len(parts) >= 3:
try:
file_time = datetime.datetime.strptime(parts[-2] + parts[-1].split('.')[0], "%Y%m%d%H%M%S")
if (datetime.datetime.now() - file_time).days > RETENTION_DAYS:
os.remove(full_path)
print(f"【清理】删除过期备份: {f}")
except ValueError:
pass
def verify_backup(backup_path):
"""验证备份文件完整性(可扩展为解压校验)"""
return os.path.exists(backup_path) and os.path.getsize(backup_path) > 0
备份JSON/CSV文件型数据
def backup_json_data(api_data_path, backup_category="json"):
"""备份API使用的JSON/CSV数据文件"""
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
backup_dir = os.path.join(BACKUP_ROOT, backup_category)
os.makedirs(backup_dir, exist_ok=True)
backup_filename = f"{backup_category}_backup_{timestamp}.tar.gz"
backup_fullpath = os.path.join(backup_dir, backup_filename)
# 压缩备份(比单纯复制节省空间)
shutil.make_archive(backup_fullpath.replace('.tar.gz', ''), 'gztar', api_data_path)
# 校验
md5 = calculate_md5(backup_fullpath)
with open(backup_fullpath + ".md5", "w") as f:
f.write(md5)
# 清理旧备份
clean_old_backups(backup_dir)
# 同步到云存储(示例:阿里云OSS/S3)
# upload_to_oss(backup_fullpath)
print(f"✅ 文件数据备份成功: {backup_fullpath} (MD5: {md5[:8]}...)")
return backup_fullpath
# 使用: backup_json_data("/data/api/data/")
备份SQLite数据库(API常用轻量级库)
SQLite是文件型数据库,直接复制可能会损坏(写入中),需要使用 sqlite3 模块的逻辑备份。
import sqlite3
def backup_sqlite(db_path, backup_category="sqlite"):
"""安全的SQLite在线备份(VACUUM INTO 或 .backup)"""
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
backup_dir = os.path.join(BACKUP_ROOT, backup_category)
os.makedirs(backup_dir, exist_ok=True)
backup_filename = f"{backup_category}_backup_{timestamp}.db"
backup_fullpath = os.path.join(backup_dir, backup_filename)
# 方法1:使用Python sqlite3 的备份API(推荐,支持热备份)
source_conn = sqlite3.connect(db_path)
backup_conn = sqlite3.connect(backup_fullpath)
source_conn.backup(backup_conn, pages=1024) # 逐页复制,不锁原库
source_conn.close()
backup_conn.close()
# 校验
md5 = calculate_md5(backup_fullpath)
with open(backup_fullpath + ".md5", "w") as f:
f.write(md5)
clean_old_backups(backup_dir)
print(f"✅ SQLite备份成功: {backup_fullpath}")
return backup_fullpath
# 使用: backup_sqlite("/data/api/mydatabase.db")
备份MySQL数据库(使用mysqldump)
import subprocess
def backup_mysql(host='localhost', user='root', password='yourpass', db_name='api_db', backup_category="mysql"):
"""使用mysqldump备份MySQL(生产环境建议用 xtrabackup 做物理备份)"""
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
backup_dir = os.path.join(BACKUP_ROOT, backup_category)
os.makedirs(backup_dir, exist_ok=True)
backup_filename = f"{backup_category}_backup_{timestamp}.sql.gz"
backup_fullpath = os.path.join(backup_dir, backup_filename)
# 压缩导出
dump_cmd = f"mysqldump -h{host} -u{user} -p{password} --single-transaction --routines --triggers {db_name} | gzip > {backup_fullpath}"
try:
subprocess.run(dump_cmd, shell=True, check=True, timeout=3600)
md5 = calculate_md5(backup_fullpath)
with open(backup_fullpath + ".md5", "w") as f:
f.write(md5)
clean_old_backups(backup_dir)
print(f"✅ MySQL备份成功: {backup_fullpath}")
except subprocess.CalledProcessError as e:
print(f"❌ MySQL备份失败: {e}")
return None
return backup_fullpath
备份Redis缓存数据(RDB快照)
import redis
def backup_redis(redis_host='localhost', redis_port=6379, backup_category="redis"):
"""对Redis执行BGSAVE并复制RDB文件"""
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
backup_dir = os.path.join(BACKUP_ROOT, backup_category)
os.makedirs(backup_dir, exist_ok=True)
client = redis.Redis(host=redis_host, port=redis_port)
# 触发BGSAVE(异步,不阻塞服务)
client.bgsave()
print("⏳ Redis BGSAVE 已触发,等待完成...")
# 等待BGSAVE完成(最多30秒)
for _ in range(30):
info = client.info("persistence")
if info.get("rdb_bgsave_in_progress") == 0:
break
time.sleep(1)
# 获取RDB文件路径(通常在 /var/lib/redis/dump.rdb)
rdb_path = client.config_get("dir")["dir"] + "/dump.rdb"
if os.path.exists(rdb_path):
backup_filename = f"{backup_category}_backup_{timestamp}.rdb"
backup_fullpath = os.path.join(backup_dir, backup_filename)
shutil.copy2(rdb_path, backup_fullpath)
md5 = calculate_md5(backup_fullpath)
with open(backup_fullpath + ".md5", "w") as f:
f.write(md5)
clean_old_backups(backup_dir)
print(f"✅ Redis备份成功: {backup_fullpath}")
return backup_fullpath
else:
print("❌ Redis RDB文件未找到")
return None
统一调度器(定时任务)
def daily_full_backup():
"""每日凌晨2点执行全量备份"""
print(f"开始每日全量备份: {datetime.datetime.now()}")
backup_json_data("/data/api/data/")
backup_sqlite("/data/api/mydatabase.db")
backup_mysql()
backup_redis()
print("全量备份完成")
def hourly_incremental_backup():
"""每小时增量备份(仅针对主要数据文件)"""
print(f"开始增量备份: {datetime.datetime.now()}")
# 增量备份方案(示例:仅备份变化文件)
# rsync --link-dest=/data/backups/json/previous_full /data/api/data/ /data/backups/json/incremental/
print("增量备份完成")
# 定时任务配置
schedule.every().day.at("02:00").do(daily_full_backup)
schedule.every(1).hour.do(hourly_incremental_backup)
if __name__ == "__main__":
print("备份守护进程启动中...")
while True:
schedule.run_pending()
time.sleep(60) # 每分钟检查一次
第三阶段:云存储与异地容灾(关键!)
仅存在本地硬盘的备份是不安全的(硬盘损坏、勒索病毒)。
def sync_to_cloud(local_backup_path, cloud_path="backups/api/"):
"""同步到阿里云OSS / AWS S3(需安装boto3)"""
import boto3
s3 = boto3.client('s3',
aws_access_key_id='your_key',
aws_secret_access_key='your_secret')
s3.upload_file(local_backup_path, 'your-bucket',
cloud_path + os.path.basename(local_backup_path))
print(f"☁️ 已同步到云存储: {cloud_path}")
或者使用 rsync 到另一台服务器(增量、带压缩):
rsync -avz --delete /data/backups/ user@remote-server:/data/backups/
第四阶段:恢复测试(最重要的一步)
创建 restore_test.py,每月自动跑一次:
def test_restore_json(backup_file, restore_path="/tmp/test_restore"):
"""解压备份到临时目录,校验文件完整性"""
shutil.rmtree(restore_path, ignore_errors=True)
shutil.unpack_archive(backup_file, restore_path)
# 校验MD5
# 模拟API启动,测试读取
print(f"✅ JSON恢复测试通过: {backup_file}")
def test_restore_sqlite(backup_db):
"""恢复SQLite并执行简单查询"""
conn = sqlite3.connect(backup_db)
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='table'")
tables = cursor.fetchone()[0]
print(f"✅ SQLite恢复测试: 包含 {tables} 个表")
conn.close()
给你的API建议
| 数据规模 | 推荐备份策略 | 工具/方式 |
|---|---|---|
| < 10MB (JSON/SQLite) | 全量备份 + 同步到Git外置仓库 | 上面脚本 + git (二进制文件慎用) |
| 10MB - 1GB | 每日全量 + 每4小时增量 | 上面脚本 + rsync |
| > 1GB (MySQL/PostgreSQL) | 物理备份 (xtrabackup / pg_basebackup) + Binlog | 专业工具,不要用mysqldump |
| Redis | RDB定期转存 + AOF日志归档 | BGSAVE + 文件复制 |
最后提醒:不要把备份脚本和业务代码混在一起!单独部署一个 Backup Service(可以是一个独立的Python进程 + crontab),这样即使API挂了,备份依然能运行。