介绍
当希望将数据从一个 Redis 实例迁移到另一个实例时,可以采用多种方法,例如 replication 或 snapshotting。 但是,当您将数据移动到由云提供商管理的 Redis 实例时,迁移可能会变得更加复杂,因为 托管数据库 通常会限制您对数据库配置的控制程度。
本教程概述了一种可用于将数据迁移到由 DigitalOcean 管理的 Redis 实例的方法。 该方法涉及创建一个 Bash 脚本,该脚本使用 Redis 的内部 migrate
命令通过配置有 stunnel 的 TLS 隧道安全地传递数据。 本指南还将介绍其他一些常用的迁移策略,以及为什么在迁移到 DigitalOcean 托管数据库 时它们会出现问题。
先决条件
要完成本教程,您需要:
- 一台运行 Ubuntu 18.04 的服务器。 该服务器应该有一个配置有管理权限的用户和一个使用
ufw
设置的防火墙。 要设置此环境,请按照我们的 Ubuntu 18.04 初始服务器设置指南进行操作。 - Redis 版本 4.0.7 或更新版本安装在您的服务器上。 要进行此设置,请按照我们关于 如何在 Ubuntu 18.04 上安装和保护 Redis 的指南中的 步骤 1。
- 由 DigitalOcean 管理的 Redis 实例。 要配置一个,请参阅我们的 Managed Redis 产品文档。
- Stunnel 是一个开源代理,用于在机器之间创建 TLS 隧道,安装在您的服务器上并配置为与您的托管 Redis 数据库保持安全连接。 这是必要的,因为 DigitalOcean 托管数据库需要通过 TLS 安全地建立连接。 完成我们关于 如何使用 Stunnel 和 redis-cli 通过 TLS 连接到托管 Redis 实例的教程以进行设置。 但是请注意,您 不需要 在步骤 1 中安装
redis-tools
包,因为您在之前的先决条件中安装 Redis 时已经安装了redis-cli
教程。
注意: 为帮助保持清晰,本指南将托管在您的 Ubuntu 服务器上的 Redis 实例称为“源”。 同样,它将 DigitalOcean 管理的实例称为“目标”或“托管数据库”。
将 Redis 数据迁移到托管数据库时要考虑的事项
您可以使用多种方法将数据从一个 Redis 实例迁移到另一个实例。 但是,当您将数据迁移到由 DigitalOcean 管理的 Redis 实例时,其中一些方法会出现问题。
例如,您可以使用复制将目标 Redis 实例转换为源的精确副本。 为此,您将连接到目标 Redis 服务器并使用以下语法运行 replicaof
命令:
replicaof source_hostname_or_ip source_port
这将导致目标实例复制源上保存的所有数据,而不会破坏之前存储在其上的任何数据。 在此之后,您将使用以下命令将副本提升回主实例:
replicaof no one
迁移 Redis 数据的另一种方法是使用 Redis 的 save
或 bgsave
命令对源实例上保存的数据进行快照。 这两个命令都将快照导出到以 .rdb
结尾的文件,然后您将其传输到目标服务器。 之后,您将重新启动 Redis 服务,以便它可以加载数据。
但是,这三个命令中的每一个 - replicaof
、save
和 bgsave
- 在 DigitalOcean 托管数据库上都被禁用。 这些以及其他禁用的命令需要高级权限或访问托管数据库服务器的底层文件系统,这使得它们对于托管数据库解决方案不切实际。 因此,与其他托管数据库提供商一样,DigitalOcean 限制了对这些命令的访问,从而使相关的迁移方法变得不可能。
由于 DigitalOcean 的托管数据库不允许复制和快照作为迁移数据的手段,本教程将改为使用 Redis 的 migrate 命令将数据从源移动到目标。 migrate
命令设计为一次只能移动一个键,但本教程将使用 Bash 脚本自动迁移整个 Redis 数据库。
第 1 步 - (可选)使用示例数据加载源 Redis 实例
此可选步骤涉及使用一些示例数据加载源 Redis 实例,以便您可以尝试将数据迁移到托管 Redis 数据库。 如果您已经有要迁移到目标实例的数据,则可以继续进行 步骤 2。
首先,运行以下命令来访问您的 Redis 服务器:
redis-cli
如果您已将 Redis 服务器配置为需要密码验证,请运行 auth
命令,然后输入您的 Redis 密码:
auth password
然后运行以下命令。 这些将创建许多保存字符串的键,一个保存哈希的键,一个保存列表,一个保存集合:
mset string1 "Redis" string2 "is" string3 "fun!" mset string4 "Redis" string5 "is" string6 "fast!" mset string7 "Redis" string8 "is" string9 "feature-rich!" mset string10 "Redis" string11 "has" string12 "fantastic documentation!" mset string13 "Redis" string14 "is" string15 "free and open-source!" mset string16 "Redis" string17 "has many" string18 "data types." mset string19 "Redis" string20 "allows" string21 "strings." hmset hash1 field1 "Redis" field2 "allows" field3 "hashes." rpush list1 "Redis" "also" "allows" "lists." sadd set1 "It" "even" "allows" "sets."
此外,运行以下 expire
命令为其中一些键提供超时。 这将使它们变为 volatile,这意味着 Redis 将在指定的时间后删除它们,7500
秒:
expire string2 7500 expire hash1 7500 expire set1 7500
这样,您就有了一些可以导出到目标 Redis 实例的示例数据。 您可以暂时保持 redis-cli
提示打开,因为您将在下一步中运行更多命令以备份此数据。
第 2 步 — 备份您的数据
之前,本教程讨论了使用 Redis 的 bgsave
命令拍摄 Redis 数据库的快照并将其迁移到另一个实例。 虽然我们不会使用 bgsave
作为 Redis 数据迁移的手段,但我们将在这里使用它来备份数据,以防在迁移过程中遇到错误。
如果你还没有打开它,首先打开 Redis 命令行界面:
redis-cli
此外,如果您已将 Redis 服务器配置为需要密码验证,请运行 auth
命令,后跟您的 Redis 密码:
auth password
接下来,运行 bgsave
命令。 这将创建当前数据集的快照并将其导出到名称以 .rdb
结尾的转储文件:
bgsave
注意: 正如前面的 Things To Consider 部分所述,您可以使用 save
或 bgsave
命令对 Redis 数据库进行快照. 我们在这里使用 bgsave
命令的原因是 save
命令同步运行 ',这意味着它将阻止任何其他连接到数据库的客户端。 正因为如此,save 命令文档 建议该命令几乎不应该在生产环境中运行。
相反,它建议使用 bgsave 命令,该命令异步运行 ' 。 这将导致 Redis 将数据库分叉为两个进程:父进程将继续为客户端提供服务,而子进程将在退出之前保存数据库。
请注意,如果客户端在 bgsave
操作正在运行或完成后添加或修改数据,这些更改将不会在快照中捕获。
之后,您可以通过运行 exit
命令关闭与 Redis 实例的连接:
exit
您可以在 Redis 安装的工作目录中找到此转储文件。 如果您不确定这是哪个目录,可以使用首选文本编辑器打开 Redis 配置文件来检查。 在这里,我们将使用 nano
:
sudo nano /etc/redis/redis.conf
导航到以 dbfilename
开头的行。 默认情况下它看起来像这样:
/etc/redis/redis.conf
. . . # The filename where to dump the DB dbfilename dump.rdb . . .
该指令定义 Redis 将导出快照的文件。 下一行(在任何注释之后)将如下所示:
/etc/redis/redis.conf
. . . dir /var/lib/redis . . .
dir
指令定义了存储任何 Redis 快照的 Redis 工作目录。 默认情况下,它设置为 /var/lib/redis
,如示例所示。
关闭 redis.conf
文件。 假设您没有对文件进行任何更改,您可以按 CTRL+X
进行更改。
然后,列出 Redis 工作目录的内容,以确认它包含导出的数据转储文件:
sudo ls /var/lib/redis
如果转储文件已正确导出,您将在此命令的输出中看到它:
Outputdump.rdb
确认已成功备份数据后,即可开始将其迁移到托管数据库的过程。
第 3 步 — 构建迁移脚本
回想一下,本指南使用 Redis 的内部 migrate
命令将键从源数据库一一移动到目标。 但是,与本教程前面的步骤不同,您不会在 redis-cli
提示符下运行此命令。 相反,您将编写一个 Bash 脚本,在调用该脚本时,您可以使用单个命令将所有密钥从源 Redis 实例迁移到托管实例。
注意: 如果您有客户端将数据写入源 Redis 实例,现在是配置它们以也将数据写入托管数据库的好时机。 这样,您可以将现有数据从源迁移到目标,而不会丢失迁移后发生的任何写入。
此外,请注意,此迁移脚本不会替换目标数据库上的任何现有密钥,除非现有密钥之一与您正在迁移的密钥具有相同的名称。
打开一个名为 redis-migrate.sh
的新文件:
nano redis-migrate.sh
在文件的顶部,添加一个 shebang。 这是一个字符序列,让您的服务器知道脚本应该使用 bash
shell 执行:
redis-migrate.sh
#!/bin/bash
set
允许您设置或取消设置某些环境变量。 这对这个脚本很有用,因为我们将使用它来防止一些潜在的陷阱。
在 shebang 下方,添加以下 set
命令:
redis-migrate.sh
#!/bin/bash set -euo pipefail
这包括 e
选项,如果其中的任何命令以非零状态退出,它将导致脚本立即退出,以及 u
选项。 set
的 u
标志将告诉脚本将任何未设置的变量视为强制其退出的错误。 这对我们的目的很有用,因为这个脚本需要用户输入。
最后一个标志 o
允许您设置各种参数。 在这里,设置 pipefail
选项。 在 *nix 系统中,管道 (|
) 用于将一个命令的输出作为输入传递给另一个命令。 例如:
echo “Carpe diem, quam minimum credula postero.” | grep diem
如果管道左侧的命令(在本例中为 echo
命令)失败,它导致的错误消息仍将通过管道传递到管道右侧的命令(grep
命令),因为错误消息仍然是有效的输出。 如果管道链中的任何命令导致错误,pipefail
选项会更改此行为并导致脚本退出。
该脚本将使用 Redis 的 scan
命令迭代数据库中的每个键。 但是,scan
一次只能遍历一个数据库,这意味着如果您将密钥存储在多个数据库中,您必须能够指定要扫描然后迁移的数据库。 同样,此脚本将使用的 migrate
命令要求您在要将数据迁移到的目标实例上指定逻辑数据库。
因此,此脚本将要求用户将代表托管 Redis 实例上的源数据库和目标数据库的数字作为命令行参数传递。 为此,添加以下 if/then
语句:
redis-migrate.sh
#!/bin/bash set -euo pipefail if [ "$#" -lt 2 ] then echo "Migrate Redis keys to a DigitalOcean Managed Database" echo "Usage: $0 [source database] [target database]" exit 1 fi
此语句检查传递给脚本的参数数量是否小于 2。 如果是这样,它会打印一条消息,提醒用户脚本的功能以及如何正确调用它。 然后它立即退出,if/then
语句以 fi
结束。
接下来,定义以下变量:
sourcedb
:脚本将使用此变量来引用源实例上的逻辑 Redis 数据库。 将其设置为调用时传递给脚本的第一个参数 (${1}
)targetdb
:同样,脚本将使用此变量来引用目标实例上的逻辑数据库。 将此变量设置为传递给脚本的第二个参数 (${2}
)cursor
:我们将很快讨论脚本如何使用这个变量。 现在,只需将其设置为-1
。
声明这些变量的新行应如下所示:
redis-migrate.sh
. . . exit 1 fi sourcedb=${1} targetdb=${2} cursor=-1
托管 Redis 实例通常要求用户提交密码进行身份验证。 与其将密码硬编码到此脚本中,不如添加以下突出显示的行来设置几个提示,这些提示将要求用户输入其本地和托管 Redis 实例的密码。
这些新行的第一行和第三行使用 Bash 的 read
内置函数。 read
将从标准输入中读取一行并将该值分配给作为参数传递给它的变量名。 这两行都包括 -s
选项,它可以防止 read
在终端中回显输入,这对于密码等敏感信息很重要。 两者还包括 -p
选项,它允许您在尝试读取任何输入之前立即输出字符串作为提示。
第一行将提示您输入本地 Redis 实例的密码,第三行将提示您输入托管 Redis 实例的密码。 它们之间的行将打印一个空行,导致第二个提示出现在新行上。 这将有助于使两个提示在终端中更具可读性:
redis-migrate.sh
. . . sourcedb=${1} targetdb=${2} cursor=-1 read -s -p "Enter your local Redis password: " localpw echo "" read -s -p "Enter your managed Redis password: " managedpw
接下来,添加以下 while
循环。 这将检查先前定义的 cursor
变量是否不等于 0
。 如果是这样,它将执行循环中的每个命令,直到到达 done
:
redis-migrate.sh
. . . cursor=-1 read -s -p "Enter your local Redis password: " localpw echo "" read -s -p "Enter your managed Redis password: " managedpw while [[ "$cursor" -ne 0 ]]; do done
因为 cursor
被初始化为 -1
,这意味着这个 while
循环将始终至少运行一次。
在 while
循环中,添加以下突出显示的 if/then
语句。 这一项检查 cursor
变量是否等于 -1
,如果是,则将其设置为等于 0
:
redis-migrate.sh
. . . while [[ "$cursor" -ne 0 ]]; do if [[ "$cursor" -eq -1 ]] then cursor=0 fi done
Redis 的 scan
命令允许几个选项,但只需要一个参数:光标值。 如果您将 Redis 数据库想象为一长串随机排列的键,则 0
的光标值告诉 scan
从列表中的第一个键开始迭代。 每次 scan
运行时,它都会返回一个新光标作为其输出的第一行,随后每一行上的单个键数量有限,通常在 10 到 20 之间。
要遍历数据库中的每个键,您必须继续调用 scan
,每次将光标替换为上一次调用输出中更新的光标,直到它返回 0
的光标。 这表明 scan
已经完成了一次完整的迭代。
这就是为什么我们将 cursor
初始化为 -1
只是为了立即将其重置为 0
并添加以下内容:为了执行完整的迭代,此脚本将需要调用 [ X188X] 命令多次,使用 0
作为初始光标,然后在每次后续调用中,使用上一次迭代返回的光标。 仅当最后一个 scan
调用返回 0
光标时,循环才应停止。
请注意,scan
不会返回负光标值,因此将 cursor
初始化为 -1
不会导致任何问题。
在 if/then
语句之后,但仍在 done
之前,添加一行定义一个新的局部变量 reply
,并将其值设置为 [ X162X] 命令使用 redis-cli
客户端执行。
此 redis-cli
命令包括 -a
选项,后跟 localpw
变量。 假设用户在提示时输入了正确的本地 Redis 实例密码,-a
标志将在此处使用该密码进行身份验证。 它还包括 -n
标志。 这告诉 redis-cli
要连接到 Redis 的哪个逻辑数据库,由 sourcedb
变量定义:
redis-migrate.sh
. . . while [[ "$cursor" -ne 0 ]]; do if [[ "$cursor" -eq -1 ]] then cursor=0 fi reply=$(redis-cli -a "$localpw" -n "$sourcedb" SCAN "$cursor") done
接下来,添加另一个 if/then
语句。 这个测试 reply
变量是否为空值,如果不是,则执行 then
和 fi
之间的所有语句:
redis-migrate.sh
. . . while [[ "$cursor" -ne 0 ]]; do if [[ "$cursor" -eq -1 ]] then cursor=0 fi reply=$(redis-cli -a "$localpw" -n "$sourcedb" SCAN "$cursor") if [ -n "$reply" ]; then fi done
在此 if/then
语句中,添加以下行。 第一个显示保存在 reply
变量中的内容,然后将它们作为输入传递给 tail
命令。
您可以将 echo "$reply"
的结果直接传递到下面的 while
循环中,但这也将通过管道传递第一行,如前所述,它保存更新的光标值。 这将导致 Redis 尝试迁移一个不存在的密钥,这可能会导致错误,或者至少对您的服务器造成不必要的额外工作。
为了解决这个问题,我们将 reply
内容通过管道传输到包含 -n +2
参数的 tail
命令中。 这告诉 tail
从第二行开始读取,然后再将每一行输入 while
循环。
这个 while
循环会一一读取 reply
的每一行。 每次读取一行时,它都会将该行的内容分配给一个新变量 key
。 循环将执行 do
和 done
语句之间的命令,直到读完每一行。
注意包含 IFS=
。 这是 Internal Field Separator 的缩写,它是一个变量,用于定义用于分隔模式的字符或字符集。 通过将其留空,可以确保 read
进程将在每一行的末尾拆分 reply
:
redis-migrate.sh
. . . while [[ "$cursor" -ne 0 ]]; do . . . if [ -n "$reply" ]; then echo "$reply" | tail -n +2 | while IFS= read -r key; do done fi done
在这个 while
循环中,添加突出显示的行。 这是执行实际迁移的命令:
redis-migrate.sh
. . . while [[ "$cursor" -ne 0 ]]; do . . . if [ -n "$keys" ]; then echo "$keys" | while IFS= read -r key; do redis-cli -a "$localpw" -n "$sourcedb" migrate localhost 8000 "$key" "$targetdb" 1000 copy auth "$managedpw" >/dev/null 2>&1 done fi done
此命令调用 redis-cli
客户端程序,并在连接用户输入的逻辑数据库(由 sourcedb
变量表示)之前使用 localpw
变量对本地 Redis 实例进行身份验证。 然后它会调用 Redis 的 migrate
命令,该命令要求您传递目标 Redis 实例服务器的 IP 地址或主机名,以及运行它的端口。 然后,它传递要迁移的键的名称(由 key
变量表示)和该键应迁移到的目标 Redis 实例上的数据库(由 targetdb
表示)。
接下来是一个代表超时的数字。 此超时是两台机器之间的最大空闲通信时间。 请注意,这不是操作的时间限制; 它只是意味着操作应该始终在定义的超时时间内取得一定程度的进展。 每个 migrate
命令都需要 ' 目标数据库编号和超时参数。
超时之后是可选的 copy
标志。 默认情况下,migrate
将在将源数据库中的每个键传输到目标后将其从源数据库中删除; 通过包含此选项,您将指示 migrate
命令仅复制密钥,以便它们将保留在源中。
在 copy
之后是 auth
标志,后跟托管 Redis 实例的密码。 如果您将数据迁移到不需要身份验证的实例,则这不是必需的,但当您将数据迁移到由 DigitalOcean 管理的实例时,这是必需的。
最后,这一行包括 /dev/null
和 2>&1
。 /dev/null
将命令的标准输出重定向到 /dev/null
文件,这是一个空设备,它会立即丢弃任何写入它的数据。 2>&1
将命令的标准错误重定向到标准输出,这意味着,由于前面的 /dev/null
,任何潜在的错误也会立即被丢弃。
最后,在 if/then
语句之后、外部 while
循环的 done
语句之前添加以下突出显示的行。 此行将 cursor
变量保存的值更新为 reply
变量保存的光标值。 它通过使用 expr
实用程序评估 reply
并搜索与正则表达式 ('\([0-9]*[0-9]\)'
) 匹配的第一个值来实现此目的。 由于 scan
命令输出的格式,此正则表达式将始终匹配正确的光标值:
redis-migrate.sh
. . . while [[ "$cursor" -ne 0 ]]; do . . . if [ -n "$keys" ]; then echo "$keys" | while IFS= read -r key; do redis-cli -a "$localpw" -n "$sourcedb" migrate localhost 8000 "$key" "$targetdb" 1000 copy auth "$managedpw" >/dev/null 2>&1 done fi cursor=$(expr "$reply" : '\([0-9]*[0-9]\)') done
总之,脚本应该如下所示:
redis-migrate.sh
#!/bin/bash set -euo pipefail if [ "$#" -lt 2 ] then echo "Migrate Redis keys to a DigitalOcean Managed Database" echo "Usage: $0 [source database] [target database]" exit 1 fi sourcedb=${1} targetdb=${2} cursor=-1 read -s -p "Enter local Redis password: " localpw echo "" read -s -p "Enter managed Redis password: " managedpw while [[ "$cursor" -ne 0 ]]; do if [[ "$cursor" -eq -1 ]] then cursor=0 fi reply=$(redis-cli -a "$localpw" -n "$sourcedb" SCAN "$cursor") if [ -n "$reply" ]; then echo "$reply" | tail -n +2 | while IFS= read -r key; do redis-cli -a "$localpw" -n "$sourcedb" migrate localhost 8000 "$key" "$targetdb" 1000 copy auth "$managedpw" >/dev/null 2>&1 done fi cursor=$(expr "$reply" : '\([0-9]*[0-9]\)') done
仔细检查您是否正确添加了每一行,然后保存并关闭文件。 如果您使用 nano
创建脚本,请按 CTRL + X
、Y
,然后按 ENTER
。
要结束脚本的创建,请使用 chmod
将其标记为可执行文件:
sudo chmod +x redis-migrate.sh
这样,您就可以使用该脚本将您的 Redis 数据迁移到托管的 Redis 实例了。
第 4 步 — 迁移您的 Redis 数据
要使用您在上一步中创建的脚本迁移您的 Redis 数据,您可以像这样调用它:
./redis-migrate.sh source_database target_database
假设您遵循 本教程的可选第一步 并使用数据加载了本地 Redis 实例的默认数据库 (0
),并且您希望将此数据迁移到您的 0
数据库托管实例,您将使用以下命令:
./redis-migrate.sh 0 0
您将收到本地 Redis 实例身份验证密码的第一个提示:
OutputEnter local Redis password:
输入本地 Redis 的密码,然后按 ENTER
。 如果您尚未将本地 Redis 实例配置为需要密码,只需按 ENTER
将 localpw
密码变量留空。
然后系统会提示您输入托管 Redis 数据库的密码:
OutputEnter local Redis password: Enter managed Redis password:
注意: 如果您手头没有托管 Redis 数据库的密码,您可以通过首先导航到 DigitalOcean 控制面板 来找到它。 从那里,单击左侧边栏菜单中的 Databases,然后单击要将数据迁移到的 Redis 实例的名称。 向下滚动到 Connection Details 部分,您将在其中找到标有 password 的字段。 单击显示按钮以显示密码,然后将其复制并粘贴到提示符中以进行身份验证。
如果您输入了正确的数据库编号和有效密码,该脚本将迁移您数据库中的每个键并关闭,而不会进一步输出。 要测试迁移是否成功,请连接到您的托管 Redis 数据库:
redis-cli -h localhost -p 8000 -a managed_redis_password
如果您将数据迁移到默认数据库以外的任何逻辑数据库,请使用 select
命令连接到该数据库:
select target_database
运行 scan
命令以查看现在保存在那里的一些键:
scan 0
如果您完成了本教程的 Step 1 并将示例数据添加到源数据库,您将看到如下输出:
Output1) "10" 2) 1) "set1" 2) "string6" 3) "string11" 4) "string3" 5) "string5" 6) "string10" 7) "string14" 8) "string18" 9) "string2" 10) "string4"
最后,在您设置为过期的任何密钥上运行 ttl
命令,以确认它仍然是易失性的:
ttl string2
Output(integer) 3944
此输出显示,即使您将密钥迁移到托管数据库,它仍会根据您之前运行的 expireat
命令设置为过期。
确认源 Redis 数据库上的所有密钥都已成功导出到目标后,您可以关闭与托管数据库的连接。 如果本地 Redis 实例上的任何其他逻辑数据库保存任何数据,则需要为每个数据库再次运行脚本,确保包含适当的源数据库和目标数据库作为参数。 此外,如果您有客户端将数据写入源 Redis 实例并且您已经将它们配置为将其写入发送到目标,则可以将它们配置为在完成迁移所有数据后停止向源发送数据。
结论
通过完成本教程,您将数据从自我管理的 Redis 数据存储移动到由 DigitalOcean 管理的 Redis 实例。 用于此过程的 Bash 脚本可能并不适合每个 Redis 用例,但它适用于本教程中描述的用例,并且还可以针对其他用例进行优化。
现在您正在使用 DigitalOcean 托管 Redis 数据库来存储数据,您可以通过 运行一些基准测试 来衡量其性能。 此外,如果您是使用 Redis 的新手,您可以查看我们关于 如何管理 Redis 数据库 的系列。