首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 實作 Redis 資料遷移:跨資料庫完整複製腳本

     編輯:PHP 綜合知識

Redis 資料跨庫遷移 PHP 完整解決方案

Redis 叢集擴容、環境遷移、資料備份等場景常需在不同 Redis 實例/資料庫間複製資料。本文提供支援所有 Redis 資料類型 (String、List、Hash、Set、Sorted Set) 的完整遷移腳本,含進度顯示與錯誤處理。

完整遷移腳本

<?php
/**
 * Redis 資料遷移腳本
 * 支援類型: String, List, Hash, Set, Sorted Set
 * 
 * 使用方式:
 * php migrate.php '127.0.0.1:6379/0' '127.0.0.1:6379/1'
 */

// 來源與目標連線字串格式: host:port/db
$from = $argv[1] ?? '127.0.0.1:6379/0';
$to   = $argv[2] ?? '127.0.0.1:6379/1';

echo "遷移: {$from} → {$to}\n";

$fromRedis = redisInit($from);
$toRedis   = redisInit($to);

// 掃描所有鍵 (使用 SCAN 避免阻塞)
$keys = [];
$iterator = null;
while ($keysBatch = $fromRedis->scan($iterator, '*', 1000)) {
    $keys = array_merge($keys, $keysBatch);
    if ($iterator === 0) break;
}

$total = count($keys);
$count = 0;

echo "發現 {$total} 個鍵,開始遷移...\n";

foreach ($keys as $key) {
    $count++;
    
    // 進度顯示
    if ($count % 100 === 1 || $count === $total) {
        echo "進度: {$count}/{$total} (" . number_format($count/$total*100, 1) "%)\n";
    }
    
    $type = $fromRedis->type($key);
    
    try {
        switch ($type) {
            case Redis::REDIS_STRING:
                $val = $fromRedis->get($key);
                $toRedis->set($key, $val);
                break;
                
            case Redis::REDIS_LIST:
                $list = $fromRedis->lRange($key, 0, -1);
                if ($list) {
                    $toRedis->delete($key);  // 確保目標不存在
                    foreach ($list as $val) {
                        $toRedis->rPush($key, $val);
                    }
                }
                break;
                
            case Redis::REDIS_HASH:
                $hash = $fromRedis->hGetAll($key);
                if ($hash) {
                    $toRedis->hMSet($key, $hash);
                }
                break;
                
            case Redis::REDIS_SET:
                $members = $fromRedis->sMembers($key);
                if ($members) {
                    $toRedis->sAdd($key, ...$members);
                }
                break;
                
            case Redis::REDIS_ZSET:
                $zset = $fromRedis->zRange($key, 0, -1, true);  // true = with scores
                if ($zset) {
                    foreach ($zset as $member => $score) {
                        $toRedis->zAdd($key, $score, $member);
                    }
                }
                break;
                
            default:
                echo "⚠️  未知類型 {$type} 鍵: {$key},跳過\n";
        }
    } catch (Exception $e) {
        echo "❌ 遷移失敗 [{$key}]: " . $e->getMessage() . "\n";
    }
}

echo "✅ 遷移完成!共處理 {$count} 個鍵\n";

/**
 * 建立 Redis 連線
 * @param string $connStr 格式: host:port/db
 * @return Redis
 */
function redisInit($connStr) {
    $redis = new Redis();
    
    // 解析連線字串
    // 格式: host:port/db 或 host/db (預設 port 6379)
    if (!preg_match('/^([^:]+)(?::(\d+))?(?:\/(\d+))?$/', $connStr, $matches)) {
        throw new InvalidArgumentException("無效連線字串: {$connStr}");
    }
    
    $host = $matches[1];
    $port = $matches[2] ?? 6379;
    $db   = $matches[3] ?? 0;
    
    if (!$redis->connect($host, (int)$port)) {
        throw new RuntimeException("無法連線 Redis: {$host}:{$port}");
    }
    
    if (!$redis->select((int)$db)) {
        throw new RuntimeException("無法選擇資料庫: {$db}");
    }
    
    // 驗證連線
    if ($redis->ping() !== '+PONG') {
        throw new RuntimeException("Redis PING 失敗");
    }
    
    return $redis;
}
?>

關鍵技術點解析

