分库分表带来的完整性和一致性问题_分库分表带来的后果-天下标王

分库分表带来的完整性和一致性问题

15 篇文章 0 订阅

分库分表带来的完整性和一致性问题

在最近做的一个项目中,由于每天核算的数据量过于庞大,需要把数据库进行分库保存。当数据分散到各个库之后,带来的数据更新操作就会存在一个一致性和完整性的问题。下面是一个典型的场景

假设目前存在三个物理库,现在有一个文件,里面有1W条数据,根据分库的规则,可以把文件里面的数据分到三个库中,现在需要保证这1W条数据要要完整的保存到这三个库里面,并且数据是一致性的,也就是说 三个库里面已导入的数据完全和文件里面的数据一致。

正常情况下,我们先把文件里面的数据按照所属的数据库分成三份,然后针对每一份数据库进行保存,在单库的情况下,可以保证单库的数据完整性。但是三个库要保证一致性,就是非常复杂的一项工作,很有可能第一个库的数据保存成功了,但是后面三个库的数据保存失败了,导致整个文件的里面的数据在数据库里面不完整。

如何解决这种问题,目前想到的有几个办法:

方案1

 使用类似JTA提供的分布式事物机制,也就是说需要相关的数据库提供支持XA的驱动。( XA 是指由 X/Open 组织提出的分布式交易处理的规范)。这个需要依赖特定的数据库厂商,也是比较简单的方案。毕竟复杂的事务管理都可以通过提供JTA服务的厂商和提供XA驱动的数据库厂商来完成。目前大多数实现了JTA的服务器厂商比较多,比如JBOSS,或者开源的JOTM(Java Open Transaction Manager)——ObjectWeb的一个开源JTA实现。但是引入支持XA的数据库驱动会带来很多潜在的问题,在 《java事务设计策略》里面:在Java事务管理中,常常令人困惑的一个问题是什么时候应该使用XA,什么时候不应使用XA。由于大多数商业应用服务器执行单阶段提交(one-phase commit)操作,性能下降并非一个值得考虑的问题。然而,非必要性的在您的应用中引入XA数据库驱动,会导致不可预料的后果与错误,特别是在使用本地事务模型(Local Transaction Model)时。因此,一般来说在您不需要XA的时候,应该尽量避免使用它。”  所以这个是一个可选的方案,也是最简单的一个方案

 

方案2

建立一张文件批次表(放在一个独立的数据库里面),保存待处理的文件批次信息(不是明细数据,简单说的就是要处理的文件名和所在路径),在每次处理文件数据的时候,先往表里面插入一条文件批次信息,并且设置文件的状态为初始状态,在文件中的数据全部成功的保存到三个分库里面之后,在更新文件的批次状态为成功。如果保存到分库的过程中出现异常,文件批次的状态还是初始状态。而后台启动一个定时机制,定时去扫描文件批次状态,如果发现是初始状态,就重新执行文件的导入操作,直到文件完全导入成功。这个方案看起来没有问题,但是可能存在重复导入的情况,比如批次导入到第一个分库成功了,后面两个库失败了,重新导入的话,可能会重复把数据重复导入第一个分库。我们可以在导入之间进行判断,如果导入过,就不进行导入,但是极端的情况,我们无法判断数据是否导入过,也是一个有缺陷的方案,并且如果每次导入之前,都进行数据是否导入的操作,性能会有一些影响。我们也可以通过异常恢复机制来进行,如果发现文件导入失败了,我们删除已经导入入库的流水,但是这也引入了错误处理带来的一致性问题,比如我们已经导入成功2个分库的数据,在导入第三个分库失败的情况下,要删除掉前面两个分库的数据,这也没有办法保证是一致的。

