[译] 迁移 MySQL 的表到 Redis

[译] 迁移 MySQL 的表到 Redis

严少安 Lv.6

原文链接:https://www.mortensi.com/2021/12/migrate-a-mysql-table-to-redis/
原文作者:Admin

1.png

在这篇文章中,我将列出一些从MySQL逻辑复制一张表到Redis的一些选项。我将使用经典的world数据库运行示例。 world数据库可以在这里下载 ,所以如果您想尝试这些示例,只需将它导入到您的MySQL实例中。

1
2
3
4
5
6
7
8
9
10
11
mysql> use world;
Database changed
mysql> show tables;
+-----------------+
| Tables_in_world |
+-----------------+
| city |
| country |
| countrylanguage |
+-----------------+
3 rows in set (0.01 sec)

有多种方法可以将表格导入Redis,让我们来看看其中的几种。在下面的例子中,我将选择一个Hash 数据类型,最类似于Redis的传统表行。

Redis哈希是字符串字段和字符串值之间的映射,所以它们是表示对象的完美数据类型(例如,一个用户有许多字段,如名字、姓氏、年龄等)

使用 SELECT INTO OUTFILE 导出CSV格式,使用Python导入

为了从MySQL导出数据并将其导入Redis,我们可以使用MySQL SELECT into OUTFILE 功能,它可以生成一个格式化的输出文件。特别是,我将确保字段用逗号分隔。让我们试一试:

1
2
mysql> SELECT * INTO OUTFILE '/tmp/city.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY 'n' FROM world.city;
ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement

噢,MySQL默认配置禁止导出数据到文件系统。让我们在配置文件my.cnf中修复它:

1
2
[mysqld]
secure_file_priv=/tmp

现在我可以导出:

1
2
mysql> SELECT * INTO OUTFILE '/tmp/city.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY 'n' FROM world.city;
Query OK, 4079 rows affected (0.01 sec)

这是示例:

1
2
3
4
5
6
7
8
"1","Kabul","AFG","Kabol","1780000"
"2","Qandahar","AFG","Qandahar","237500"
"3","Herat","AFG","Herat","186800"
"4","Mazar-e-Sharif","AFG","Balkh","127800"
"5","Amsterdam","NLD","Noord-Holland","731200"
"6","Rotterdam","NLD","Zuid-Holland","593321"
"7","Haag","NLD","Zuid-Holland","440900"
"8","Utrecht","NLD","Utrecht","234323"

现在数据是CSV格式,我能使用一个简单的Python脚本导入它,下面是个可用的示例(参考说明设置 Python 环境 以连接到Redis)。

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python3
import redis
from csv import reader

r = redis.Redis(host='127.0.0.1', port=4321, password='')

with open('/tmp/city.csv', 'r') as cities:
csv_reader = reader(cities)
for row in csv_reader:
r.hset("city:" + row[0], mapping={ "Name" : row[1], "CountryCode" : row[2], "District" : row[3], "Population" : row[4] })

让我们检查下是否正确导入:

1
2
3
4
5
6
7
8
127.0.0.1:4321> SCAN 0 MATCH city:* COUNT 5
1) "3072"
2) 1) "city:2316"
2) "city:749"
3) "city:570"
4) "city:3625"
5) "city:3328"
6) "city:1771"

我也可以验证Hash项的内容:

1
2
3
4
5
6
7
8
9
127.0.0.1:4321> HGETALL city:4059
1) "Name"
2) "Cary"
3) "CountryCode"
4) "USA"
5) "District"
6) "North Carolina"
7) "Population"
8) "91213"

使用mysqldump导出CSV格式

备份工具 mysqldump 可作为客户端,可以从远端导出数据,而不需要修改让任何服务器配置。可以此来代替SELECT INTO OUTFILE,对有些人来说,它可能代表一个安全问题,因为它是在服务器本身导出行数据到文件系统,并需要启用secure_file_priv

1
mysqldump --host=127.0.0.1 --user=root --password --tab=/tmp --fields-enclosed-by='"' --fields-terminated-by=',' --lines-terminated-by='n' world city

