【Azure Developer】一個複製Redis Key到另一個Redis服務的工具(redis_copy_net8)

路边两盏灯發表於2024-07-11

介紹一個簡單的工具,用於將Redis資料從一個redis端點複製到另一個redis端點,基於原始儲存庫轉換為.NET 8:https://github.com/LuBu0505/redis-copy-net8

【Azure Developer】一個複製Redis Key到另一個Redis服務的工具(redis_copy_net8)

Redis Copy .NET8

Redis Copy 控制檯工具允許將 Redis 資料從一個 Redis 服務端複製到另一個。

【Azure Developer】一個複製Redis Key到另一個Redis服務的工具(redis_copy_net8)

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
}

【Azure Developer】一個複製Redis Key到另一個Redis服務的工具(redis_copy_net8)

選項 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 <******************> 

【Azure Developer】一個複製Redis Key到另一個Redis服務的工具(redis_copy_net8)

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()}");
                    }
                }
            }

【Azure Developer】一個複製Redis Key到另一個Redis服務的工具(redis_copy_net8)

第二階段:複製

  • 迴圈執行復制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

相關文章