在这个方案里面,我们可以在进行一定的优化,让它看起来运作起来是没有问题的。首先再建立一张子批次表(和文件批次表放在同一个库),在进行处理的时候,我们把大的文件的数据按照分库规则拆成三个子文件,每一个子文件里面的数据对应一个分库。这样就产生三条子批次信息,由于文件批次信息和子批次信息 在同一个库里面,可以保证一致性。这样每个待处理的文件就分成了四条记录,一条主文件批次信息,三条子批次信息,在导入数据之前,这些批次的信息的状态都是初始状态。这样一个文件的导入就分解为三个子文件,分别导入到对应库里面去。对于每个子文件批次,我们可以保证子文件数据的都是在同一个库里面,保证每个子文件里面数据的一致性和完整性,然后导入成功之后,在更新子批次的状态为成功,如果所有的子文件的批次状态都为成功,那么对应的文件批次状态就更新为成功。这样看起来非常完美,解决了问题。但是仔细考虑一下,有一个小的细节问题:子批次信息和一个独立的库,要导入的数据是和子批次信息可能不再一个库,没有办法保证这两个操作是一致性的,也就是说 子文件里面的数据成功的导入到分库,但是可能子批次信息状态没有更新。那子批次信息能不能放在每个分库里面了,这样的话,又回到刚开始提出的问题了(这里面就不解释,可以去自己去想想)。

下面一副图简单的演示的设计思想:

 

 

方案3

第2个方案的基础上,可以继续加以优化。首先我们保留第二个方案的文件批次信息表和子文件批次信息表,而且我们必须把这两个表放在同一个库里面(这里假设分配到主库),保证我们拆分任务时的一致性。然后在各个分库里面,我们建立一张各个分库的子文件批次表。这个表模型基本上是和主库的子文件批次信息表一样。当拆分任务的时候,先保证主库数据的完整性,也就是产生了一条文件批次信息记录和三条子文件批次记录,然后把这三条子文件批次信息分别复制到对应的分库中的子文件批次信息表里面,然后更新主库的子文件批次信息状态为“已同步”。当然,这个过程是无法保证一致性的。解决方案启动一个定时任务,定期的把主库重点的子文件批次表信息中初始状态的记录 同步到各个分库的子文件批次表里面,这里面可能导致两种情况

1 分库子批次信息表已经存在相同的信息(这个可以通过唯一性主键保证),说明已经同步,直接更新主库的子文件批次信息状态为 “已经同步”

2 分库子批次信息不存在,则往对应的分库insert一条数据,然后更新主库的子文件批次信息状态为 “已经同步”

然后各个分库 就是先导入子文件中的数据,在更新分库的子文件批次表的状态为处理成功 ,这两个操作由于都是分库的上的操作,可以保证一致性。最后,在更新主库的子批次信息表的状态为 “处理成功”。同样,更新主库的子批次信息状态如果失败,可以采取类似的定时机制,同步分库子文件批次信息表和主库的子文件批次信息表的状态。通过这种努力重试型机制,保证了主库中的子文件批次表和分库的子文件批次表是一致的。等所有的主库子文件批次信息表状态全部更新为“处理成功”,则文件批次状态就更新为“处理成功”。

相比第二个方案,我们在两个库里面增加了数据的同步,用这种机制,保证了主库分库数据的一致性。

这里简单的介绍一下第二个方案的简单实现细节:

首先是数据库之间表结构关联关系

 

 下面用脚本的方式简单的演示一下这个过程

我们假设有四个库,一个主库MAIN,三个字库SUB1,SUB2,SUB3

MAIN库两张表:

FILE_BATCH_NO,主要关注status状态 I(初始)->S(成功)

SUB_BATCH_NO,主要关注status状态 I(初始)->R(同步成功)->S(处理成功)

SUB库两张表

DATA_DEAIL:保存明细数据,也就是业务逻辑主要处理的表

SUB_BATCH_NO:主要关注status状态,I(初始)->S(处理成功)

 1 拆分文件批次的过程

begin
 
declare file_name,batch_no,sub_batch_no;
 
