数据库数据同步,结果踩了 7 个大坑!
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
你是小阿巴,刚入职一家电商公司。 ![]() 第一天上班,老板就交给你一个艰巨的任务:定期把公司的订单数据同步到数据分析仓库。 一听到数据同步这 4 个字,你立刻汗流浃背了。 ![]() 你的哥哥程序员鱼皮,曾经就是在大公司负责数据同步。结果双十一当天,近 2 小时的订单数据没有同步过去。数据分析团队看到的数据是 2 小时前的,以为销量没达到预期,就没有及时给热销商品补货。最终错失了 1 个多亿的销售额!鱼皮也因此被老板优化掉了。 ![]() 作为一名程序员,怎能轻易退缩?你握紧拳头,一定要完成好这个任务! ![]()
全量同步什么是数据同步呢?就像你有两个手机,要把其中一个手机的照片复制到另一个手机里。数据同步就是把一个数据库的数据,定期复制到另一个数据库里。 你心想:这还不简单吗?我写个定时任务,每天 把整个订单表的数据 全部查出来,然后 一股脑插入 到数据仓库! ![]() 这种方法叫 全量同步,不管数据有没有变化,每次都把所有数据重新复制一遍,简单粗暴。
第一天,公司有 1 万条订单数据。你的程序跑了 3 个小时,同步成功! 老板看着数据夸到:不错啊小阿巴,开发神速啊! ![]() 你内心暗爽:高端的功能往往只需要最简单的代码。 ![]() 基础告警第二天一早,你还没到公司,就收到了运营小姐姐的夺命连环 call:“小阿巴!出大事了!昨天晚上的数据同步失败了,现在老板还没看到昨天的数据,他正在会议室发火呢!” ![]() 你意识到:现在的程序是不可靠的,如果因为网络等原因同步失败了,可能要到第二天才会发现。 于是你给程序加了一段逻辑:如果同步失败,会记录错误日志,同时发邮件通知管理员,并且把数据库回滚到同步前的状态。 ![]() 这样如果出了问题,你会比老板先发现,手动重新执行一遍同步任务就好。 增量同步过了几天,老板黑着脸找你了:“小阿巴,为什么今天数据还没同步完?” 你看了下订单数据,立刻发现了问题。现在有 10 万条数据,程序跑了 30 个小时还没结束;这样下去,如果有 100 万条数据,估计要 300 个小时! 老板:少废话,快点解决,不然送你到隔壁餐饮部沉淀沉淀。 ![]() 你开始思考:既然全量同步太慢,那我只同步每天新增的订单不就行了? ![]() 这就是 增量同步,约定每天 0 点执行同步任务,只同步昨天 0 点之后创建的订单。 ![]() 这样大大减少了每次同步的数据量,任务的执行时间不再线性增加了。 但很快,你发了一个问题,订单的状态是会发生变化的,比如用户付款后又退款。 如果使用订单创建时间作为增量同步的分界标志,只能同步新增的订单,但是订单状态的更新不会同步! ![]() 于是,聪明的你使用 updated_time 字段进行增量同步,这个字段会在每次数据变化时自动更新。 ![]() 这样新增和修改的数据都能同步了! 你内心窃喜:聪明如我小阿巴,老板夸我好开发。 ![]() 批处理过了几天,公司搞了一波大促活动,当天订单量比平时多了几倍,老板请大家通宵狂欢开 party。 结果正在你欢唱 “只因你太美” 的时候,突然公司的运维大叫:不好了不好了,我们的项目服务器卡死了,用户无法下单! 你看了看时间,0 点多,不正是数据同步任务的执行时间么?你瞬间汗流浃背,放下麦克风,赶回公司修 Bug。 原来是因为一次查询出来的订单数据太多了,都加载到内存,导致服务器 OOM 内存溢出了。 ![]() 你悔不当初:唉,早该想到这个问题,既然单次处理数据量太大有风险,那我就 分批处理。 每 100 条数据为一批,每次只从数据库中分页查询出这一批数据,同步完这一批,再执行下一批。 ![]() 这样每次只处理少量数据,内存压力小;而且如果某一批处理失败,只需要回滚和重新同步这一批,而不是全部重来。 问题算是解决了,但是今夜你彻夜难眠,你似乎成了公司业务增长后,唯一不开心的那个人。 ![]() 希望今后不会再遇到这种闹心事了吧。 游标机制但生活总是这样,屋漏偏逢连夜雨。两天后,你又收到了老板的咆哮:“狗阿巴,这就是你做的数据同步?你自己看看丢了多少数据!” 你大惊,丢失数据?不应该啊。。。 经过仔细排查,你发现了问题:如果在分页查询的过程中,有新的数据被插入或更新,就会导致数据偏移和丢失! 比如查询第 1 页时,符合条件的订单有 4 条:
执行第 1 页查询,每页 2 条,偏移量为 0:
查询结果返回订单 A(08:00)和订单 B(09:00),同步完成后将偏移量调整为下一页 可就在查询下一页前,订单 B 因为 “修改订单状态” 被更新,这时订单 B 的更新时间就不在查询范围内了。 这就导致查询下一页 ![]() 怎么解决这个问题呢? 这可难不倒你小阿巴,既然 动态数据集 中使用 SQL 自带的 OFFSET 偏移作为分页起点会出问题,那不妨 自定义一个标志来记录下一批要同步的起点。 这就是游标机制。 比如我约定自增的主键 id 作为游标,每查询一批数据之后,把这批数据的最后一条记录作为新的游标值;查询下一批数据时,只查询 id > 游标的数据。 ![]() 这样不仅防止数据丢失,还避免了 OFFSET 深度分页带来的性能问题。 此外,如果把游标想象成进度条的断点,可以更清晰地记录同步进度,失败后可以从断点继续。 做完这一通优化后,你长吁了一口气,工作看来是保住了。 ![]() 性能优化你以为终于可以安稳一段时间了。但没想到,公司的好日子才刚刚开始。 随着老板大力扩张业务,公司每天的订单量可以达到百万条,你的同步任务每次要跑几个小时才能完成。 更要命的是,老板现在对数据的依赖越来越强,要求每隔 2 个小时就要同步一次数据! 怎么能够让任务执行更快呢? 这时,你想起了曾经在 程序员面试神器 - 面试鸭 上背过的各种性能优化八股文。好家伙,终于能派上用场了! ![]() 首先,修改插入数据库的操作方式。之前是一条一条地插入订单数据,改为执行一条批处理语句来同时插入同一批内多个订单数据,减少了跟数据库通信的次数,性能大幅提升。 ![]() 但是你觉得还不够快,于是优化了批处理的流程。之前是等一批同步完再同步下一批,串行执行;现在你启动了多个线程,每个线程负责处理一批订单的同步,多个线程可以同时搬砖干活,效率大幅提高。 ![]() 这一通操作下来,老板都激动了:“小阿巴,你这技术可以啊!好好干,我给你升职加薪!” 你笑了,有了老板这句话,你想继续努力给公司卖命了。 ![]() 实时同步两年后,随着你头发的消逝,公司总部决定要上市了! ![]() 老板找到了你:“阿巴阿巴,咱们的数据同步能不能做到实时?我想给投资人展示实时的业务监控。最好是用户刚下单,投资人立刻就能在大屏幕上看到!" ![]() 你早料到会有这个需求,帅气地甩出一句话:“交给我吧,我让你见识一下企业级方案!” ![]() 这两年你也没闲着,像实时数据分析这种典型需求的实现方案,你早已烂熟于心。 普通的定时任务已经无法满足实时性的要求,只能搬出 CDC + 消息队列 这两大杀器了。 CDC(Change Data Capture)就像给数据库安装了一个 24 小时实时监控的摄像头,数据有任何变化,摄像头都能立刻发现。 消息队列就像一个快递中转站。数据库变化的消息先发送到这个中转站,然后同步数据的程序从中转站取出数据并写入到数据分析仓库中。 ![]() 有了中转站后,如果消息特别多,程序来不及处理,也不会丢失数据。 实现了这套方案之后,用户只要一下单,100 毫秒内老板就能在仪表盘上看到。而且保险起见,你还给消息队列加了个监控,如果消息堆积太多,就会自动告警。 他开心得像个孩子:“小阿巴,我给你升职加薪,还给你带新人!” 你又笑了,开始幻想着你和新人坐在高高的办公桌上,你给他讲述自己光辉事迹,他向你投来羡慕的目光。 ![]() 方案完善半个月后,公司迎来了双十一大促活动。 你正在和新来的实习生阿坤吹牛皮,没想到瞬间产生了大量订单,像洪水一般,让你的实时同步系统出现了各种异常:
![]() 监控大屏上各种告警此起彼伏,老板的脸色越来越难看:“狗阿巴,这就是你说的企业级方案?” 这一刻,你有点恍惚:“我只是知道可以这么实现,但没人告诉我出了这些问题怎么解决。。。” 这时,你身旁的实习生阿坤说话了:“我来!” ![]() 只见他对着电脑一通操作,嘴里振振有词: 1)消息重复问题,可以通过幂等机制解决。给每条消息一个唯一编号,处理过的消息编号记录下来,就不会重复处理了。 ![]() 2)消息乱序问题,可以通过分区解决。把相关的消息放到同一个分区,保证同一个订单的消息按顺序处理。 ![]() 3)消息堆积问题,可以通过搭建集群和动态扩容,增加对消息的处理能力。 ![]() “这些点在使用消息队列来实现数据同步的时候就要考虑到才对呀!” 你听着阿坤说的话,默默低下了头,内心五味杂陈,这就是应届生的水平么? 阿坤看着你的代码接着说:“不是哥,做个数据同步要这么麻烦么?DataX、Canal、Debezium,这么多现成的企业级数据同步工具你不用?非要自己写代码?” ![]() 你无言以对,内心感受到了来自实习生的一吨暴击。 阿坤甚至一脸嫌弃地看着你:“不是哥们,连数据对账都不做么?你自己都不定期检查同步前后的数据是否一致么?我这有份数据同步的企业级方案,你好好看看吧。” ![]() ![]() 老板拍了拍阿坤的肩膀:“小伙子优秀,导师的导师,今后公司的未来就靠你了,我给你升职加薪。至于老阿巴,你好自为之吧。” 这个城市,又多了一个伤心的人。 阅读原文:原文链接 该文章在 2025/10/11 10:14:44 编辑过 |
关键字查询
相关文章
正在查询... |