找回密码
 立即注册
首页 业界区 科技 让老弟做个数据同步,结果踩了 7 个大坑! ...

让老弟做个数据同步,结果踩了 7 个大坑!

筒濂 3 天前
<p >你是小阿巴,刚入职一家电商公司。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/image-20251009155924950.png" >
</p>
<p > </p>
<p >第一天上班,老板就交给你一个艰巨的任务:<strong>定期把公司的订单数据同步到数据分析仓库。</strong></p>
<p >一听到数据同步这 4 个字,你立刻汗流浃背了。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1757330035584-983462d6-c9d3-40db-b840-49adbd5f72f1.png" >
</p>
<p > </p>
<p >你的哥哥程序员鱼皮,曾经就是在大公司负责数据同步。结果双十一当天,近 2 小时的订单数据没有同步过去。数据分析团队看到的数据是 2 小时前的,以为销量没达到预期,就没有及时给热销商品补货。最终错失了 1 个多亿的销售额!鱼皮也因此被老板优化掉了。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759994850635-37124cca-61f1-4086-8034-a1b8425264f4.png" >
</p>
<p > </p>
<p >作为一名程序员,怎能轻易退缩?你握紧拳头,一定要完成好这个任务!</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1757302352558-87315fa6-afc4-498a-8d9d-f7d31eaf486e.png" >
</p>
<p > </p>
<blockquote>
<p >建议观看本文对应视频版:https://bilibili.com/video/BV1Xvnoz7EKJ</p>
</blockquote>
<p > </p>
<h2 >全量同步</h2>
<p >什么是数据同步呢?就像你有两个手机,要把其中一个手机的照片复制到另一个手机里。数据同步就是把一个数据库的数据,定期复制到另一个数据库里。</p>
<p >你心想:这还不简单吗?我写个定时任务,<strong>每天</strong> 把整个订单表的数据 <strong>全部查出来</strong>,然后 <strong>一股脑插入</strong> 到数据仓库!</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759994919352-d7f8c0ec-2487-4757-a96c-e7f1688ed617.png" >
</p>
<p > </p>
<p >这种方法叫 <strong>全量同步</strong>,不管数据有没有变化,每次都把所有数据重新复制一遍,简单粗暴。</p>
  1. # 查出全部订单<br>orders = db.query("SELECT * FROM orders")<br># 清空已有老数据<br>warehouse.execute("DELETE FROM orders")<br># 插入新值<br>warehouse.insert(order)
