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

在這篇文章中,我將列出一些從MySQL邏輯復制一張表到Redis的一些選項。我將使用經典的world數據庫運行示例。 world數據庫可以在這里下載,所以如果您想嘗試這些示例,只需將它導入到您的MySQL實例中。
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 功能,它可以生成一個格式化的輸出文件。特別是,我將確保字段用逗號分隔。讓我們試一試:
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中修復它:
[mysqld]
secure_file_priv=/tmp
現在我可以導出:
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","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)。
#!/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] })
讓我們檢查下是否正確導入:
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項的內容:
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可作為客戶端,可以從遠端導出數據,而不需要修改讓任何服務器配置??梢源藖泶鍿ELECT INTO OUTFILE,對有些人來說,它可能代表一個安全問題,因為它是在服務器本身導出行數據到文件系統,并需要啟用secure_file_priv。
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。

Redis輸入輸出工具: RIOT
Redis輸入/輸出工具(RIOT)是一個工具集,其設計目的在于幫助您從Redis導入導出數據。
在本節中,我將演示RIOT的CSV導入工具,稱之為RIOT文件。首先,安裝它(我使用Mac,但是您可以參考文檔了解其他安裝方法)
brew install redis-developer/tap/riot-file
安裝后,測試幫助方法:
(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,完成后,繼續:
(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文件。
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)。
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頭。
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。下面來安裝它:
brew install redis-developer/tap/riot-db
然后像往常一樣導入,指定表名和列名,提供正確的連接信息,顯然,需要定義導入數據集的查詢語句。
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服務器:
*<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語句將是這樣的:
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:
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值,您可以執行下面的命令:
redis-cli -p <PORT> --scan --pattern city:* -i 0.01 | xargs redis-cli -p <PORT> unlink
總結
現在,您能從MySQL導出一張表,或者從其他的關系型數據庫,任何可以導出CSV的數據庫,或者RIOT DB,或者RedisCDC能連接的多種數據庫。
接下來,您可能想知道您能用這些創建在Redis實例中的哈希值做些什么。所以我在下篇文章中,將分享一些例子,關于在Redis中存儲、檢索、更改、刪除、索引和檢索數據的命令。
那么現在呢? 你可能想知道你可以用這些在Redis實例中創建的哈希值做什么。所以在下一篇文章中,我將分享幾個在Redis中存儲、檢索、更改、刪除、索引和搜索數據的命令示例。別走開,我很快就會寫的!




