/
latin
/
redis-copy
Обзор
Документация
Войти
/
latin
/
redis-copy
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
Program.cs
403 строки
18 KB
Nikolay Latin
filter export import
03 дек 2025, 12:25
03 дек 2025, 12:25
302dd30
Код
Авторство
О чём код?
using StackExchange.Redis; using System.Diagnostics; using CommandLine; using RedisCopy.Services; namespace RedisCopy; class Program { // Опции для копирования Redis -> Redis public class CopyOptions { [Option('s', "source-host", Required = true, HelpText = "Хост исходной Redis базы")] public string SourceHost { get; set; } = string.Empty; [Option("source-port", Default = 6379, HelpText = "Порт исходной Redis базы")] public int SourcePort { get; set; } = 6379; [Option("source-password", HelpText = "Пароль исходной Redis базы")] public string? SourcePassword { get; set; } [Option("source-db", Default = 0, HelpText = "Номер базы данных источника")] public int SourceDatabase { get; set; } = 0; [Option('t', "target-host", Required = true, HelpText = "Хост целевой Redis базы")] public string TargetHost { get; set; } = string.Empty; [Option("target-port", Default = 6379, HelpText = "Порт целевой Redis базы")] public int TargetPort { get; set; } = 6379; [Option("target-password", HelpText = "Пароль целевой Redis базы")] public string? TargetPassword { get; set; } [Option("target-db", Default = 0, HelpText = "Номер базы данных цели")] public int TargetDatabase { get; set; } = 0; [Option("flush-target", Default = false, HelpText = "Очистить целевую базу перед копированием")] public bool FlushTarget { get; set; } } // Опции для экспорта Redis -> JSON public class ExportOptions { [Option('h', "host", Required = true, HelpText = "Хост Redis базы")] public string Host { get; set; } = string.Empty; [Option('p', "port", Default = 6379, HelpText = "Порт Redis базы")] public int Port { get; set; } = 6379; [Option("password", HelpText = "Пароль Redis базы")] public string? Password { get; set; } [Option('d', "database", Default = 0, HelpText = "Номер базы данных")] public int Database { get; set; } = 0; [Option('o', "output", Required = true, HelpText = "Путь к выходному JSON файлу")] public string OutputFile { get; set; } = string.Empty; [Option("key-pattern", HelpText = "Фильтр ключей по паттерну (например: 'user:*' или '*session*')")] public string? KeyPattern { get; set; } [Option("key-contains", HelpText = "Фильтр ключей, содержащих указанный текст")] public string? KeyContains { get; set; } } // Опции для импорта JSON -> Redis public class ImportOptions { [Option('i', "input", Required = true, HelpText = "Путь к входному JSON файлу")] public string InputFile { get; set; } = string.Empty; [Option('h', "host", Required = true, HelpText = "Хост Redis базы")] public string Host { get; set; } = string.Empty; [Option('p', "port", Default = 6379, HelpText = "Порт Redis базы")] public int Port { get; set; } = 6379; [Option("password", HelpText = "Пароль Redis базы")] public string? Password { get; set; } [Option('d', "database", Default = 0, HelpText = "Номер базы данных")] public int Database { get; set; } = 0; [Option("flush", Default = false, HelpText = "Очистить базу перед импортом")] public bool FlushTarget { get; set; } [Option("key-pattern", HelpText = "Фильтр ключей по паттерну (например: 'user:*' или '*session*')")] public string? KeyPattern { get; set; } [Option("key-contains", HelpText = "Фильтр ключей, содержащих указанный текст")] public string? KeyContains { get; set; } } static async Task<int> Main(string[] args) { if (args.Length == 0) { ShowUsage(); return 1; } var command = args[0].ToLower(); var commandArgs = args.Skip(1).ToArray(); return command switch { "copy" => await Parser.Default.ParseArguments<CopyOptions>(commandArgs) .MapResult( async options => await RunCopy(options), errors => Task.FromResult(1) ), "export" or "backup" => await Parser.Default.ParseArguments<ExportOptions>(commandArgs) .MapResult( async options => await RedisExporter.ExportToJson( options.Host, options.Port, options.Password, options.Database, options.OutputFile, options.KeyPattern, options.KeyContains), errors => Task.FromResult(1) ), "import" or "restore" => await Parser.Default.ParseArguments<ImportOptions>(commandArgs) .MapResult( async options => await RedisImporter.ImportFromJson( options.InputFile, options.Host, options.Port, options.Password, options.Database, options.FlushTarget, options.KeyPattern, options.KeyContains), errors => Task.FromResult(1) ), _ => await Parser.Default.ParseArguments<CopyOptions>(args) .MapResult( async options => await RunCopy(options), errors => { ShowUsage(); return Task.FromResult(1); } ) }; } static void ShowUsage() { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("═══════════════════════════════════════════════════════════"); Console.WriteLine(" Redis Copy Tool - Копирование и бэкап Redis баз данных"); Console.WriteLine("═══════════════════════════════════════════════════════════"); Console.ResetColor(); Console.WriteLine(); Console.WriteLine("Команды:"); Console.WriteLine(); Console.WriteLine(" 1. Копирование Redis -> Redis:"); Console.WriteLine(" copy --source-host <host> --target-host <host> [опции]"); Console.WriteLine(); Console.WriteLine(" 2. Экспорт Redis -> JSON:"); Console.WriteLine(" export --host <host> --output <file.json> [опции]"); Console.WriteLine(" backup --host <host> --output <file.json> [опции]"); Console.WriteLine(); Console.WriteLine(" 3. Импорт JSON -> Redis:"); Console.WriteLine(" import --input <file.json> --host <host> [опции]"); Console.WriteLine(" restore --input <file.json> --host <host> [опции]"); Console.WriteLine(); Console.WriteLine("Для подробной справки используйте: <команда> --help"); Console.WriteLine(); } static async Task<int> RunCopy(CopyOptions options) { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"Исходная база: {options.SourceHost}:{options.SourcePort} (DB {options.SourceDatabase})"); Console.WriteLine($"Целевая база: {options.TargetHost}:{options.TargetPort} (DB {options.TargetDatabase})"); Console.ResetColor(); Console.WriteLine(); ConnectionMultiplexer? sourceConnection = null; ConnectionMultiplexer? targetConnection = null; try { // Подключение к исходной базе var sourceConfig = new ConfigurationOptions { EndPoints = { { options.SourceHost, options.SourcePort } }, DefaultDatabase = options.SourceDatabase, AbortOnConnectFail = false, ConnectTimeout = 10000, SyncTimeout = 5000 }; if (!string.IsNullOrEmpty(options.SourcePassword)) { sourceConfig.Password = options.SourcePassword; } Console.WriteLine("Подключение к исходной базе..."); sourceConnection = await ConnectionMultiplexer.ConnectAsync(sourceConfig); var sourceDb = sourceConnection.GetDatabase(options.SourceDatabase); var sourceServer = sourceConnection.GetServer(options.SourceHost, options.SourcePort); // Подключение к целевой базе var targetConfig = new ConfigurationOptions { EndPoints = { { options.TargetHost, options.TargetPort } }, DefaultDatabase = options.TargetDatabase, AbortOnConnectFail = false, ConnectTimeout = 10000, SyncTimeout = 5000 }; if (!string.IsNullOrEmpty(options.TargetPassword)) { targetConfig.Password = options.TargetPassword; } Console.WriteLine("Подключение к целевой базе..."); targetConnection = await ConnectionMultiplexer.ConnectAsync(targetConfig); var targetDb = targetConnection.GetDatabase(options.TargetDatabase); var targetServer = targetConnection.GetServer(options.TargetHost, options.TargetPort); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Подключение установлено успешно!"); Console.ResetColor(); Console.WriteLine(); // Очистка целевой базы, если указано if (options.FlushTarget) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Очистка целевой базы..."); await targetServer.FlushDatabaseAsync(options.TargetDatabase); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Целевая база очищена"); Console.ResetColor(); Console.WriteLine(); } // Получение всех ключей Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Получение списка ключей из исходной базы..."); Console.ResetColor(); var keys = sourceServer.Keys(options.SourceDatabase, "*").ToArray(); var totalKeys = keys.Length; Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"Найдено ключей: {totalKeys}"); Console.ResetColor(); Console.WriteLine(); if (totalKeys == 0) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Исходная база пуста. Копирование не требуется."); Console.ResetColor(); return 0; } // Копирование данных var stopwatch = Stopwatch.StartNew(); var copied = 0; var errors = 0; var lastProgressUpdate = DateTime.Now; for (int i = 0; i < keys.Length; i++) { var key = keys[i]; var keyName = key.ToString(); try { // Определяем тип ключа var keyType = await sourceDb.KeyTypeAsync(keyName); var ttl = await sourceDb.KeyTimeToLiveAsync(keyName); switch (keyType) { case RedisType.String: var stringValue = await sourceDb.StringGetAsync(keyName); await targetDb.StringSetAsync(keyName, stringValue, ttl); break; case RedisType.Hash: var hashFields = await sourceDb.HashGetAllAsync(keyName); if (hashFields.Length > 0) { await targetDb.HashSetAsync(keyName, hashFields); if (ttl.HasValue) { await targetDb.KeyExpireAsync(keyName, ttl); } } break; case RedisType.List: var listLength = await sourceDb.ListLengthAsync(keyName); if (listLength > 0) { var listValues = await sourceDb.ListRangeAsync(keyName, 0, -1); if (listValues.Length > 0) { await targetDb.ListRightPushAsync(keyName, listValues); if (ttl.HasValue) { await targetDb.KeyExpireAsync(keyName, ttl); } } } break; case RedisType.Set: var setMembers = await sourceDb.SetMembersAsync(keyName); if (setMembers.Length > 0) { await targetDb.SetAddAsync(keyName, setMembers); if (ttl.HasValue) { await targetDb.KeyExpireAsync(keyName, ttl); } } break; case RedisType.SortedSet: var sortedSetMembers = await sourceDb.SortedSetRangeByRankWithScoresAsync(keyName, 0, -1); if (sortedSetMembers.Length > 0) { var entries = sortedSetMembers.Select(m => new SortedSetEntry(m.Element, m.Score)).ToArray(); await targetDb.SortedSetAddAsync(keyName, entries); if (ttl.HasValue) { await targetDb.KeyExpireAsync(keyName, ttl); } } break; default: Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"Неизвестный тип ключа: {keyType} для ключа {keyName}"); Console.ResetColor(); break; } copied++; // Обновление прогресса каждые 100 ключей или каждую секунду if (copied % 100 == 0 || (DateTime.Now - lastProgressUpdate).TotalSeconds >= 1) { var percent = Math.Round((double)copied / totalKeys * 100, 2); var elapsed = stopwatch.Elapsed.TotalSeconds; var rate = copied / elapsed; var remaining = (totalKeys - copied) / rate; Console.Write($"\rСкопировано: {copied}/{totalKeys} ({percent}%) | " + $"Скорость: {rate:F0} ключей/сек | " + $"Осталось: {remaining:F0} сек"); lastProgressUpdate = DateTime.Now; } } catch (Exception ex) { errors++; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"\nОшибка при копировании ключа {keyName}: {ex.Message}"); Console.ResetColor(); } } stopwatch.Stop(); // Итоговая статистика Console.WriteLine(); Console.WriteLine(); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("════════════════════════════════════════"); Console.WriteLine("Копирование завершено!"); Console.WriteLine("════════════════════════════════════════"); Console.ResetColor(); Console.WriteLine($"Скопировано ключей: {copied} из {totalKeys}"); Console.WriteLine($"Ошибок: {errors}"); Console.WriteLine($"Время выполнения: {stopwatch.Elapsed.TotalSeconds:F2} секунд"); Console.WriteLine($"Средняя скорость: {copied / stopwatch.Elapsed.TotalSeconds:F0} ключей/сек"); if (errors > 0) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"\nВнимание: При копировании произошло {errors} ошибок."); Console.ResetColor(); } return 0; } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"\nКритическая ошибка: {ex.Message}"); Console.WriteLine($"Stack trace: {ex.StackTrace}"); Console.ResetColor(); return 1; } finally { sourceConnection?.Close(); targetConnection?.Close(); sourceConnection?.Dispose(); targetConnection?.Dispose(); } } }