一旦文件导出(mysqldump导出两个文件,表定义保存在city.sql文件,数据以CSV格式保存在city.txt文件),数据可以按照说明导入。

使用RIOT导入CSV备份

假设你有一个MySQL表的CSV备份,事实上,CSV可以来自于任何数据源,比如Excel表格,Postgres等等。如果你不想编码来导入到Redis,你可以使用RIOT(Redis输入输出工具),用于将异构数据源迁移到Redis。

2.png

Redis输入输出工具: RIOT

Redis输入/输出工具(RIOT)是一个工具集,其设计目的在于帮助您从Redis导入导出数据。

在本节中,我将演示RIOT的CSV导入工具,称之为 RIOT文件 。首先,安装它(我使用Mac,但是您可以参考文档了解其他安装方法)

1
brew install redis-developer/tap/riot-file

安装后,测试帮助方法:

1
2
3
(redisvenv) bash-3.2$ riot-file --help
The operation couldn’t be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.

是的,您也需要从www.java.com 下载JRE,完成后,继续:

1
2
3
4
5
6
7
8
9
10
11
(redisvenv) bash-3.2$ riot-file import hset --help
Usage: riot-file import hset [OPTIONS]
Set hashes from input
-H, --help Show this help message and exit
-s, --separator=<str> Key separator (default: :)
-r, --remove Remove key or member fields the first time they are used
--ignore-missing Ignore missing fields
-p, --keyspace=<str> Keyspace prefix
-k, --keys=<fields>... Key fields
--include=<field>... Fields to include
--exclude=<field>... Fields to exclude

现在,我们必须将CSV数据源映射到适当的Hash数据结构。我们原始的CSV文件第一行没有标题。如果我们想加标题,我们甚至可以按照顺序从 MySQL information_schema 中抽取。

注意:MySQL SELECT INTO OUTFILE不会在导出的数据集添加头,这必须使用正确的SQL语法手动添加,或编辑导出的CSV文件。

1
2
3
4
5
6
7
mysql> SELECT group_concat(COLUMN_NAME ORDER BY ORDINAL_POSITION separator ',') FROM information_schema.COLUMNS WHERE TABLE_NAME='city' AND TABLE_SCHEMA='world';
+-------------------------------------------------------------------+
| group_concat(COLUMN_NAME ORDER BY ORDINAL_POSITION separator ',') |
+-------------------------------------------------------------------+
| ID,Name,CountryCode,District,Population |
+-------------------------------------------------------------------+
1 row in set (0.00 sec)

添加ID,Name,CountryCode,District,Population到CSV文件的第一行。但是我们仍然可以继续手动执行映射。在这里,我们指定字段在CSV文件中出现的顺序,指定表名(keyspace),及补充字段名(key)(例如city:1234)。

1
riot-file -h 127.0.0.1 -p 4321 import /tmp/city.csv --fields ID Name CountryCode District Population hmset --keyspace city --keys ID

或者,如果CSV文件有从MySQL获得的头,我们可以添加--header参数来使用手动添加的CSV头。

1
riot-file -h 127.0.0.1 -p 4321 import /tmp/city.csv --header hmset --keyspace city --keys ID

使用 RIOT DB 从MySQL导入

为了从没有中间转储备份导入MySQL,您可以编码一个方案,来连接源数据库和目标Redis库,或者简单地使用RIOT DB 。下面来安装它:

1
brew install redis-developer/tap/riot-db

然后像往常一样导入,指定表名和列名,提供正确的连接信息,显然,需要定义导入数据集的查询语句。

1
riot-db -h 127.0.0.1 -p 4321 import "SELECT * FROM city" --url jdbc:mysql://root:Oracle1*@127.0.0.1:3306/world hmset --keyspace city --keys ID

导出并以RESP格式做大量插入

到目前为止,我已经探索了一些逻辑方法,但是对于大量插入,最高速度需要达到百万个键,有一种不同的方法,即使用redis-cli的管道模式,读取数据的同时,立即发送到服务器。特别地,使用这种方法,我们将使用Redis协议(RESP) 。关于大量插入的更多信息请参阅这里

它可能导出RESP格式的表,并转瞬流到Redis服务器。让我们看看它是如何工作的。我们能使用SQL来生成数据,并以这种格式发送到Redis服务器:

1
2
3
4
5
6
7
8
*<args><cr><lf>
$<len0><cr><lf>
<arg0><cr><lf>
$<len1><cr><lf>
<arg1><cr><lf>
...
$<lenN><cr><lf>
<argN><cr><lf>

这里:

  • args 是参数的数量
  • lenN 是参数跟随的长度
  • argN 是参数

假设我们要为MySQL表中的每一行添加一个Hash,每个Hash将插入4个字段,在RESP协议中生成转储的SQL语句将是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT CONCAT(
"*10\r\n",
'$', LENGTH(redis_cmd), '\r\n',redis_cmd, '\r\n','$', LENGTH(redis_key), '\r\n',redis_key, '\r\n',
'$', LENGTH(hkey1), '\r\n',hkey1, '\r\n','$', LENGTH(hval1), '\r\n', hval1, '\r\n'
'$', LENGTH(hkey2), '\r\n',hkey2, '\r\n','$', LENGTH(hval2), '\r\n', hval2, '\r\n'
'$', LENGTH(hkey3), '\r\n',hkey3, '\r\n','$', LENGTH(hval3), '\r\n', hval3, '\r\n'
'$', LENGTH(hkey4), '\r\n',hkey4, '\r\n','$', LENGTH(hval4), '\r\n', hval4, '\r\n'
)
FROM (
SELECT
'HSET' AS redis_cmd, CONCAT('city:',ID) AS redis_key,
'Name' AS hkey1, Name AS hval1,
'CountryCode' AS hkey2, CountryCode AS hval2,
'District' AS hkey3, District AS hval3,
'Population' AS hkey4, Population AS hval4
FROM world.city
) AS t;

保存SQL到resp.sql文件,并用Redis的管道模式从MySQL流到Redis:

1
2
3
4
5
bash-3.2$ mysql -h 127.0.0.1 -uroot -p -Dworld --skip-column-names --raw </tmp/resp.sql |redis-cli -p 4321 --pipe
Enter password:
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 4079

数据已经导入到Redis服务器!

使用 Redis 的 MySQL 连接器:redis-connect-mysql

最后一节,我将介绍如何使用Redis连接套件,特别是 redis-connect-mysql 。您可以阅读这份文档:

redis-connect-mysql第一次连接MySQL时,它会读取所有模式的一致性快照。当快照创建完成,连接器将连续发送MySQL的变更并生成相应的insert,update或delete事件。

因此,它不仅仅是一个迁移工具,还是一个连接异构数据库的复制和传输工具。Redis CDC (变更数据捕捉)曾在RedisConf 2021上演示,所以请查看演示的详细内容。

译者注:这段视频在油管上,此处跳过。
RedisCDC: Seamless database migrations and continuous changed-data replication, Redis Labs

我现在没有运行使用RedisCDC的示例,但是请查看代码库 来了解如何操作它。

清理

为了移除那些导入的Hash值,您可以执行下面的命令:

1
redis-cli -p <PORT> --scan --pattern city:* -i 0.01 | xargs redis-cli -p <PORT> unlink

总结

现在,您能从MySQL导出一张表,或者从其他的关系型数据库,任何可以导出CSV的数据库,或者RIOT DB,或者RedisCDC能连接的多种数据库。

支持RedisCDC的连接器在 这里 ,支持 RIOT DB的连接器在这里

接下来,您可能想知道您能用这些创建在Redis实例中的哈希值做些什么。所以我在下篇文章中,将分享一些例子,关于在Redis中存储、检索、更改、删除、索引和检索数据的命令。

那么现在呢? 你可能想知道你可以用这些在Redis实例中创建的哈希值做什么。所以在下一篇文章中,我将分享几个在Redis中存储、检索、更改、删除、索引和搜索数据的命令示例。别走开,我很快就会写的!

  • Title: [译] 迁移 MySQL 的表到 Redis
  • Author: 严少安
  • Created at: 2022-03-16 20:03:49
  • Updated at: 2022-03-16 20:03:49
  • Link: https://shawnyan.cn/2022/mysql/fanyi-migrate-a-mysql-table-to-redis/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments