Python脚本如何批量迁移缓存存储数据

wen python案例 31

本文目录导读:

Python脚本如何批量迁移缓存存储数据

  1. 通用缓存迁移脚本
  2. Redis缓存迁移脚本
  3. Memcached缓存迁移脚本
  4. 文件系统缓存迁移(带验证)
  5. 增量迁移与进度显示
  6. 使用建议
  7. 安装依赖

我来为您介绍几种Python批量迁移缓存数据的方案。

通用缓存迁移脚本

import json
import pickle
import os
from typing import Any, Dict, List
import shutil
from datetime import datetime
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class CacheMigrator:
    """缓存数据迁移器"""
    def __init__(self, source_path: str, target_path: str, format_type: str = 'json'):
        """
        初始化迁移器
        :param source_path: 源缓存路径
        :param target_path: 目标缓存路径
        :param format_type: 数据格式 (json/pickle)
        """
        self.source_path = source_path
        self.target_path = target_path
        self.format_type = format_type
        self.stats = {'success': 0, 'failed': 0, 'skipped': 0}
    def migrate_single_file(self, filename: str) -> bool:
        """迁移单个缓存文件"""
        try:
            source_file = os.path.join(self.source_path, filename)
            target_file = os.path.join(self.target_path, filename)
            # 检查文件是否存在
            if not os.path.exists(source_file):
                self.stats['skipped'] += 1
                logger.warning(f"源文件不存在: {filename}")
                return False
            # 检查目标文件是否已存在
            if os.path.exists(target_file):
                # 可以选择覆盖或跳过
                if not self.overwrite_existing:
                    self.stats['skipped'] += 1
                    logger.info(f"目标文件已存在,跳过: {filename}")
                    return False
            # 读取源文件
            data = self._read_cache(source_file)
            # 写入目标文件
            self._write_cache(target_file, data)
            self.stats['success'] += 1
            logger.info(f"成功迁移: {filename}")
            return True
        except Exception as e:
            self.stats['failed'] += 1
            logger.error(f"迁移失败 {filename}: {str(e)}")
            return False
    def _read_cache(self, filepath: str) -> Any:
        """读取缓存文件"""
        with open(filepath, 'rb') as f:
            if self.format_type == 'json':
                return json.load(f)
            elif self.format_type == 'pickle':
                return pickle.load(f)
            else:
                raise ValueError(f"不支持的数据格式: {self.format_type}")
    def _write_cache(self, filepath: str, data: Any) -> None:
        """写入缓存文件"""
        os.makedirs(os.path.dirname(filepath), exist_ok=True)
        with open(filepath, 'wb' if self.format_type == 'pickle' else 'w') as f:
            if self.format_type == 'json':
                json.dump(data, f, ensure_ascii=False, indent=2)
            elif self.format_type == 'pickle':
                pickle.dump(data, f)
    def batch_migrate(self, file_pattern: str = '*', overwrite: bool = False) -> Dict:
        """
        批量迁移缓存文件
        :param file_pattern: 文件匹配模式
        :param overwrite: 是否覆盖已存在的文件
        """
        self.overwrite_existing = overwrite
        import glob
        source_files = glob.glob(os.path.join(self.source_path, file_pattern))
        logger.info(f"找到 {len(source_files)} 个缓存文件待迁移")
        for file_path in source_files:
            filename = os.path.basename(file_path)
            self.migrate_single_file(filename)
        logger.info(f"迁移完成 - 成功: {self.stats['success']}, "
                   f"失败: {self.stats['failed']}, 跳过: {self.stats['skipped']}")
        return self.stats
# 使用示例
def migrate_cache_example():
    # 基本迁移
    migrator = CacheMigrator(
        source_path='/path/to/source/cache',
        target_path='/path/to/target/cache',
        format_type='json'
    )
    # 迁移所有文件
    stats = migrator.batch_migrate(overwrite=False)
    print(f"迁移统计: {stats}")
    # 只迁移特定模式的文件
    stats = migrator.batch_migrate(file_pattern='*.cache', overwrite=True)

Redis缓存迁移脚本

