当前位置: 移动技术网 > IT编程>数据库>Redis > 聊聊数据同步

聊聊数据同步

2020年07月23日  | 移动技术网IT编程  | 我要评论

一、简述

        数据同步,这是一个很宽泛的概念,在互联网或者传统软件公司,一定会遇到数据同步的场景。数据同步一般会遇到的问题诸如同步时延、数据一致性、性能低、强依赖于中间件、失败后无法补偿等。本文笔者试图简要总结下常见的数据同步场景,并对其中一种遇到的场景给一个案例分享。这个案例其实是所有数据同步场景中最简单的一种情况,但是依然走了很多坑,所以记录一下。对于其他中间件比如db/redis/mq的数据同步,底层实现更加优雅,涉及到通信协议和操作系统相关的知识,且每一种都不太一样,后期会再专门总结。

二、为什么要做数据同步?

1、业务需要

       比如说你和第三方系统对接,对方只提供一个数据库视图给你,对于数据的变动,没有所谓本地的触发器或者binlog日志通知你数据变更了,人家也不想开发一个通知机制,只是丢给你一个视图,你自己从视图中拉取所有数据到本地库,做自己的业务逻辑判断;在一个大型分布式系统中,部分核心数据需要做全量拷贝,后面做数据分析和挖掘用户行为,但又不想直接基于你的业务库,因为会影响业务性能;再或者你和你们公司另一个业务部门,有些数据需要从他们库中同步到你本地来;还有更常见的,数据库和缓存的双写一致性等等。

2、高可用

       所谓高可用,有人发明出所谓n个9,也就是说在99.9%还是99.99%的时间里,系统能正常对外提供服务,这对于大型互联网公司,一般要求是至少4个9,如果动不动系统不可用,对公司的损失少则几十万,多则上千万的损失,所以,在超大型分布式架构中,必须做高可用。广义的高可用,总结起来,方案主要有多副本、隔离、限流、熔断、降级、灰度发布与回滚、监控和日志等。其中多副本是很多中间件主从架构的必备方案。

可用性指标 计算方式 不可用时间(分钟)
99.9% 0.1%*365*24*60 525.6
99.99% 0.01%*365*24*60 52.56
99.999% 0.001%*365*24*60 5.256

      对于数据库,做主从提升数据库并发读写性能,分库分表和相关数据库中间件必不可少,你就必须考虑数据库主从库之间数据同步的细节以及相关问题,比如主从时延以及性能,主从数据一致性内部是如何保证的等等;

       对于分布式缓存,如redis,单机redis虽然号称能抗6-10万并发,但是存在单点故障,虽然你可以开启rdb和aof的混合同步快速恢复数据,但是相比于fail over还是逊色不少,单点故障还是会让你整个系统短时间不可用。所以你必须要做主从架构来减轻高并发压力并提升性能的,数据量大的时候,也需要考虑水平扩容,做数据分片存储,每个分片再做主从,又会涉及到很多数据同步的场景;

        对于MQ,也需要保证MQ集群中单点问题导致的系统不可用问题,不论是kafk这种分布式的还是RabbitMQ的镜像集群模式,集群中主从同步的相关问题依然是你必须要考虑到的;

 

3、数据灾备

        假如说你的系统是你公司的核心业务系统,甚至涉及到资金相关的。你没做数据容灾?天啊,太可怕了。数据一旦丢失,不就全完了?你得有n份的数据备份,在主库有宕机时,做主备切换,BAT等大型互联网公司的数据中心,是异地多活的,保证数据不丢,这里面,每时每刻都在发生着数据同步;

 

三、怎么做数据同步?

       千万不要企图寻找一种完全通用的数据同步方案,因为任何方案和技术,都是要根据不同的业务场景和不同的技术去分别讨论的。理论上来说,数据同步很难做到绝对的强一致性,一般情况下都是最终一致性,即允许中间过程短暂的不一致。常见的中间件的数据同步,其中间件本身都有相关的配置参数和对应的架构方案来保证数据同步的数据一致性和尽量低时延。

