人麻了,领导让我写个脚本,把700T数据库,全部迁移到别的机房,带宽不超过2Mbps
说真的,领导这口一开,“你写个脚本把700T数据库迁一下”,我当场差点没直接过去了。
700T,朋友们,这不是700MB,不是700GB,是700T!
脑子里第一个反应是:您老在开玩笑吧?700T,还是跨机房的迁移,还给我限速——2Mbps?这速度怕不是想让我搬到下辈子?
不过嘴上不能说,心里也只能默默咽下这口老血,毕竟“程序员不能说不行,只能说安排”。
“你就写个脚本嘛”,咋写?
先说说这事的背景,两个机房,分别部署了不同类型的国产数据库,老机房用的是 A 数据库,新机房上的是 B 数据库。问题来了,字段类型都不完全兼容,就像你拿 Excel 想导入 Word,那肯定要先把格式整理一遍。
然后领导画了个饼:不能影响骨干网络,传输速度别超过 2Mbps。
我听完脑子里三个字:物理迁移。
网友说“硬盘快递”,真不是开玩笑
700T 数据,2Mbps 的速度,咱们算一笔账:
2Mbps 也就是每秒 0.25MB,一天 24 小时、3600 秒,差不多就是每天 21GB。你迁一整年也迁不完好吧?这不扯淡嘛。
所以网友说的“把硬盘拔下来,坐高铁送过去”,是真·工程师智慧。现在还有个专业名词:“Sneakernet”——意思就是物理拷贝比网络快。
不过呢,现实中硬盘不是你想拔就拔,你得考虑权限、安全、数据库是否在运行中、冷热数据如何区分……但方向没错,大数据量物理迁移是主流。
假如你非要写“一个脚本”?
咱说点正经的,真要写脚本,也得结合这个场景:
数据源:A 国产数据库 目标库:B 国产数据库(字段类型不完全兼容) 限速传输(2Mbps) 数据总量 700TB
假设这个场景是你非写不可,那至少你得按这个流程来想:
数据导出(ETL 过程) 字段映射与转换 压缩与分片 限速网络传输 目标库导入
是不是听着就想辞职?我来详细说说怎么搞。
第一步:数据导出
你得从 A 数据库中把数据“导”出来,这时候不是一口气全导,是分批来,一张张表按顺序导。写个 Java 脚本用 JDBC 连接数据库,按分页导出,比如这样:
Connection conn = DriverManager.getConnection(sourceUrl, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM huge_table LIMIT 0, 10000");
// 把结果写到本地文件,可能用 JSON、CSV、Parquet,根据实际情况来
别忘了加断点续传机制,你不可能一口气跑完,写个状态文件存每张表的进度。
第二步:字段转换
两个数据库字段不兼容?这就涉及到数据类型映射了。
比如 A 库里的 VARCHAR(255) 到 B 库里可能是 STRING,或者你遇到 CLOB、BLOB 这种老祖宗类型,还得转换成文件、再 base64 编码。
这部分没捷径,只能提前列清楚所有字段和类型的映射关系,写个转换器,类似下面:
public String convertType(String sourceType){
switch (sourceType.toUpperCase()) {
case"VARCHAR":
return"STRING";
case"CLOB":
return"TEXT";
case"NUMBER":
return"DECIMAL";
default:
return"STRING";
}
}
这个步骤也可以借助 Apache NiFi 或者 DataX 这类工具,但你得自己配置字段映射。
第三步:压缩 + 限速传输
带宽有限制?那你必须压缩啊。搞个压缩率高点的,比如 LZ4、Zstd,压完还要控制传输速度,不能上来就“满血输出”,那会被网管拍死。
Java 里实现限速传输,可以用 RateLimiter 或者搞个 Thread.sleep 控制节奏,比如用 Guava 的:
RateLimiter limiter = RateLimiter.create(0.25); // 每秒 0.25MB
while ((bytesRead = input.read(buffer)) != -1) {
limiter.acquire(bytesRead);
output.write(buffer, 0, bytesRead);
}
当然,也可以压完先“冷藏”到移动硬盘里,晚上下班后再通过 VPN 挂上慢慢传。你以为程序员都是敲键盘的?不,咱还得会掐点偷跑。
第四步:导入目标库
这时候得看 B 数据库怎么支持批量导入,假设支持 LOAD DATA 或者你写个批量 insert 脚本:
PreparedStatement ps = conn.prepareStatement("INSERT INTO target_table (a, b) VALUES (?, ?)");
ps.setString(1, "xxx");
ps.setInt(2, 123);
ps.addBatch();
批处理注意设置 commit 间隔,不然内存爆炸,还得控制导入速度,防止目标库崩了。
“同步”可以搞,但得砸钱
有网友说他买了阿里云 DTS 同步服务,花几十块钱就搞定——这要看你量多不多。
700T 用阿里云 DTS?兄弟,你这是要让阿里云给你搬家啊……几十块钱估计也就跑个“你昨天改过的三行配置”。
商业同步服务确实省事,但前提是你得把钱花在刀刃上,还要保证云上有直连,不然传输还是慢。
写脚本?可以,别全靠
700T 数据迁移,不是你写个 for 循环就能解决的事。真要写,脚本得做到:
能导出、导入; 能压缩、限速; 能容错、断点续传; 能字段映射、兼容目标库格式; 能分批执行,不能搞一波流。
但说实话,这种场景下“写脚本”就像你爸让你修房子给你一把锤子——是能干,但不现实。
更靠谱的方案,是脚本负责处理部分数据迁移+字段转换的逻辑,大头交给物理移动 + 专业工具。
毕竟,写代码不是为了让自己秃头,是为了让事情更简单。700T 的数据不怕多,就怕你轻敌。眼里没活、手里全是 bug 的,搞不好最后写脚本的是你,扛硬盘的也是你。