import redis
import json
from typing import Iterator, Dict, Any
class RedisCacheMigrator:
    """Redis缓存迁移器"""
    def __init__(self, source_config: Dict, target_config: Dict, 
                 batch_size: int = 1000):
        """
        初始化Redis迁移器
        :param source_config: 源Redis配置
        :param target_config: 目标Redis配置
        :param batch_size: 批量处理大小
        """
        self.source_redis = redis.StrictRedis(**source_config)
        self.target_redis = redis.StrictRedis(**target_config)
        self.batch_size = batch_size
        self.stats = {'migrated': 0, 'failed': 0, 'total': 0}
    def migrate_keys_by_pattern(self, pattern: str = '*') -> Dict:
        """
        根据模式迁移键
        :param pattern: 键匹配模式
        """
        cursor = 0
        total_keys = 0
        while True:
            cursor, keys = self.source_redis.scan(
                cursor=cursor, 
                match=pattern, 
                count=self.batch_size
            )
            if keys:
                self._migrate_keys(keys)
                total_keys += len(keys)
            if cursor == 0:
                break
        return self.stats
    def _migrate_keys(self, keys: list) -> None:
        """批量迁移键值对"""
        pipeline = self.target_redis.pipeline()
        for key in keys:
            try:
                # 获取键的类型
                key_type = self.source_redis.type(key).decode()
                # 获取TTL
                ttl = self.source_redis.ttl(key)
                # 根据类型迁移
                if key_type == 'string':
                    value = self.source_redis.get(key)
                    pipeline.set(key, value)
                elif key_type == 'hash':
                    value = self.source_redis.hgetall(key)
                    pipeline.hset(key, mapping=value)
                elif key_type == 'list':
                    value = self.source_redis.lrange(key, 0, -1)
                    pipeline.rpush(key, *value)
                elif key_type == 'set':
                    value = self.source_redis.smembers(key)
                    pipeline.sadd(key, *value)
                elif key_type == 'zset':
                    value = self.source_redis.zrange(key, 0, -1, withscores=True)
                    pipeline.zadd(key, dict(value))
                # 设置TTL
                if ttl > 0:
                    pipeline.expire(key, ttl)
                self.stats['migrated'] += 1
            except Exception as e:
                self.stats['failed'] += 1
                print(f"迁移键 {key} 失败: {e}")
        # 执行批量操作
        pipeline.execute()
    def verify_migration(self, pattern: str = '*', sample_size: int = 100) -> bool:
        """
        验证迁移结果
        """
        source_keys = list(self.source_redis.scan_iter(match=pattern))
        target_keys = list(self.target_redis.scan_iter(match=pattern))
        print(f"源Redis键数: {len(source_keys)}")
        print(f"目标Redis键数: {len(target_keys)}")
        # 采样验证
        import random
        sample_keys = random.sample(source_keys, min(sample_size, len(source_keys)))
        for key in sample_keys:
            source_val = self.source_redis.get(key)
            target_val = self.target_redis.get(key)
            if source_val != target_val:
                print(f"键 {key} 值不匹配")
                return False
        return True
# 使用示例
def redis_migration_example():
    # 配置源和目标Redis
    source_config = {
        'host': 'source-redis-host',
        'port': 6379,
        'db': 0,
        'password': 'source-password'
    }
    target_config = {
        'host': 'target-redis-host',
        'port': 6379,
        'db': 0,
        'password': 'target-password'
    }
    # 创建迁移器
    migrator = RedisCacheMigrator(source_config, target_config, batch_size=500)
    # 执行迁移
    stats = migrator.migrate_keys_by_pattern('user:*')
    print(f"迁移完成: {stats}")
    # 验证迁移
    if migrator.verify_migration('user:*', sample_size=50):
        print("迁移验证通过")
    else:
        print("迁移验证失败")

Memcached缓存迁移脚本

from pymemcache.client.base import Client
from pymemcache import serde
import pickle
import sys
class MemcachedMigrator:
    """Memcached缓存迁移器"""
    def __init__(self, source_server: tuple, target_server: tuple):
        """
        初始化Memcached迁移器
        :param source_server: (host, port)
        :param target_server: (host, port)
        """
        self.source = Client(source_server, serde=serde.pickle_serde)
        self.target = Client(target_server, serde=serde.pickle_serde)
        self.stats = {'migrated': 0, 'failed': 0}
    def migrate_key(self, key: str) -> bool:
        """迁移单个键"""
        try:
            value = self.source.get(key)
            if value is not None:
                # 获取过期时间
                expires = self.source.get_expiration(key)
                # 设置到目标
                self.target.set(key, value, expire=expires)
                self.stats['migrated'] += 1
                return True
        except Exception as e:
            self.stats['failed'] += 1
            print(f"迁移键 {key} 失败: {e}")
        return False
    def migrate_all_keys(self, keys: list) -> Dict:
        """迁移所有指定的键"""
        for key in keys:
            self.migrate_key(key)
        return self.stats
# 使用示例
def memcached_migration_example():
    migrator = MemcachedMigrator(
        source_server=('localhost', 11211),
        target_server=('localhost', 11212)
    )
    # 假设我们有一个要迁移的键列表
    keys_to_migrate = ['user:1', 'user:2', 'session:abc']
    stats = migrator.migrate_all_keys(keys_to_migrate)
    print(f"Memcached迁移完成: {stats}")

文件系统缓存迁移(带验证)

