论一数据同步方案

来源:互联网 发布:希腊神话 知乎 编辑:程序博客网 时间:2024/06/05 15:45

前段时间重做了一个数据同步方面的工作,具体是把相关数据从一个远端复制到本地端,需要做到不重、不漏、无误的同时保持使用简单方便。

背景:

我们跟第三方合作,可以读取使用第三方的数据源,但不能修改,而第三方的数据源没有我们想要的索引和主键约束,所以想要更方便地使用第三方数据必须把数据同步传输到本地,根据自己的需求加索引加主键约束等等。第三方数据库中的数据会增加,修改,但不被会删除。

原来的方案:

根据数据源的特点,把我们需要的字段同步过来,根据数据特性加上主键约束来确保数据的唯一性,更新操作通过主键来做对应。
每次同步都只取前一天更新的源数据进行更新操作。

遇到的问题:

  1. 经过不断地踩坑后发现,原来第三方的数据源不止是会增加,还会进行修改,不止是普通字段的修改,我们自建的主键中的值也会修改,虽然发生的频率很小,由于数据特性,不会发生主键冲突,但主键都改了,两份数据就没法对应了,这就导致了会多出一些“错误”数据。
  2. 利用cron做的定时任务,一旦某次数据冲突或者发生硬件故障没有更新,那么在解决了故障之后必须手动的传入参数把前N天的数据都做一遍更新。
  3. 原来的方案数据是一条一条的查找的,效率十分低下。

所以很有必要重新做一个同步方案。

新的尝试

经过观察,第三方数据库中每一张表都有且仅有一个唯一主键的id,此外还有数据更新时间op_date。所以我们只能把唯一主键也同步过来。

方案1

  • 借鉴以前的老方案,根据传入的参数来取相应的数据。传入的参数中有一个参数指定同步N天以前直到当前最新一天期间发生变更的数据。源数据是一次性取出来的,本地数据库中的数据也是一次性取出来,然后做对应。

这种方案,数据正确性可以保证,但是其他方面还是不尽如人意。而且,假如说某一个天的数据量特别巨大的话,会不会发生内存不够?既然选择重做,那就做好,把一切有可能发生的问题都避免掉。所以可以由一次性同步改为分批次同步。这是一个小的优化点。

其实也还有其他的问题,假如更新了一般发生了意外停止了更新而又没有被监控到怎么办?

方案2

这个时候老大看了我的设计方案,我们思索讨论了一下,有了一个新的思路。
这里引入了两个新名词:全量更新和差量更新。
全量更新是从零开始全部更新,差量更新是从上次更新的地方接着更新。差量更新的好处在于,及时上次因为某种意外断掉了,也不会影响下一次更新,并且下一次更新会把上一次未更新的数据也更新了。

最后成型的具体方案是:

  1. 获取local库相应表中最新的数据,以op_date和id倒序排列。记最新数据的op_date和id分别为l_op_date, l_id。
  2. 获取origin库相应表中,op_date 等于 l_op_date的数据进行同步,一次最多取max_count个,直到取完。
  3. 获取origin库响应表中,op_date 大于 l_op_date、id 大于 l_id的数据,以op_date和id倒序排列,最多取max_count个,同步更新并记 l_op_date、l_id为最新条数据相应的op_date和l_id。
  4. 重复2-3,直到取完所有数据。

这样的话,每次更新操作都会接着上一次的操作继续执行,并且无论多大的数据量都不会发生内存不够的情况,无论上次同步更新到哪儿了,下一次更新的时候总会接着上一次的更新位置继续执行。

代码大致如下

class BaseTransfer(object):    max_count = 2000    def run():        count = 0        exec_count = 0        for origin_data in self.get_origin_data():            local_data_dict = {x.id: x for x in self.get_local_data(origin_data)}            for item in origin_data:                if item.is_broken():                    continue                # 特殊的检测                if not self.special_check(item):                    continue                mn = mass_dict.get(item.id)                if mn:                    # 更新                    if self.update(item, mn):                        count += 1                else:                    # 插入                    self.add(item)                    count += 1                if count - exec_count >= self.max_count:                    exec_count = count                    db.session.commit()        db.session.commit()    def get_origin_data(self):        # 获取op_date和wind_id        local_data = self.get_lastest_record()        # 第一次导入全部数据的时候需要考虑没有数据的情况        if not local_data:            l_op_date = datetime(1980, 1, 1)            l_id = ''        else:            l_op_date = local_data.op_date            l_id = local_data.id        while True:            # 先做op_date等量查询            while True:                origin_data = 等量查询                if not origin_data:                    break                l_id = origin_data[-1].id                yield origin_data            # 大于当前op_date            origin_data = origin_model.query.filter(                origin_model.op_date > l_op_date            ).order_by(                origin_model.op_date,                origin_model.id_            ).limit(self.max_count).all()            if not origin_data:                break            l_op_date = origin_data[-1].op_date            l_id = origin_data[-1].id            yield origin_data            if len(origin_data) < self.max_count:                break

当然,其中做了很多的优化,比如说可重用性,扩展性等等。想要做多个表的同步更新,每个表传输类只需要继承自BaseTransfer,然后实现自己独有的一些特殊方法就可以了,只需区区5,6行就可以完成一个新的传输类。具体代码就不展示了。

0 0
原创粉丝点击