1. SCAN vs KEYS

<?php
// ❌ 不要在生產環境使用 KEYS * (阻塞單執行緒)
// $keys = $redis->keys('*');  

// ✅ 使用 SCAN 迭代器 (非阻塞,游標式)
$iterator = null;
while ($batch = $redis->scan($iterator, '*', 1000)) {
    // 處理批次
    if ($iterator === 0) break;  // 遊標歸零表示結束
}

// PHP Redis 擴充 5.3.0+ 支援 scan 回傳陣列直接
$keys = [];
$iterator = null;
do {
    $batch = $redis->scan($iterator, '*', 1000);
    $keys = array_merge($keys, $batch);
} while ($iterator !== 0);
?>

2. 管線化批次寫入 (效能優化)

<?php
// 大量資料時使用 Pipeline 減少網路往返
$pipe = $toRedis->multi(Redis::PIPELINE);

foreach ($keys as $key) {
    $type = $fromRedis->type($key);
    switch ($type) {
        case Redis::REDIS_STRING:
            $pipe->set($key, $fromRedis->get($key));
            break;
        case Redis::REDIS_HASH:
            $pipe->hMSet($key, $fromRedis->hGetAll($key));
            break;
        // ... 其他類型
    }
    
    // 每 1000 條執行一次
    if (++$count % 1000 === 0) {
        $pipe->exec();
        $pipe = $toRedis->multi(Redis::PIPELINE);
    }
}

$pipe->exec();  // 執行剩餘
?>

3. TTL 保留遷移

<?php
// 保留原鍵的過期時間
$ttl = $fromRedis->ttl($key);
if ($ttl > 0) {
    $toRedis->expire($key, $ttl);
} elseif ($ttl === -1) {
    $toRedis->persist($key);  // 移除過期時間
}

// 更精確: 取得毫秒級 TTL (Redis 2.6+)
$pttl = $fromRedis->pttl($key);
if ($pttl > 0) {
    $toRedis->pexpire($key, $pttl);
}
?>

4. 大鍵分塊遷移 (避免記憶體爆炸)

<?php
function migrateLargeHash($fromRedis, $toRedis, $key, $chunkSize = 1000) {
    $cursor = 0;
    do {
        // HSCAN 分批讀取
        $data = $fromRedis->hScan($key, $cursor, '*', $chunkSize);
        $cursor = $data[0];
        $fields = $data[1];
        
        if ($fields) {
            $toRedis->hMSet($key, $fields);
        }
    } while ($cursor !== '0');
}

// List 大鍵同理使用 LRANGE 分批
function migrateLargeList($fromRedis, $toRedis, $key, $chunkSize = 1000) {
    $len = $fromRedis->lLen($key);
    for ($offset = 0; $offset < $len; $offset += $chunkSize) {
        $chunk = $fromRedis->lRange($key, $offset, $offset + $chunkSize - 1);
        if ($chunk) {
            $toRedis->rPush($key, ...$chunk);
        }
    }
}
?>

生產環境部署清單

  • [ ] 來源/目標 Redis 版本相容性確認
  • [ ] 網路連通性與防火牆規則驗證
  • [ ] 目標資料庫空間充足評估
  • [ ] 遷移腳本乾跑 (Dry-run) 驗證邏輯
  • [ ] 制定回滾方案 (目標資料庫備份)
  • [ ] 低峰期執行並監控記憶體/CPU
  • [ ] 遷移後抽樣驗證資料一致性
  • [ ] 更新應用配置指向新 Redis

替代工具對比

根據資料量、停機容忍度、團隊技術棧選擇最適合的遷移方案。PHP 腳本適合中小規模、需自定義轉換邏輯的場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
工具優點缺點適用場景
本腳本可控性強、支援自定義邏輯、PHP 環境原生需自行維護、單執行緒較慢中小量遷移、特殊轉換需求
redis-cli --rdb官方工具、二進制級備份、速度快全量覆蓋、不支援增量、需檔案存取權完整備份/還原、叢集搬遷
redis-replication主從同步、增量同步、生產級架構複雜、需重啟/重新配置高可用架構、即時同步
RedisShake阿里開源、多模式、效能極佳Go 編譯、學習曲線大規模遷移、同步、備份