import hashlib
from pathlib import Path
class FileCacheMigrator:
    """文件系统缓存迁移器(带完整性验证)"""
    def __init__(self, source_dir: str, target_dir: str):
        self.source_dir = Path(source_dir)
        self.target_dir = Path(target_dir)
        self.stats = {'copied': 0, 'verified': 0, 'failed': 0}
    def copy_with_verification(self, relative_path: str) -> bool:
        """复制文件并验证完整性"""
        source_file = self.source_dir / relative_path
        target_file = self.target_dir / relative_path
        if not source_file.exists():
            self.stats['failed'] += 1
            return False
        try:
            # 创建目标目录
            target_file.parent.mkdir(parents=True, exist_ok=True)
            # 计算源文件哈希
            source_hash = self._calculate_hash(source_file)
            # 复制文件
            import shutil
            shutil.copy2(source_file, target_file)
            # 验证目标文件哈希
            target_hash = self._calculate_hash(target_file)
            if source_hash == target_hash:
                self.stats['verified'] += 1
                return True
            else:
                target_file.unlink()  # 删除损坏的文件
                self.stats['failed'] += 1
                return False
        except Exception as e:
            self.stats['failed'] += 1
            print(f"复制失败 {relative_path}: {e}")
            return False
    def _calculate_hash(self, filepath: Path) -> str:
        """计算文件哈希"""
        sha256_hash = hashlib.sha256()
        with open(filepath, 'rb') as f:
            for byte_block in iter(lambda: f.read(4096), b""):
                sha256_hash.update(byte_block)
        return sha256_hash.hexdigest()
    def batch_migrate(self, pattern: str = '**/*') -> Dict:
        """批量迁移文件"""
        files = list(self.source_dir.glob(pattern))
        for filepath in files:
            if filepath.is_file():
                relative_path = filepath.relative_to(self.source_dir)
                if self.copy_with_verification(str(relative_path)):
                    self.stats['copied'] += 1
        return self.stats
# 使用示例
def file_cache_migration_example():
    migrator = FileCacheMigrator(
        source_dir='/var/cache/app_v1',
        target_dir='/var/cache/app_v2'
    )
    stats = migrator.batch_migrate('*.cache')
    print(f"文件缓存迁移完成: {stats}")

增量迁移与进度显示

import time
from tqdm import tqdm
class IncrementalCacheMigrator:
    """增量缓存迁移器(带进度显示)"""
    def __init__(self, migrator, checkpoint_file: str = 'migration_checkpoint.json'):
        """
        初始化增量迁移器
        :param migrator: 基础迁移器实例
        :param checkpoint_file: 检查点文件路径
        """
        self.migrator = migrator
        self.checkpoint_file = checkpoint_file
        self.checkpoint = self._load_checkpoint()
    def _load_checkpoint(self) -> set:
        """加载检查点"""
        if os.path.exists(self.checkpoint_file):
            with open(self.checkpoint_file, 'r') as f:
                return set(json.load(f))
        return set()
    def _save_checkpoint(self, migrated_keys: set):
        """保存检查点"""
        self.checkpoint.update(migrated_keys)
        with open(self.checkpoint_file, 'w') as f:
            json.dump(list(self.checkpoint), f)
    def incremental_migrate(self, keys: list, show_progress: bool = True) -> Dict:
        """增量迁移(只迁移未迁移过的键)"""
        keys_to_migrate = [k for k in keys if k not in self.checkpoint]
        if show_progress:
            pbar = tqdm(total=len(keys_to_migrate), desc="迁移进度")
        for key in keys_to_migrate:
            success = self.migrator.migrate_key(key)
            if success:
                self.checkpoint.add(key)
            if show_progress:
                pbar.update(1)
                pbar.set_postfix({
                    '成功': len(self.checkpoint),
                    '总': len(keys)
                })
        if show_progress:
            pbar.close()
        # 保存检查点
        self._save_checkpoint(self.checkpoint)
        return {
            'total': len(keys),
            'migrated': len(self.checkpoint),
            'remaining': len(keys) - len(self.checkpoint)
        }
# 使用示例
def incremental_migration_example():
    # 假设我们有一个简单的迁移器
    class SimpleMigrator:
        def migrate_key(self, key):
            # 模拟迁移操作
            time.sleep(0.1)
            return True
    basic_migrator = SimpleMigrator()
    incremental_migrator = IncrementalCacheMigrator(basic_migrator)
    # 模拟要迁移的键
    keys = [f"key_{i}" for i in range(100)]
    # 执行增量迁移
    result = incremental_migrator.incremental_migrate(keys)
    print(f"增量迁移结果: {result}")
    # 第二次运行只会迁移剩余的键
    result2 = incremental_migrator.incremental_migrate(keys)
    print(f"第二次迁移结果: {result2}")

使用建议

  1. 备份数据:迁移前务必备份源数据
  2. 分批处理:大数据量时分批迁移,避免内存溢出
  3. 验证完整性:迁移后验证数据完整性
  4. 错误处理:实现断点续传机制
  5. 性能优化:使用批量操作和并行处理

安装依赖

pip install redis pymemcache tqdm

选择合适的迁移方案取决于您的具体缓存系统和需求。

抱歉,评论功能暂时关闭!