复制代码
<p > </p>
<p >第一天,公司有 1 万条订单数据。你的程序跑了 3 个小时,同步成功!</p>
<p >老板看着数据夸到:不错啊小阿巴,开发神速啊!</p>
<p >
<img  data-src="https://pic.yupi.icu/1/image-20251009155909647.png" >
</p>
<p >你内心暗爽:高端的功能往往只需要最简单的代码。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1757330515863-cc70f514-e613-4d5a-b414-148a67128c51.png" >
</p>
<p > </p>
<h2 >基础告警</h2>
<p >第二天一早,你还没到公司,就收到了运营小姐姐的夺命连环 call:“小阿巴!出大事了!昨天晚上的数据同步失败了,现在老板还没看到昨天的数据,他正在会议室发火呢!”</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1759995033482-9f076b28-288b-4625-98c3-96aaf54362e8.png" >
</p>
<p > </p>
<p >你意识到:现在的程序是不可靠的,如果因为网络等原因同步失败了,可能要到第二天才会发现。</p>
<p >于是你给程序加了一段逻辑:如果同步失败,会记录错误日志,同时发邮件通知管理员,并且把数据库回滚到同步前的状态。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759995098675-467c67d2-011c-45b4-b3e2-85bc75a9df59.png" >
</p>
<p >这样如果出了问题,你会比老板先发现,手动重新执行一遍同步任务就好。</p>
<p > </p>
<h2 >增量同步</h2>
<p >过了几天,老板黑着脸找你了:“小阿巴,为什么今天数据还没同步完?”</p>
<p >你看了下订单数据,立刻发现了问题。现在有 10 万条数据,程序跑了 30 个小时还没结束;这样下去,如果有 100 万条数据,估计要 300 个小时!</p>
<p >老板:少废话,快点解决,不然送你到隔壁餐饮部沉淀沉淀。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1757331438867-56b95599-5841-479d-a938-bf7d2c9f10d1.png" >
</p>
<p > </p>
<p >你开始思考:既然全量同步太慢,那我只同步每天新增的订单不就行了?</p>
<p >
<img  data-src="https://pic.yupi.icu/1/image-20251009155853538.png" >
</p>
<p > </p>
<p >这就是 <strong>增量同步</strong>,约定每天 0 点执行同步任务,只同步昨天 0 点之后创建的订单。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759995158283-eb8f60ee-b266-413d-95b9-52ed5bee4769.png" >
</p>
<p > </p>
<p >这样大大减少了每次同步的数据量,任务的执行时间不再线性增加了。</p>
<p >但很快,你发了一个问题,订单的状态是会发生变化的,比如用户付款后又退款。</p>
<p >如果使用订单创建时间作为增量同步的分界标志,<strong>只能同步新增的订单</strong>,但是订单状态的更新不会同步!</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1759995212769-4f34e5f2-ccd5-43f2-9f69-75761964863a.png" >
</p>
<p > </p>
<p >于是,聪明的你使用 updated_time 字段进行增量同步,这个字段会在每次数据变化时自动更新。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1759995261304-6b2c9082-1001-4975-a0c4-030d18b2b499.png" >
</p>
<p > </p>
<p >这样新增和修改的数据都能同步了!</p>
<p >你内心窃喜:聪明如我小阿巴,老板夸我好开发。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/image-20251009155835763.png" >
</p>
<p > </p>
<h2 >批处理</h2>
<p >过了几天,公司搞了一波大促活动,当天订单量比平时多了几倍,老板请大家通宵狂欢开 party。</p>
<p >结果正在你欢唱 “只因你太美” 的时候,突然公司的运维大叫:不好了不好了,我们的项目服务器卡死了,用户无法下单!</p>
<p >你看了看时间,0 点多,不正是数据同步任务的执行时间么?你瞬间汗流浃背,放下麦克风,赶回公司修 Bug。</p>
<p >原来是因为一次查询出来的订单数据太多了,都加载到内存,导致服务器 OOM 内存溢出了。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759995313237-0b07ed98-7b26-4d06-8655-e037aef3774c.png" >
</p>
<p > </p>
<p >你悔不当初:唉,早该想到这个问题,既然单次处理数据量太大有风险,那我就 <strong>分批处理</strong>。</p>
<p >每 100 条数据为一批,每次只从数据库中分页查询出这一批数据,同步完这一批,再执行下一批。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759995343276-e0bcb724-08f2-47a3-84c3-5f14e99ef57c.png" >
</p>
<p >这样每次只处理少量数据,内存压力小;而且如果某一批处理失败,只需要回滚和重新同步这一批,而不是全部重来。</p>
<p >问题算是解决了,但是今夜你彻夜难眠,你似乎成了公司业务增长后,唯一不开心的那个人。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/image-20251009155942288.png" >
</p>
<p >希望今后不会再遇到这种闹心事了吧。</p>
<p > </p>
<h2 >游标机制</h2>
<p >但生活总是这样,屋漏偏逢连夜雨。两天后,你又收到了老板的咆哮:“狗阿巴,这就是你做的数据同步?你自己看看丢了多少数据!”</p>
<p >你大惊,丢失数据?不应该啊。。。</p>
<p >经过仔细排查,你发现了问题:如果在分页查询的过程中,有新的数据被插入或更新,就会导致数据偏移和丢失!</p>
<p >比如查询第 1 页时,符合条件的订单有 4 条:</p>
  1. 订单 A:updateTime=08:00<br>订单 B:updateTime=09:00<br>订单 C:updateTime=09:30<br>订单 D:updateTime=09:50
