论一数据同步方案
来源:互联网 发布:希腊神话 知乎 编辑:程序博客网 时间:2024/06/05 15:45
前段时间重做了一个数据同步方面的工作,具体是把相关数据从一个远端复制到本地端,需要做到不重、不漏、无误的同时保持使用简单方便。
背景:
我们跟第三方合作,可以读取使用第三方的数据源,但不能修改,而第三方的数据源没有我们想要的索引和主键约束,所以想要更方便地使用第三方数据必须把数据同步传输到本地,根据自己的需求加索引加主键约束等等。第三方数据库中的数据会增加,修改,但不被会删除。
原来的方案:
根据数据源的特点,把我们需要的字段同步过来,根据数据特性加上主键约束来确保数据的唯一性,更新操作通过主键来做对应。
每次同步都只取前一天更新的源数据进行更新操作。
遇到的问题:
- 经过不断地踩坑后发现,原来第三方的数据源不止是会增加,还会进行修改,不止是普通字段的修改,我们自建的主键中的值也会修改,虽然发生的频率很小,由于数据特性,不会发生主键冲突,但主键都改了,两份数据就没法对应了,这就导致了会多出一些“错误”数据。
- 利用cron做的定时任务,一旦某次数据冲突或者发生硬件故障没有更新,那么在解决了故障之后必须手动的传入参数把前N天的数据都做一遍更新。
- 原来的方案数据是一条一条的查找的,效率十分低下。
所以很有必要重新做一个同步方案。
新的尝试
经过观察,第三方数据库中每一张表都有且仅有一个唯一主键的id,此外还有数据更新时间op_date。所以我们只能把唯一主键也同步过来。
方案1
- 借鉴以前的老方案,根据传入的参数来取相应的数据。传入的参数中有一个参数指定同步N天以前直到当前最新一天期间发生变更的数据。源数据是一次性取出来的,本地数据库中的数据也是一次性取出来,然后做对应。
这种方案,数据正确性可以保证,但是其他方面还是不尽如人意。而且,假如说某一个天的数据量特别巨大的话,会不会发生内存不够?既然选择重做,那就做好,把一切有可能发生的问题都避免掉。所以可以由一次性同步改为分批次同步。这是一个小的优化点。
其实也还有其他的问题,假如更新了一般发生了意外停止了更新而又没有被监控到怎么办?
方案2
这个时候老大看了我的设计方案,我们思索讨论了一下,有了一个新的思路。
这里引入了两个新名词:全量更新和差量更新。
全量更新是从零开始全部更新,差量更新是从上次更新的地方接着更新。差量更新的好处在于,及时上次因为某种意外断掉了,也不会影响下一次更新,并且下一次更新会把上一次未更新的数据也更新了。
最后成型的具体方案是:
- 获取local库相应表中最新的数据,以op_date和id倒序排列。记最新数据的op_date和id分别为l_op_date, l_id。
- 获取origin库相应表中,op_date 等于 l_op_date的数据进行同步,一次最多取max_count个,直到取完。
- 获取origin库响应表中,op_date 大于 l_op_date、id 大于 l_id的数据,以op_date和id倒序排列,最多取max_count个,同步更新并记 l_op_date、l_id为最新条数据相应的op_date和l_id。
- 重复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行就可以完成一个新的传输类。具体代码就不展示了。
- 论一数据同步方案
- 数据同步处理方案
- oracle 同步数据方案
- “游戏数据同步方案”
- 数据同步方案
- windows mobile数据同步方案
- Oracle的数据同步方案
- 数据同步流程方案记录
- 游戏服务器 数据同步方案
- Windows Mobile数据同步项目方案
- 关于异地数据同步的方案
- 浅谈Oracle 数据库之间数据同步方案
- Oracle 提供的数据同步方案
- 基于SOA的数据同步方案
- kettle数据同步的五种方案
- 一种客户端同步server数据的方案
- HBase数据同步到ElasticSearch的方案
- kettle数据同步的5中方案
- 收藏的一些网站
- 关于薪水保险金的那些事
- Android的五种数据储存
- android 设置控件焦点
- 小P的故事——神奇的Dota (动态规划)
- 论一数据同步方案
- Hibernate -- 映射实体关联关系(一对多关联关系)
- git reset的三种模式soft mixed hard
- Java基础知识(二)
- 2017年人生计划
- PAT 1014 福尔摩斯的约会(二)
- Maven本地环境搭建
- Android Receiver(听筒模式和扬声器模式,铃音模式)
- PHP经典面试题集锦