insert into MAIN.FILE_BATCH_INFO(id,file_name,batch_no,status) values (seq.FILE_BATCH_INFO,#file_name#,#batch_no#, 'I' );
 
insert into MAIN.SUB_BATCH_INFO(id,file_name,main_batch_no,status) values (seq.SUB_BATCH_INFO,#file_name#,#batch_no#,#sub_batch_no#, 'I' );
insert into MAIN.SUB_BATCH_INFO(id,file_name,main_batch_no,status) values (seq.SUB_BATCH_INFO,#file_name#,#batch_no#,#sub_batch_no#, 'I' );
insert into MAIN.SUB_BATCH_INFO(id,file_name,main_batch_no,status) values (seq.SUB_BATCH_INFO,#file_name#,#batch_no#,#sub_batch_no#, 'I' );
 
commit ;
 
end ;

 

2 同步MAIN库的子批次信息到分库的各个SUB库中对应的子批次信息表,同步成功,更新MAIN库对应的子批次信息状态为同步成功。

##分库的操作,从MAIN库SUB_BATCH_INFO表中获取对应的数据插入到SUB1库里面
begin transaction in SUB1
declare file_name,batch_no,sub_batch_no;
 
select SUB_BATCH_INFO.ID into SUB_ID from  MAIN.SUB_BATCH_INFO where SUB_BATCH_INFO.DATA_BASE = SUB1
//判断分库数据是否存在,存在就返回 true
if( select * from SUB1.SUB_BATCH_INFO where SUB_ID = SUB_BATCH_INFO.ID)
   return SUCCESS
insert into SUB1.SUB_BATCH_INFO(id,file_name,main_batch_no,status) values (SUB_ID,#file_name#,#batch_no#,#sub_batch_no#, 'I' );
commit ;
 
end ;
 
 
##SUB1库的操作完成之后,开始进行MAIN库SUB_BATCH_INFO表对应的 update 操作
begin transaction in MAIN
 
declare SUB_ID;
## R代表已经同步的状态,这里面可以判断status的状态,不过意义不大
update  MAIN.SUB_BATCH_INFO set status = 'R' where ID = SUB_ID
 
commit ;
end ;

 上面只是一个SUB库的操作,如果有多个库,循环进行操作。如果某一个库没有同步成功,有定时恢复机制。定时恢复机制的对应的SQL就是从MAIN中提取出是状态的SUB_BATCH_INFO记录,重复进行上述处理的过程

 

3 SUB库处理子批次信息,对流水进行保存,然后更新SUB库对应的SUB_BATCH_INFO记录状态为处理成功。然后在更新MAIN库的对应的SUB_BATCH_INFO记录状态为成功。

 

##分库的流水操作
begin transaction in SUB1
declare file_name,batch_no,sub_batch_no;
 
select SUB_BATCH_INFO.status into SUB_ID from  MAIN.SUB_BATCH_INFO where SUB_BATCH_INFO.DATA_BASE = SUB1
//判断状态是否是初始
 
if status == 'I'
    insert into SUB1.DATA_DETAIL
    update SUB1.SUB_BATCH_INFO.status = 'S'
end if
commit ;
end ;
 
 
##SUB1库的操作完成之后,开始进行MAIN库SUB_BATCH_INFO表对应的 update 操作
begin transaction in MAIN
 
declare SUB_ID;
## R代表已经同步的状态,这里面可以判断status的状态,不过意义不大
update  MAIN.SUB_BATCH_INFO set status = 'S' where ID = SUB_ID
commit ;
end ;

 这里的情况一样,就是SUB库和MAIN库也存在状态同步的问题,这里也需要一个定时对MAIN库的 SUB_BATCH_INFO表状态进行同步更新

 

4 判断MAIN库对应的SUB_BATCH_INFO所有状态是否已经为成功,如果成功,更新MAIN库的FILE_BATCH_NO 的状态为成功。

 

在这四个过程中,需要三个定时器。有两个定时器保证MAIN库和SUB库之间的数据一致性问题,另外一个定时器负责异步更新MAIN库 批次和子批次的一致性问题。

 

对于第三个方案,可以抽取出通用的逻辑,来解决后续类似的场景。比如根据条件,删除各个分库中满足条件的流水,或者批量更新各个分库中满足条件的流水。我们可以把这些作为一个任务来抽象出来,一个具体的任务由N个子任务组成(N为分库的个数),系统要保证N个子任务要么全部成功,要么全部失败,不允许部分成功。我们可以在方案三的思想上,建立总任务表和子任务表,文件导入的处理只是其中的一个任务类型而已,批量删除,批量更新以及其他类似的操作,都可以当做具体的任务类型。

 

4 第四种方案就是经典的分布式事务设计中的 两阶段提交思想。两阶段提交的有三个重要的子操作:准备提交,提交,回滚。

继续拿文件导入来举例子,各个分库作为一个事务参与者 , 我们需要设计各个分库的准备提交操作,提交,回滚操作。

准备提交阶段:各个分库可以把要处理的文件明细保存到一张临时表里面,并且记住这一次事务中上下文信息。

提交阶段:把这一次事务上下文中对应的临时表数据同步到对应的明细表中

回滚阶段:删除本次事务相关的临时表流水信息。

通过设计一个两阶段的提交的事务管理器,我们可以在导入文件的时候启动一个分布式事务,生成一个事务上下文(这个上下文信息要保存到数据库里面),然后在调用各个子参与者的时候,需要把这个上下文信息传递下去,分库先进行准备工作(就是保存明细到临时表),如果成功,就返回准备成功。等所有的参与者成功了,事务管理器就提交这个事务,这个分库完成提交动作,把数据从临时表插入到正式表。如果某一个准备操作失败,所有的分库执行回滚操作,删除导入的流水。

这里面最重要的就是,如果某分库准备阶段返回成功,那么提交一定要成功,否则只能做数据订正或者人工处理了。这个是在两阶段中事务中没有办法解决。

对于不同的操作,要设计对应的准备提交,提交,回滚操作,开发量比较大,而且分布式事务管理器的实现也需要一定的功底。

 

上面四种方案,能够保证完整性和一致性的只有第三种和第四种方案。其实这两种方案的设计思想是一致的。就是通过努力重试以及异步确认进行的。严格的说,第三种方案会有一定的问题,因为在整个处理过程中,只能保证最终一致性,而没有办法保证ACID里面的孤立性。因为存在部分提交的情况,而这一些数据有可能后续会进行回滚。不过可以就第三种方案在进行优化,加上一个锁机制,不过扩展下来就比较复杂了。

博客等级

码龄9年
45
原创
40
点赞
78
收藏
17
粉丝
私信
写文章

分类专栏

解决eclipse闪退的4种方法

biubiu324: 最后一个办法对我有用

  • echarts的主题如何设置

    即将拥有人鱼线的Iverson: var myChart = ec.init(document.getElementById('main'),'macarons');

  • sftp文件上传下载

    唯刀百辟唯心不易: 我在sshSession.connect之后就走不下去了

  • 解决eclipse闪退的4种方法

    东北大Nemo_: makr

  • echarts的主题如何设置

    DW1202: 没有用啊,我能能看看你的文件配置吗

  • 最新文章

    目录

    Java
    31篇
  • hibernate
    13篇
  • struts
  • spring
    13篇
  • springMVC
    2篇
  • mybatis
  • web开发
    6篇
  • mysql
    9篇
  • 开发工具
    4篇
  • activiti
  • 项目经验
    15篇
  • 学习总结
    7篇
  • oracle
  • CSS
    11篇
  • jquery插件
    6篇
  • SVN
    2篇
  • android
    2篇
  • xml
    2篇
  • tomcat
    2篇
  • 网络通信
    5篇
  • 小技巧
    1篇
  • eclipse
    4篇
  • 项目管理
  • javascript
    9篇
  • 分布式集群
    5篇
  • 安卓
    2篇
  • IOS
  • html5
  • 连接池
    2篇
  • nosql
    2篇
  • socket
    1篇
  • maven
    7篇
  • Linux
    6篇
  • solr
    3篇
  • kafka
    1篇
  • jsp
    1篇
  • 为什么被折叠? 到【灌水乐园】发言
    前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值

    相关内容推荐

    淮安seo公司立找2火星seo适合转行卖设备吗搜索引擎优化seo南宁专业的整站优化seo逆冬seo网站塘沽seo优化哪家专业肥西seo优化哪家好php动态网站seo黄石广告seo推广公司徐州seo网络推广哪家好偃师seo优化哪家好黔西南哪家seo优化公司专业seo从零开始学会留痕盐城seo推广服务哪家公司好实时seo排名点击软件seo和sem那个有前途seo代理刻羽云seo搜索引擎优化实训报告分类seo技术教学课程运营昌吉seo 网络推广厂家哪家好海兴关键词seo优化哪家好河北seo关键词排名优化费用seo软件只找15火星软件seo访问页面咸宁seo收费SEO是什么粉底液最好均安seo托管国外的seo靠谱吗衢州专业优化seo价格增城seo技术江苏seo网络推广品牌企业邢台抖音seo搜索优化排名宿迁seo网络推广公司咨询seo培训有哪些好处中山昆仑seo咨询武汉seo关键词推广排名seo黑帽教程下载seo必学的八个技术seo教程seo和口碑营销seo培训价格低峡江seo优化公司湛江seo推广价格费用中山网站建设知名乐云seo搜狗快排和seo泸州seo优化生产厂家大学城放心的seo公司益阳seo优化流程衢州常山县seoseo外链群发平台顺德公司seo优化代理商吴川seo优化多少钱衡水seo优化专业定制seo提高排名 site新乡seo外包便宜麻城百度霸屏seo软件短视频入口seo外推l老域名做seoseo优化外包哪个品牌好h2seo4和氢氧化钠柳州专业seo网站关键词优化seo询盘最多多少询盘seo优化从零开始留痕seo网站诊断表格百度竞价代理哪里专业seo推广关于seo的软文介绍企业产品站seo揭阳技术seo广西整站优化seo报价谷歌seo软件哪个好用排名衡水安平网站seo优化河南多方位服务型seo提高口碑湖南seo网络推广佛山电子seo如何seo 测试员浦城有效的seo介绍翻译seo搜索广告是sem广告还是seo独立ip做seo的优势吴忠seo优化哪家好律师网络推广seo长安seo关键词优化多少钱丰都一站式seo推广香港网站营销推荐乐云seoseo优化排名哪个品牌好上海seo优化公司有哪些坪山seo优化定制seo动态链接不利于优化汕尾市seo推广学习价格岳阳seo优化师湖北seo整站优化怎么做网络编辑跟seoseo如何有效的发布外链广东推荐的seo优化有什么谷歌seo优化培训班seo搜索引擎优化论文收录seo优化是指什么seo从业者工资福州seo公司要找10火星淘宝seo新手入门seo教程松溪搜索引擎seo介绍SEO优化黑词成都网站制作十年乐云seo江门全网营销seo推广哪家好杭州国内seo公司百度推广网站排名技术SEO优化官渡区seo优化开发安顺网络推广seoseo怎么找长尾字武汉怎样做seo推广麻城百度霸屏seo软件怎么运营百度竞价霸屏seo嘉兴seo优化企业三种方式提高seo效果兰州seo关键词排名服务平台seo财务自由濮阳seo推广方案如何seo优化一个名字收录晋中seo优化要多少钱淄博seo网络推广方法南川区seo优化计划济南seo选小七珠海seo排名优化公司云南seo哪家便宜SEO是什么粉底液最好官网seo前期建设seo2和乙醛的关系seo排名赚软件seo自学和零基础哪个好seo账号搭建南庄seo优化报价白云区市场seo优化包括什么seo三种形式链接seo精准营销霸屏seo外包收费标准珠海出名的seo排名系统渝中seo建站优化推广厦门哪里有seo外包seo整站优化哪家比较好兰州seo优化技巧费用徐州抖音搜索seo优化排名什么是独站SEOseo优化与百度竞价seo优化忌讳正规百度霸屏方案seo教程龙华seo哪家最好合肥专业企业seo价格放羊哥seo博客鼓楼区正规seo最新seo书籍云南seo推广服务商鄢陵seo优化排名多少钱云南长沙seo优化价格顺昌网络seo优化长尾词精选seo推广广州增城SEO排名优化seo优化终极算法视频seo新媒体推广电话新闻营销系统首选乐云seo上海谷歌seo该怎么开始重庆seo推广优化必看seo小白薪资seo手机流量排名页面优化seo立联盟3星seo关键字是什么排名seo提高新闻收录的方法seo网站查网址河源市seoseo每天干什么精确到点纸业seo案例重庆seo推广优化必看太康网站seo优化报价泉州seo推广营销外推霸屏推广怎么操作seo教程在线seo优化联系人仓山区效果好的seo服务费焦作seo优化软件丹灶seo优化费用seo检测方法小店区seo优化靠谱吗正规百度霸屏方案seo教程长沙株洲seo优化报价沅江seo优化关键词seo优化收费广告麻城百度霸屏seo软件传播SEOseo中核心词和属性词seo怎么优化关键词排名收录湖州正规seo外包费用悟空互动seo鹤山seo排名优化软件和平区运营抖音seo优化哪家好平顶山线上seo关键词优化效果seo中的mece望江seo优化哪家信誉好景德镇seo选哪家自己怎么做百度seo推广沙洋县seo关键词排名优化从seo频道起步高州关键词seo优化软件seo免费教程视频推广

    合作伙伴

    天下标王

    龙岗网络公司
    深圳网站优化
    龙岗网站建设
    坪山网站建设
    百度标王推广
    天下网标王
    SEO优化按天计费
    SEO按天计费系统