复制代码
<p > </p>
<p >执行第 1 页查询,每页 2 条,偏移量为 0:</p>
  1. SELECT * FROM orders <br>WHERE updated_time >= '2025-09-08' and updated_time < '2025-09-09'<br>ORDER BY updated_time <br>LIMIT 2 OFFSET 0;  -- 每页 2 条,第 1 页
复制代码
<p > </p>
<p >查询结果返回订单 A(08:00)和订单 B(09:00),同步完成后将偏移量调整为下一页 <code>OFFSET=2</code>。</p>
<p >可就在查询下一页前,订单 B 因为 “修改订单状态” 被更新,这时订单 B 的更新时间就不在查询范围内了。</p>
<p >这就导致查询下一页 <code>OFFSET=2</code> 时,直接跳过了前两条订单 A 和 C,从 D 开始同步,导致订单 C 丢失。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759995524386-f10f3fed-300d-4851-9f01-526792b5365d.png" >
</p>
<p > </p>
<p >怎么解决这个问题呢?</p>
<p >这可难不倒你小阿巴,既然 <strong>动态数据集</strong> 中使用 SQL 自带的 OFFSET 偏移作为分页起点会出问题,那不妨 <strong>自定义一个标志来记录下一批要同步的起点</strong>。</p>
<p >这就是游标机制。</p>
<p >比如我约定自增的主键 id 作为游标,每查询一批数据之后,把这批数据的最后一条记录作为新的游标值;查询下一批数据时,只查询 id > 游标的数据。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759995573090-9735de06-2e15-4461-8f7c-3bab44a97359.png" >
</p>
<p > </p>
<p >这样不仅防止数据丢失,还避免了 OFFSET 深度分页带来的性能问题。</p>
<p >此外,如果把游标想象成进度条的断点,可以更清晰地记录同步进度,失败后可以从断点继续。</p>
<p >做完这一通优化后,你长吁了一口气,工作看来是保住了。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1759995638465-73c62f0d-7a7b-4dc0-88b5-a36d6bdbc191.png" >
</p>
<p > </p>
<h2 >性能优化</h2>
<p >你以为终于可以安稳一段时间了。但没想到,公司的好日子才刚刚开始。</p>
<p >随着老板大力扩张业务,公司每天的订单量可以达到百万条,你的同步任务每次要跑几个小时才能完成。</p>
<p >更要命的是,老板现在对数据的依赖越来越强,要求每隔 2 个小时就要同步一次数据!</p>
<p >怎么能够让任务执行更快呢?</p>
<p >这时,你想起了曾经在 程序员面试神器 - 面试鸭 上背过的各种性能优化八股文。好家伙,终于能派上用场了!</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759995722167-efb7f2c3-f273-4e18-b8c2-e7d7818d21aa.png" >
</p>
<p > </p>
<p >首先,修改插入数据库的操作方式。之前是一条一条地插入订单数据,改为执行一条批处理语句来同时插入同一批内多个订单数据,减少了跟数据库通信的次数,性能大幅提升。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759995753532-567f228e-555e-415e-90a4-b041f42b3e2d.png" >
</p>
<p > </p>
<p >但是你觉得还不够快,于是优化了批处理的流程。之前是等一批同步完再同步下一批,串行执行;现在你启动了多个线程,每个线程负责处理一批订单的同步,多个线程可以同时搬砖干活,效率大幅提高。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759995777027-8b24157c-2516-45ce-9dc1-daaa01d08422.png" >
</p>
<p > </p>
<p >这一通操作下来,老板都激动了:“小阿巴,你这技术可以啊!好好干,我给你升职加薪!”</p>
<p >你笑了,有了老板这句话,你想继续努力给公司卖命了。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/image-20251009160000114.png" >
</p>
<p > </p>
<h2 >实时同步</h2>
<p >两年后,随着你头发的消逝,公司总部决定要上市了!</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1757333121647-2aee1cf4-985b-4f7d-b32b-e4f78341143d.png" >
</p>
<p > </p>
<p >老板找到了你:“阿巴阿巴,咱们的数据同步能不能做到实时?我想给投资人展示实时的业务监控。最好是用户刚下单,投资人立刻就能在大屏幕上看到!"</p>
<p >
<img data-src="https://pic.yupi.icu/1/1757333292392-76574e68-7543-4c8b-857b-de4f957dff5d.png" >
</p>
<p > </p>
<p >你早料到会有这个需求,帅气地甩出一句话:“交给我吧,我让你见识一下企业级方案!”</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1757333312216-dd91bdde-412c-4fa0-a22f-9141b05107a4.png" >
</p>
<p > </p>
<p >这两年你也没闲着,像实时数据分析这种典型需求的实现方案,你早已烂熟于心。</p>
<p >普通的定时任务已经无法满足实时性的要求,只能搬出 <strong>CDC + 消息队列</strong> 这两大杀器了。</p>
<p >CDC(Change Data Capture)就像给数据库安装了一个 24 小时实时监控的摄像头,数据有任何变化,摄像头都能立刻发现。</p>
<p >消息队列就像一个快递中转站。数据库变化的消息先发送到这个中转站,然后同步数据的程序从中转站取出数据并写入到数据分析仓库中。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759995906086-cd50caab-2999-41f6-b741-29094764baab.png" >
</p>
<p >有了中转站后,如果消息特别多,程序来不及处理,也不会丢失数据。</p>
<p >实现了这套方案之后,用户只要一下单,100 毫秒内老板就能在仪表盘上看到。而且保险起见,你还给消息队列加了个监控,如果消息堆积太多,就会自动告警。</p>
<p >他开心得像个孩子:“小阿巴,我给你升职加薪,还给你带新人!”</p>
<p >你又笑了,开始幻想着你和新人坐在高高的办公桌上,你给他讲述自己光辉事迹,他向你投来羡慕的目光。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/image-20251009160016437.png" >
</p>
<p > </p>
<h2 >方案完善</h2>
<p >半个月后,公司迎来了双十一大促活动。</p>
<p >你正在和新来的实习生阿坤吹牛皮,没想到瞬间产生了大量订单,像洪水一般,让你的实时同步系统出现了各种异常:</p>
<ul  data-mark="-">
<li >
<p >有些订单重复同步了</p>
</li>
<li >
<p >有些订单的状态变化顺序错乱了</p>
</li>
<li >
<p >还有大量消息堆积在消息队列里处理不过来,导致数据没有及时同步</p>
</li>
</ul>
<p >
<img data-src="https://pic.yupi.icu/1/1759995960271-41cb0164-d4f3-411e-a38e-6fd3e6adb445.png" >
</p>
<p > </p>
<p >监控大屏上各种告警此起彼伏,老板的脸色越来越难看:“狗阿巴,这就是你说的企业级方案?”</p>
<p >这一刻,你有点恍惚:“我只是知道可以这么实现,但没人告诉我出了这些问题怎么解决。。。”</p>
<p >这时,你身旁的实习生阿坤说话了:“我来!”</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1759995990844-91a74952-e241-4185-8258-c6b532915ef5.png" >
</p>
<p >只见他对着电脑一通操作,嘴里振振有词:</p>
<p >1)消息重复问题,可以通过幂等机制解决。给每条消息一个唯一编号,处理过的消息编号记录下来,就不会重复处理了。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1759996039756-342426f2-ad73-44c8-825a-030f5509c895.png" >
</p>
<p >2)消息乱序问题,可以通过分区解决。把相关的消息放到同一个分区,保证同一个订单的消息按顺序处理。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/1759996073928-505edf6a-5b77-40d3-8415-6524eeadf0a8.png" >
</p>
<p >3)消息堆积问题,可以通过搭建集群和动态扩容,增加对消息的处理能力。</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759996099155-67f92096-243e-4018-9ead-7b99fa0e6974.png" >
</p>
<p > </p>
<p >“这些点在使用消息队列来实现数据同步的时候就要考虑到才对呀!”</p>
<p >你听着阿坤说的话,默默低下了头,内心五味杂陈,这就是应届生的水平么?</p>
<p >阿坤看着你的代码接着说:“不是哥,做个数据同步要这么麻烦么?DataX、Canal、Debezium,这么多现成的企业级数据同步工具你不用?非要自己写代码?”</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759996166549-33e27c19-8aff-4d98-a976-08081aaa0e19.png" >
</p>
<p > </p>
<p >你无言以对,内心感受到了来自实习生的一吨暴击。</p>
<p >阿坤甚至一脸嫌弃地看着你:“不是哥们,连数据对账都不做么?你自己都不定期检查同步前后的数据是否一致么?我这有份数据同步的企业级方案,你好好看看吧。”</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759996200951-2f830025-e6e2-4cf6-903e-96f4c43b4275.png" >
</p>
<p >
<img data-src="https://pic.yupi.icu/1/1759996218595-b67524b0-e9b4-4898-b67e-ea5803d3c3da.png" >
</p>
<p > </p>
<p >老板拍了拍阿坤的肩膀:“小伙子优秀,导师的导师,今后公司的未来就靠你了,我给你升职加薪。至于老阿巴,你好自为之吧。”</p>
<p >这个城市,又多了一个伤心的人,给个点赞收藏三连,帮忙回回血吧。</p>
<p >
<img  data-src="https://pic.yupi.icu/1/image-20251009160034592.png" >
</p>
<h2 >更多编程学习资源</h2>
<ul  data-mark="-">
<li >
<p >Java前端程序员必做项目实战教程+毕设网站</p>
</li>
<li >
<p >程序员免费编程学习交流社区(自学必备)</p>
</li>
<li >
<p >程序员保姆级求职写简历指南(找工作必备)</p>
</li>
<li >
<p >程序员免费面试刷题网站工具(找工作必备)</p>
</li>
<li >
<p >最新Java零基础入门学习路线 + Java教程</p>
</li>
<li >
<p >最新Python零基础入门学习路线 + Python教程</p>
</li>
<li >
<p >最新前端零基础入门学习路线 + 前端教程</p>
</li>
<li >
<p >最新数据结构和算法零基础入门学习路线 + 算法教程</p>
</li>
<li >
<p >最新C++零基础入门学习路线、C++教程</p>
</li>
<li >
<p >最新数据库零基础入门学习路线 + 数据库教程</p>
</li>
<li >
<p >最新Redis零基础入门学习路线 + Redis教程</p>
</li>
<li >
<p >最新计算机基础入门学习路线 + 计算机基础教程</p>
</li>
<li >
<p >最新小程序入门学习路线 + 小程序开发教程</p>
</li>
<li >
<p >最新SQL零基础入门学习路线 + SQL教程</p>
</li>
<li >
<p >最新Linux零基础入门学习路线 + Linux教程</p>
</li>
<li >
<p >最新Git/GitHub零基础入门学习路线 + Git教程</p>
</li>
<li >
<p >最新操作系统零基础入门学习路线 + 操作系统教程</p>
</li>
<li >
<p >最新计算机网络零基础入门学习路线 + 计算机网络教程</p>
</li>
<li >
<p >最新设计模式零基础入门学习路线 + 设计模式教程</p>
</li>
<li >
<p >最新软件工程零基础入门学习路线 + 软件工程教程</p>
</li>
</ul><br>来源:程序园用户自行投稿发布,如果侵权,请联系站长删除<br>免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册