介紹一個簡單的工具,用於將Redis資料從一個redis端點複製到另一個redis端點,基於原始儲存庫轉換為.NET 8:https://github.com/LuBu0505/redis-copy-net8
Redis Copy .NET8
Redis Copy 控制檯工具允許將 Redis 資料從一個 Redis 服務端複製到另一個。
Note: 不支援redis叢集
軟體要求
執行 Redis Copy 工具需要以下軟體。它可能會在其他版本上執行.
- .NET 8
- VS Code / Visual Studio 2022
下載原始碼
clone https://github.com/LuBu0505/redis-copy-net8.git
使用方式
選項 1 -- 使用 AppSetting.json
將“< ... >”替換為真實的redis端點
{ "SourceRedisConnectionString": "<source redis name>:6380,password=<your password>,ssl=True,abortConnect=False", //Source Redis ConnectionString "DestRedisConnectionString": "<Destination redis name>:6380,password=<your password>,ssl=True,abortConnect=False" //Destination Redis ConnectionString }
選項 2 -- 使用命令引數
redis-copy-net8.exe Parameter Description: --se Required. SourceEndpoint *.redis.cache.windows.net --sa Required. Source password --sp (Default: 6380) Source port --sssl (Default: true) Connect Source over ssl --de Required. DestinationEndpoint *.redis.cache.windows.net --da Required. Destination Password --dp (Default: 6380) Destination port --dssl (Default: true) Destination Source over ssl --help Display this help screen. --version Display version information. eg: redis-copy-net8.exe --se <xxxxxx.redis.cache.chinacloudapi.cn> --sa <******************> --de <xxxxxx.redis.cache.chinacloudapi.cn> --da <******************>
Redis Copy 工具的工作流程
第 1 階段:準備Redis源和目標資訊
- 使用 StackExchange.Redis ConnectionMultiplexer 類,預設建立20個連線。
- 檢查源redis的Used Memory、Keyspace資訊
- 根據Keys數量拆分成更多子任務
var infoGroup = sourcecon.BasicRetryInfo((conn) => conn.GetServer(conn.GetEndPoints()[0]).Info()); foreach (var info in infoGroup) { if (info.Key.Equals("Memory")) { Console.WriteLine($"==\t# {info.Key}"); var lists = info.ToList().Where(i => i.Key.Equals("used_memory_human") || i.Key.Equals("maxmemory_human")).ToList(); foreach (var list in lists) Console.WriteLine($"==\t {list.ToString()}"); } if (info.Key.Equals("Keyspace")) { Console.WriteLine($"==\t# {info.Key}"); foreach (var list in info.ToList()) { long dbindex, dbkeys = 0; long.TryParse(Regex.Match(list.Key, @"\d+\.*\d*").Value, out dbindex); long.TryParse(list.Value.Split(new char[] { ',' })[0].Split(new char[] { '=' })[1], out dbkeys); dictdbIdxKeysNum[dbindex] = dbkeys; totalKeysSource += dbkeys; Console.WriteLine($"==\t {list.ToString()}"); } } }
第二階段:複製
- 迴圈執行復制Redis Keys的子任務,SCAN列出所有Keys。
- 建立更多子任務以使用 StackExchange.Redis bacth 操作進行 TTL,驗證Key是否過期,DUMP出Key的byte[]資訊
- 使用批次操作將Key恢復到目標Redis
- 如果遇到異常,則將Key資訊新增到失敗佇列中。
- 檢查移動的keys的進度,同時檢查失敗的佇列,如果不為空,將重新執行移動任務
var allkeys = sourcecon.BasicRetryInfo((conn) => conn.GetServer(conn.GetEndPoints()[0]).Keys(dbindex).Skip(skipKeys).Take(takeKeys)).ToArray(); var sourcedb = sourcecon.GetConection().GetDatabase(dbindex); var destdb = destcon.GetConection().GetDatabase(dbindex); foreach (var keys in SplitKeys(allkeys)) { var rbatch = sourcedb.CreateBatch(); var ttltask = new List<Task<TimeSpan?>>(); var dumptask = new List<Task<byte[]?>>(); foreach (var key in keys) { ttltask.Add(rbatch.KeyTimeToLiveAsync(key)); dumptask.Add(rbatch.KeyDumpAsync(key)); } rbatch.Execute(); var ttlResults = Task.WhenAll(ttltask).Result; var dumpkResults = Task.WhenAll(dumptask).Result; //Restore the key to destation DB. var destBatch = destdb.CreateBatch(); var i = 0; foreach (var key in keys) { destBatch.KeyRestoreAsync(key, dumpkResults[i], ttlResults[i]); i++; } destBatch.Execute(); //Random select one key to verify in Phase 3. if (keys.Count() > 0) { int index = RandomNumberGenerator.GetInt32(keys.Count()); verifiedKeys.Add((dbindex, keys.ElementAt<RedisKey>(index).ToString())); } lock (lockObject) { totalKeysCopied += keys.Count(); } }
第三階段:驗證
- 隨機選取某個key, 一個一個的檢查他們的值在兩個Redis伺服器之間是否相同
foreach (var key in verifiedKeys) { try { var sourdump = await sourcecon.BasicRetryInfo(async (sc) => sc.GetDatabase(key.Item1).KeyDumpAsync(key.Item2)); var destdump = await destcon.BasicRetryInfo(async (sc) => sc.GetDatabase(key.Item1).KeyDumpAsync(key.Item2)); if (!sourdump.Result.SequenceEqual(destdump.Result)) { Console.Write($"\n"); Console.WriteLine($"== {key} Verify Failed"); } else { Console.Write($"{key}, "); } } catch (Exception ex) { Console.BackgroundColor = ConsoleColor.Red; Console.WriteLine($"=={DateTime.Now.ToLocalTime()} Verify {key} failed ({ex.Message})"); Console.BackgroundColor = ConsoleColor.Black; } }
測試結果
Copied 369886 keys(812MB) from Redis1 to Redis2 in 233 seconds