如何将Redis数据迁移至DigitalOcean托管数据库

介绍

当寻求将数据从一个 Redis 实例迁移到另一个时,可以使用多种方法,例如 复制snapshotting.然而,当您将数据迁移到由云提供商管理的 Redis 实例时,迁移可能会变得更加复杂,因为 管理数据库经常限制您对数据库配置的控制量。

本教程概述了一种方法,您可以将数据迁移到由 DigitalOcean 管理的 Redis 实例。该方法涉及创建一个 Bash 脚本,该脚本使用 Redis 内部迁移命令安全地通过与 stunnel配置的 TLS 隧道传输数据。

前提条件

要完成本教程,您将需要:

  • 联合国 一个服务器运行Ubuntu 18.04. 该服务器应配置一个具有行政特权的用户,并设置有ufw的防火墙。 要建立这种环境,请遵循我们Ubuntu 18.04的初始服务器设置指南.
  • Redis版本 4.0.7 或更新安装在您的服务器上. 为了确定这一点,遵循我们关于[如何在Ubuntu 18.04上安装和保障Redis(https://andsky.com/tech/tutorials/how-to-install-and-secure-redis-on-ubuntu-18-04)的指南中的步骤1**。
  • 由 DigitalOcean 管理的一个 Redis 实例 。 有关第1款,见我们管理Redis产品文件
  • Stunnel,一个开源代理服务器,用于在机器之间创建TLS隧道,安装在您的服务器上并配置,以维持与您所管理的Redis数据库的安全连接. 这样做是必要的,因为DigitalOcean管理的数据库要求连接在TLS上安全地进行. 完成关于 如何与 Stunnel 和 redis- cli 的 TLS 上管理过的重现实例连接 的教学( https://andsky.com/tech/tutorials/how-to-connect-to-managed-redis-over-tls-with-stunnel-and-redis-cli) 来设置此功能 。 不过,请注意,你** 不需要在步骤1中安装 " redis-tools " 软件包,因为当你在之前的必修课程中安装Redis-cli时,你已经安装了 " redis-cli " 软件包。 (英语)

<$>[注] 注: 为了帮助保持事情清晰,本指南将指向您 Ubuntu 服务器上托管的 Redis 实例作为

将 Redis 数据迁移到受管理的数据库时需要考虑的事情

您可以使用几种方法将数据从一个 Redis 实例迁移到另一个。

例如,您可以使用复制来将目标 Redis 实例转化为源的精确副本. 要做到这一点,您将连接到目标 Redis 服务器并使用以下语法运行 replicaof 命令:

1[environment second]
2replicaof source_hostname_or_ip source_port

这将导致目标实例复制源中持有的所有数据,而不会破坏以前存储在其上的任何数据。

1[environment second]
2replicaof no one

迁移 Redis 数据的另一种方法是使用 Redis 的保存bgsave命令拍摄源实例中所保留的数据的截图。这两个命令都将截图导出到以.rdb结束的文件中,然后将其传输到目标服务器。

然而,这三个命令中的每个命令(replicaof,保存bgsave)在DigitalOcean Managed Databases中都被禁用,这些命令除其他禁用命令外,都需要高级权限或访问管理数据库服务器的底层文件系统,这使得它们对于管理数据库解决方案不实用。

由于DigitalOcean的管理数据库不允许复制和快速拍摄作为数据迁移的手段,本教程将使用Redis的 migrate命令将数据从源移动到目标。

步骤 1 — (可选) 用样本数据加载您的源 Redis 实例

此可选步骤包括将您的源 Redis 实例加载一些样本数据,以便您可以尝试将数据迁移到您的 Managed Redis 数据库. 如果您已经有想要迁移到目标实例的数据,您可以继续到 步骤 2

首先,运行以下命令来访问您的 Redis 服务器:

1redis-cli

如果您已配置您的 Redis 服务器以要求密码验证,请运行auth命令,然后运行您的 Redis 密码:

1auth password

然后运行以下命令. 这些命令将创建一些持有字符串的键,加上一个持有哈希的键,一个持有列表,一个持有集合:

 1mset string1 "Redis" string2 "is" string3 "fun!"
 2mset string4 "Redis" string5 "is" string6 "fast!"
 3mset string7 "Redis" string8 "is" string9 "feature-rich!"
 4mset string10 "Redis" string11 "has" string12 "fantastic documentation!"
 5mset string13 "Redis" string14 "is" string15 "free and open-source!"
 6mset string16 "Redis" string17 "has many" string18 "data types."
 7mset string19 "Redis" string20 "allows" string21 "strings."
 8hmset hash1 field1 "Redis" field2 "allows" field3 "hashes."
 9rpush list1 "Redis" "also" "allows" "lists."
10sadd set1 "It" "even" "allows" "sets."

此外,运行以下到期命令以提供一些这些密钥的时效,这将使它们 volatile,这意味着Redis将在指定时间内删除它们,即7500秒:

1expire string2 7500
2expire hash1 7500
3expire set1 7500

有了它,您可以将一些示例数据导出到您的目标 Redis 实例. 您可以暂时保持redis-cli提示打开,因为您将在下一步执行更多命令以备份此数据。

步骤2 - 备份您的数据

此前,本教程讨论了使用Redis的bgsave命令来拍摄一个Redis数据库,并将其迁移到另一个实例.虽然我们不会使用bgsave作为迁移Redis数据的手段,但我们将在这里使用它来备份数据,如果我们在迁移过程中遇到错误。

如果您尚未打开它,请通过打开 Redis 命令行界面开始:

1redis-cli

此外,如果您已配置您的 Redis 服务器以要求密码验证,请运行auth命令,然后是您的 Redis 密码:

1auth password

接下来,运行bgsave命令,创建当前数据集的截图,并将其导出到以.rdb结束的 dump 文件:

1bgsave

<$>[注] 注: 如前面所提到的(#things-to-consider-when-migrating-redis-data-to-a-managed-database)部分中所提到的,您可以用保存bgsave命令拍摄您的Redis数据库。我们在这里使用bgsave命令的理由是保存命令运行同步,这意味着它将阻止与数据库连接的任何其他客户端。

相反,它建议使用 bgsave命令,该命令运行 asynchronously. 这将导致Redis将数据库折叠成两个流程:家长流程将继续为客户提供服务,而孩子在退出之前将数据库保存。

请注意,如果客户端在运行bgsave操作时或完成后添加或修改数据,则这些更改不会在即时截图中捕捉到。

接下来,您可以通过运行exit命令来关闭连接到您的 Redis 实例:

1exit

您可以在您的 Redis 安装的工作目录中找到这个垃圾文件. 如果您不确定这是哪个目录,您可以通过使用您喜爱的文本编辑器打开您的 Redis 配置文件来检查。

1sudo nano /etc/redis/redis.conf

导航到从dbfilename开始的行. 默认情况下,它将看起来像这样的:

1[label /etc/redis/redis.conf]
2. . .
3# The filename where to dump the DB
4dbfilename dump.rdb
5. . .

本指令定义了 Redis 将快照导出的文件,下一行(在任何评论之后)将看起来如下:

1[label /etc/redis/redis.conf]
2. . .
3dir /var/lib/redis
4. . .

dir 指令定义了 Redis 的工作目录,其中存储了任何 Redis 快照. 默认情况下,此设置为/var/lib/redis,如示例所示。

假设您没有对该文件进行任何更改,您可以通过按CTRL+X来做到这一点。

然后列出您的 Redis 工作目录的内容,以确认它持有导出数据卸载文件:

1sudo ls /var/lib/redis

如果 dump 文件被正确导出,您将在这个命令的输出中看到它:

1[secondary_label Output]
2dump.rdb

一旦您确认已成功备份数据,您可以开始将其迁移到管理数据库的过程。

步骤 3 – 构建迁移脚本

请记住,本指南使用 Redis 的内部迁移命令将密钥从源数据库移动到目标,但与本教程的以前步骤不同,您将不会从redis-cli提示程序运行此命令。

<$>[注] 注: 如果您有客户端将数据写入您的源 Redis 实例,现在将是一个很好的时机来配置它们也将数据写入您的管理数据库。

另外,请注意,此迁移脚本不会取代目标数据库上的任何现有密钥,除非现有密钥中的一个具有与您正在迁移的密钥相同的名称。

打开名为redis-migrate.sh的新文件:

1nano redis-migrate.sh

在文件的顶部,添加一个 shebang。这是一个字符序列,让您的服务器知道该脚本应该用bash壳执行:

1[label redis-migrate.sh]
2#!/bin/bash

设置允许您设置或取消某些环境变量,这对本脚本很有用,因为我们将使用它来防止一些潜在的陷阱。

在 shebang 下方,添加以下设置命令:

1[label redis-migrate.sh]
2#!/bin/bash
3set -euo pipefail

这包括e选项,如果其中的任何命令以非零状态退出,则会立即导致脚本退出,以及u选项。setu旗将告诉脚本将任何未设置的变量视为强迫其退出的错误。

最后一个旗帜,‘o’允许您设置各种参数。在这里,设置‘pipefail’选项。在 *nix 系统中,管道(‘annoo’)用于将一个命令的输出输入到另一个命令中。

1echo “Carpe diem, quam minimum credula postero.” | grep diem

如果管道左侧的命令(在本示例中,响应命令)失败,导致的错误消息仍然会被导入管道右侧的命令(抓住命令),因为错误消息仍然是有效的输出。

此脚本将使用Redis的扫描命令重复数据库中的每个密钥,然而,扫描只能一次重复一个数据库,这意味着如果您有存储在多个数据库中的密钥,您必须能够指定要扫描的数据库,然后迁移。

因此,本脚本将要求用户在管理的 Redis 实例中传递代表源数据库和目标数据库的数字作为命令行参数。

 1[label redis-migrate.sh]
 2#!/bin/bash
 3set -euo pipefail
 4
 5if [ "$#" -lt 2 ]
 6then
 7  echo "Migrate Redis keys to a DigitalOcean Managed Database"
 8  echo "Usage: $0 [source database] [target database]"
 9  exit 1
10fi

此声明会检查向脚本传递的参数数是否小于2。如果是这样,则会打印一个提醒用户脚本的函数以及如何正确召唤函数的消息。

其次,定义以下变量:

  • sourcedb: 脚本将使用此变量来参考源实例上的逻辑 Redis 数据库. 将其设置为在调用(${1})时传给脚本的第一个参数
  • targetdb: 类似地,脚本将使用此变量来参考目标实例上的逻辑数据库。 将此变量设置为将其传给脚本的第二个参数(${2})
  • cursor: 我们将讨论脚本如何使用此变量。

声明这些变量的新行应该是这样的:

1[label redis-migrate.sh]
2. . .
3  exit 1
4fi
5
6sourcedb=${1}
7targetdb=${2}
8cursor=-1

管理 Redis 实例通常需要用户提交密码来验证。而不是硬编码密码到此脚本中,添加以下突出的行来设置一对提示,这将要求用户输入本地和管理 Redis 实例的密码。

这些新行中的第一条和第三条使用Bash的内置。将从标准输入中读取一个单行,并将该值分配给作为参数传递给它的变量名称。这两个行包括-s选项,这防止在终端中响应输入,这对于敏感信息如密码很重要。

第一行会提示您输入本地 Redis 实例的密码,第三行会提示您输入您管理的 Redis 实例的密码. 它们之间的行会打印一个空行,导致第二个提示在新行上出现。

1[label redis-migrate.sh]
2. . .
3sourcedb=${1}
4targetdb=${2}
5cursor=-1
6
7read -s -p "Enter your local Redis password: " localpw
8echo ""
9read -s -p "Enter your managed Redis password: " managedpw

接下来,添加下面的while循环,这会检查以前定义的cursor变量是否不等于0

 1[label redis-migrate.sh]
 2. . .
 3cursor=-1
 4
 5read -s -p "Enter your local Redis password: " localpw
 6echo ""
 7read -s -p "Enter your managed Redis password: " managedpw
 8
 9while [[ "$cursor" -ne 0 ]]; do
10
11done

因为 cursor被初始化为-1,这意味着这个while循环将始终至少运行一次。

循环中,添加以下突出的如果/然后语句,此语句检查指标变量是否等于-1,如果是,则将其设置为0:

 1[label redis-migrate.sh]
 2. . .
 3while [[ "$cursor" -ne 0 ]]; do
 4
 5  if [[ "$cursor" -eq -1 ]]
 6  then
 7    cursor=0
 8  fi
 9
10done

Redis 的扫描命令允许几个选项,但只需要一个参数:一个标记值. 如果你想象一个 Redis 数据库作为随机分类的长列表的键,一个标记值为0告诉扫描从列表中的第一个键开始迭代。

要重复数据库中的每一个键,您必须继续调用扫描,每次用上次呼叫的输出中更新的标记符代替标记符,直到它返回一个标记符为0

这就是为什么我们将标记器初始化为-1只以立即将其重置为0这个添加:为了执行一个完整的迭代,这个脚本将需要多次调用扫描命令,使用0作为初始标记器,然后,在每一个后续的调用,标记器返回由前一次迭代。

请注意,扫描不会返回负标记值,因此将标记初始化为-1不会导致任何问题。

如果/然后陈述之后,但仍在完成之前,添加一个定义新的本地变量响应的行,并将其值设置为使用redis-cli客户端执行的扫描命令的输出。

这个redis-cli命令包括-a选项,然后是localpw变量。假设用户在提示时输入了本地 Redis 实例的正确密码,则-a旗将使用此处的密码来验证。

 1[label redis-migrate.sh]
 2. . .
 3while [[ "$cursor" -ne 0 ]]; do
 4
 5  if [[ "$cursor" -eq -1 ]]
 6  then
 7    cursor=0
 8  fi
 9
10  reply=$(redis-cli -a "$localpw" -n "$sourcedb" SCAN "$cursor")
11
12done

接下来,添加另一个如果/然后语句,测试响应变量是否为 null 值,如果没有,则执行然后fi之间的所有语句:

 1[label redis-migrate.sh]
 2. . .
 3while [[ "$cursor" -ne 0 ]]; do
 4
 5  if [[ "$cursor" -eq -1 ]]
 6  then
 7    cursor=0
 8  fi
 9
10  reply=$(redis-cli -a "$localpw" -n "$sourcedb" SCAN "$cursor")
11
12  if [ -n "$reply" ]; then
13
14  fi
15
16done

在此如果/然后声明中,添加下列行:第一个显示了回复变量中的内容,然后将其作为输入到尾巴命令中。

您可以将响应的结果直接传递到下一个同时循环中,但这也将引导第一个行,如前所述,包含更新的路由器值,这将导致Redis尝试迁移一个不存在的密钥,这可能会导致错误,或者至少对您的服务器带来不必要的额外工作。

为了绕过这一点,我们将回复内容导入尾巴命令中,该命令包含-n +2参数,这告诉尾巴从第二行开始阅读,然后将每个行导入同时循环。

每次读一行,它将该行的内容分配给一个新的变量,关键

请注意包括IFS=。这简称为 Internal Field S分离器,这是定义用于分离模式的字符或字符集的变量。

 1[label redis-migrate.sh]
 2. . .
 3while [[ "$cursor" -ne 0 ]]; do
 4. . .
 5  if [ -n "$reply" ]; then
 6          echo "$reply" | tail -n +2 |
 7          while IFS= read -r key; do
 8
 9          done
10  fi
11
12done

在这个同时循环中,添加突出的行,这是执行实际迁移的命令:

 1[label redis-migrate.sh]
 2. . .
 3while [[ "$cursor" -ne 0 ]]; do
 4. . .
 5  if [ -n "$keys" ]; then
 6          echo "$keys" |
 7          while IFS= read -r key; do
 8                  redis-cli -a "$localpw" -n "$sourcedb" migrate localhost 8000 "$key" "$targetdb" 1000 copy auth "$managedpw" >/dev/null 2>&1
 9          done
10  fi
11
12done

此命令调用了redis-cli客户端程序,并在连接用户输入的逻辑数据库(由sourcedb变量表示)之前通过localpw变量对本地 Redis 实例进行身份验证。然后,它调用了 Redis 的迁移命令,要求您传输目标 Redis 实例服务器的 IP 地址或主机名,以及运行该实例的端口。

接下来是代表时间的数字. 这个时间是两台机器之间空闲通信的时间的最大量. 请注意,这不是操作的时间限制; 它只是意味着操作应该在定义时间的范围内总是取得一定程度的进展。

默认情况下,迁移将从源数据库中删除每个密钥,然后将它们传输到目标;通过启用此选项,您指示迁移命令仅仅复制密钥,以便它们在源头上继续存在。

复制之后,出现了Auth标志,然后是您管理的 Redis 实例的密码. 如果您将数据迁移到不需要身份验证的实例,则不需要此操作,但当您将数据迁移到由 DigitalOcean 管理的实例时,则需要此操作。

最后,这个行包括 /dev/null2>&1. /dev/null 重定向了命令的标准输出到 /dev/null 文件,一个 null 设备,即时抛弃任何写给它的数据。 2>&1 重定向了命令的标准错误到标准输出,这意味着,由于在它之前的 /dev/null,任何潜在的错误也立即被抛弃。

最后,在如果/然后语句之后,但在外部同时循环的完成语句之前,添加下面的突出行。 此行更新了指针变量所持有的值到响应变量中所持有的指针值。 它通过使用expr实用程序评估响应并搜索匹配正常表达式的第一个值(\([0-9]*[0-9]\))。 由于扫描命令的输出是如何格式化的,这个正常表达式将始终匹配正确的指针值:

 1[label redis-migrate.sh]
 2. . .
 3while [[ "$cursor" -ne 0 ]]; do
 4. . .
 5  if [ -n "$keys" ]; then
 6          echo "$keys" |
 7          while IFS= read -r key; do
 8                  redis-cli -a "$localpw" -n "$sourcedb" migrate localhost 8000 "$key" "$targetdb" 1000 copy auth "$managedpw" >/dev/null 2>&1
 9          done
10  fi
11
12  cursor=$(expr "$reply" : '\([0-9]*[0-9]\)')
13
14done

总的来说,剧本应该是这样的:

 1[label redis-migrate.sh]
 2#!/bin/bash
 3
 4set -euo pipefail
 5
 6if [ "$#" -lt 2 ]
 7then
 8  echo "Migrate Redis keys to a DigitalOcean Managed Database"
 9  echo "Usage: $0 [source database] [target database]"
10  exit 1
11fi
12
13sourcedb=${1}
14targetdb=${2}
15cursor=-1
16
17read -s -p "Enter local Redis password: " localpw
18echo ""
19read -s -p "Enter managed Redis password: " managedpw
20
21while [[ "$cursor" -ne 0 ]]; do
22
23  if [[ "$cursor" -eq -1 ]]
24  then
25    cursor=0
26  fi
27
28  reply=$(redis-cli -a "$localpw" -n "$sourcedb" SCAN "$cursor")
29
30  if [ -n "$reply" ]; then
31          echo "$reply" | tail -n +2 |
32          while IFS= read -r key; do
33                  redis-cli -a "$localpw" -n "$sourcedb" migrate localhost 8000 "$key" "$targetdb" 1000 copy auth "$managedpw" >/dev/null 2>&1
34          done
35  fi
36
37  cursor=$(expr "$reply" : '\([0-9]*[0-9]\)')
38
39done

如果您使用nano创建脚本,请按CTRL + X,Y,然后按ENTER

要将脚本的创建包裹起来,用chmod标记为可执行:

1sudo chmod +x redis-migrate.sh

有了它,您可以使用脚本将您的 Redis 数据迁移到受管理的 Redis 实例。

步骤 4 – 迁移您的 Redis 数据

若要使用您在上一步创建的脚本迁移您的 Redis 数据,您可以这样调用它:

1./redis-migrate.sh source_database target_database

假设您遵循了 本教程的可选第一步并加载了本地 Redis 实例的默认数据库 (0) 数据,并希望将这些数据迁移到您管理实例的 0 数据库,您将使用以下命令:

1./redis-migrate.sh 0 0

您将收到本地 Redis 实例的身份验证密码的第一个提示:

1[secondary_label Output]
2Enter local Redis password:

输入您的本地 Redis 密码,然后按ENTER。 如果您尚未配置本地 Redis 实例以要求密码,只需按ENTER来空置localpw密码变量。

然后,您将被要求输入您的 Managed Redis 数据库的密码:

1[secondary_label Output]
2Enter local Redis password:
3Enter managed Redis password:

<$>[info] 注: 如果您没有您的 Managed Redis 数据库密码,您可以通过先导航到 DigitalOcean 控制面板来找到它。从那里,在左侧侧栏菜单中点击 Databases,然后点击您想要迁移数据的 Redis 实例名称。向下滚动到 连接详细信息部分,在那里您将找到标记为 密码的字段。

如果您输入了正确的数据库号码和有效的密码,则脚本将迁移数据库中的每个密钥,并在没有进一步输出的情况下关闭。

1redis-cli -h localhost -p 8000 -a managed_redis_password

如果您将数据迁移到默认数据库以外的任何逻辑数据库,请使用选择命令连接到该数据库:

1select target_database

运行一个扫描命令,查看一些现在保存在的密钥:

1scan 0

如果您完成了本教程的 [步骤 1](#步骤-1-%E2%80%94(可选) - 加载 - 您的源代码 - 重新实例 - 与示例 - 数据),并将示例数据添加到您的源数据库中,您将看到这样的输出:

 1[secondary_label Output]
 21) "10"
 32)  1) "set1"
 4    2) "string6"
 5    3) "string11"
 6    4) "string3"
 7    5) "string5"
 8    6) "string10"
 9    7) "string14"
10    8) "string18"
11    9) "string2"
12   10) "string4"

最后,在您已设置到过期的任何密钥上运行ttl命令,以确认它仍然是波动的:

1ttl string2
1[secondary_label Output]
2(integer) 3944

此输出显示,即使您将密钥迁移到您的管理数据库,它仍然会根据您之前运行的 expireat命令过期。

一旦您确认您的源代码数据库中的所有密钥都成功导出到您的目标,您可以关闭连接到管理数据库. 如果您的本地代码实例中的任何其他逻辑数据库都包含任何数据,则需要对每个密码再次运行脚本,确保将适当的源和目标数据库作为参数。

结论

完成本教程后,您将从自管理的 Redis 数据库迁移到由 DigitalOcean 管理的 Redis 实例. 用于此过程的 Bash 脚本可能不适合每个 Redis 使用案例,但它对本教程中描述的使用案例都很好,也可以为其他使用案例优化。

现在你正在使用DigitalOcean Managed Redis数据库来存储你的数据,你可以通过运行一些基准测试(https://andsky.com/tech/tutorials/how-to-perform-redis-benchmark-tests)来衡量其性能。

Published At
Categories with 技术
comments powered by Disqus