先给出几种数据库同步的方案:

方案1:分页查询批量插入

适合数量较少,且不需要时时刻刻一致性。实现简单,一般不用;

方案2:触发器增量复制

基于数据库的触发器,当有数据增删改时,将数据放入增量表,然后处理增量数据。比较依赖触发器,假如触发器触发时出现网络问题等情况,会出现少量数据不一致情况,关键是下一次也无法补偿回来。

方案3:基于mysql的binlog日志

可以伪装成一个从库,让mysql把数据同步到虚拟的从库中,不做详细介绍,具体参考阿里的canal中间件,就是基于此原理;

方案4:大数据方案

       现在都是大数据时代了,可以基于BulkLoad的数据同步,比如从hive同步数据到hbase;也可以基于sqoop的全量导入;也可以在HBase中建表,然后Hive中建一个外部表,这样当Hive中写入数据后,HBase中也会同时更新。当然,类似于数据同步这种如此常见的场景,肯定有人已经为你造好了轮子,比如说Debezium+bireme:Debezium for PostgreSQL to Kafka Debezium是一个通过监控数据库的日志变化,通过对行级日志的处理来达到数据同步,而且Debezium 可以通过把数据放入到kafka,这样就可以通过消费kafka的数据来达到数据同步的目的。而且还可以给多个地方进行消费使用。这样,你的数据就可以同步到很多地方去啦!具体参见其github:https://github.com/debezium/debezium 

          然鹅!

          以上牛逼的方案和不牛逼的方案,都不是我们项目的方案,已哭晕在厕所......

         以后有时间会再总结一下数据库及其相关中间件(mycat、Sharding-jdbc)、redis及其代理中间件(如codis)、MQ集群自身的主从同步原理做深入探讨。本文只结合一个实际案例,来总结一下做过的一个数据同步的几点优化。

四、案例分享

1、场景

       我们公司一个项目,需要和第三方的人员进行同步,第三方给了一个他们人员库的视图和数据库连接信息,我们需要连接他们的数据库将人员拉取到我们系统中。且每次都需要找出哪些人被删除了、哪些人需要新增、哪些人需要修改。即需要将第三方库与我们本地库的数据做比对,来保证数据的最终一致性。

        基于某种原因,我不能直接连接我们本地人员库,而只能通过别人暴露出来的CRUD接口来对本地人员库做变更。坑爹啊!先给出最开始的一种比较坑爹的一种方案,复杂而不实用。

方案1:

          本地三张表

                tb_person_recent:最新人员表 -> 存储当前第三方库的所有人员

                tb_person_last:上次人员表 -> 存储上一次人员同步时,第三方库的所有人员

                tb_person_increment:人员增量表 -> 通过存储过程将recent和last表进行逐行全量比对,找出需要增删改三种状态的人员,放入人员增量表。

    整体流程图如下图所示:

         很显然,这种方案最坑爹的地方在于说,人员张三一旦同步失败了,那么如果不进行人为干涉,就永远没机会再添加进来了。

         对于这种方案,我最开始开发时就预感到这种问题,但是项目时间紧张、方案本身也不是我自己设计,将就用了,就自己在人员同步组件中去做补偿。一旦一个人添加失败了,就放入失败表中,下一次人员同步任务时,先处理失败表中的数据,再处理增量表的数据。但是,这种设计,为自己后面埋了很大的坑。这个错误表,会给下一次同步任务造成很多问题,读者可以自己假设多种场景,看看每个场景下会遇到什么问题。

        于是乎,经常遇到人员没同步过来,数据不一致,少了一些人甚至很多人。初期的时候,由于第三方库的连接经常有问题,导致可能数据只拉取了一部分,然后比对的时候,只有一部分人员参与了存储过程的比对。再比如说连接失效了,拉取数据失败了。再比如说人员拉取的自动任务是单线程的,遇到过自动任务没有执行的问题。几乎每周都会遇到人员不一致的情况,头大。

        头大没用,还是要硬着头皮看下问题到底出在哪,由于日志不是很充足,不太容易定位出问题出在存储过程比对还是数据拉取还是数据增删改。所以干脆不优化了,直接放弃这种方案。接下来,就简单分析下,新方案的思路历程。       

        你想啊,上面那个同步方案的最大问题就是这一次同步失败了的人员,下一次居然没办法弥补,必须强行人工来干预。那我们能不能设计出一种方案,每次自动任务都是做全量同步,也就是说,我每次自动任务,把第三方的数据全部拉过来放在内存的一个map1中,我们人员管理服务的数据全部拉过来也放在内存的map2中,然后map1和map2直接代码做比对。

以下是新方案的一个总思路:去存储过程、去last和recent表、内存比对、多线程拉取

方案2

1、先查询第三方视图库的数据总页数n(每页1000条)
2、从线程池中开n个线程任务用于并行执行第1到第n页的数据,每个线程查其中一页
3、将这n个线程拉取的n页的数据汇总(CountDownLatch),放在map中
4、同理,人员管理服务这边也先查有m页,从线程池中开启m个线程任务拉取放在map中
5、两边map比对,找出增删改的数据,分别处理
    第三方map中有,人员管理的map没有--------------------> 要新增
    第三方map中没有,人员管理的map中有-----------------> 要删除
    第三方map和人员管理的map中都有-----------------------> 要更新
6、将第5步中的3种状态的数据添加到增量表中,后续的处理复用以前的代码;

这样做要考虑两个问题:

      1、使用CountDownLatch来协调多个线程,要注意线程创建的数量和上下文切换,线程池参数要调节好;

      2、两边的数据放在内存,jvm和gc参数要适当调整,如果数据量足够大,还是放在redis或者数据库中再比对。

      3、拉取数据的过程要保证全量,一旦有一个线程抛异常,整个任务需要return退出,否则容易出现第三发的人员没拉全,而没拉到的那些人误认为是要删除的,结果全删了。

 

五、总结

          这个方案的主要思路是全量比对,每次都是走全量的比对,能保证我这一次同步失败的人员,下一次只要你程序比对没有逻辑问题,就一定能在之后的同步任务中弥补回来。为了提升拉取数据的性能,使用了分段拉取,实测2万人也只需要4-6s左右。性能较方案1有数十倍的提升。但是随之而来的问题你也要尤其的注意,比如多线程间的同步问题,有一个线程失败了怎么办?线程长期阻塞了怎么办?线程池参数等等问题都要考虑到。如果不在乎性能,笔者建议采用单线程分页拉取所有数据可能更好。

         其次,将存储过程的比对逻辑,写入到代码中去,这样能更好的定制化个性化的同步需求,比如说有部分人我想做过滤,做特殊的校验,存储过程实现起来就不够灵活,其带来的性能提升,远远小于它引申出来的一系列难以排查的问题,得不偿失。我想,这也是阿里巴巴不建议使用存储过程和外键的原因了吧。

         整个方案其实并不复杂,实际业务中,比对的逻辑也不像图中那么简单,做了一些过滤和校验等操作。但大体上,这种方案很适合我们这个项目的场景,当然,肯定也不是最优方案。主要是因为前期的方案,排查问题花了很多时间,很多又是偶现,日志又不充分,排查又需要远程,很多代码又不是自己写的,整个排查过程让人很心累,索性重写了整个逻辑。并反复的测试了其可行性。不过,也要运行至少三个月才能知道这种方案有没有问题了。欢迎大家指出其中的问题,或者给出更加优雅的方案来适配我们项目中的这种人员同步场景,散会,下次见!
  

本文地址:https://blog.csdn.net/applehub/article/details/107482836

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网