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

如果你对项目管理、系统架构有兴趣,请加微信订阅号“softjg”,加入这个PM、架构师的大家庭

0aee4a538f7510a6d3f381dd53bc1108.png

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

假设目前存在三个物理库,现在有一个文件,里面有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个分库的数据,在导入第三个分库失败的情况下,要删除掉前面两个分库的数据,这也没有办法保证是一致的。

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

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

0dac93df1c8961b2df42bc66de086700.png

d37ca828bee355fae4a71f464e579c03.png

方案3

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

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

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

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

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

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

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

3721746.html

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

我们假设有四个库,一个主库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.IDinto SUB_IDfrom MAIN.SUB_BATCH_INFOwhere SUB_BATCH_INFO.DATA_BASE = SUB1

//判断分库数据是否存在,存在就返回true

if(select *from SUB1.SUB_BATCH_INFOwhere 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_INFOset status ='R' where ID = SUB_ID

commit;

end;

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

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

+ View Code?

##分库的流水操作

begin transaction in SUB1

declare file_name,batch_no,sub_batch_no;

select SUB_BATCH_INFO.statusinto SUB_IDfrom MAIN.SUB_BATCH_INFOwhere 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_INFOset 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 第四种方案就是经典的分布式事务设计中的 两阶段提交思想。两阶段提交的有三个重要的子操作:准备提交,提交,回滚。

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

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

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

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

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

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

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

7cf2009e39bbdb1695fbf0600a9c56cd.png

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

如果你对项目管理、系统架构有兴趣,请加微信订阅号“softjg”,加入这个PM、架构师的大家庭

0aee4a538f7510a6d3f381dd53bc1108.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/427254.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

同名字的数值求和插入行_EXCEL条件求和的6种技术,你会的超过3种吗?

今天我们来谈谈EXCEL中的条件求和。我们将利用不同的技术实现,而不是使用函数的6种方法直接开始吧!数据源结果1、数据透视表-难度系统★☆☆☆☆插入-透视表,行字段-销售员,值-金额2、 函数公式法-难度系数:★★☆☆☆…

Centos7构建NFS服务器和连接

准备两台centos7虚拟机 192.168.30.133 192.168.30.129 2.192.168.30.1(服务端), 3查看rpc服务是否启动 4测试安装是否成功 5修改配置文件vi/etc/exports /data 192.168.1.0/24(rw,async) 6启动服务 systemctl start nfs rpcinfo -p查看 …

maven pc配置要求_《使命召唤:黑色行动5》公开测试PC配置要求:推荐GTX970+i7

动视官方公开了《使命召唤:黑色行动5》的公开测试PC系统要求,从官方给出的信息来看,PC平台最低需求为GTX 670显卡i5 2500k处理器。 以下为官方公布的系统要求:PC公开测试最低配置需求操作系统:Windows 7 64-Bit (SP1) …

linux下源码安装nginx

LNMP模式 后续继续更新,先搭建nginx 安装环境gcc gcc-c 2 下载源码包解压 配置第一个报错 安装openssl openssl-devel yum -y install openssl openssl-devel Make报错 解决 tar -zxvf pcre-8.37.tar.gz cd pcre-8.34 ./configure make make install tar -zxvf …

jdk安装失败_windows配置安装单个Tomcat

1.前期准备1).将tomcat安装文件复制到服务器2) 安装jdk,将jdk对应的版本安装到服务器,安装好后cmd命令java -version可以看到对应的版本2.配置环境变量1)jdk安装好后配置jdk环境变量变量名:JAVA_HOME 变量值…

centos源码安装PHP

上篇博客说了nginx了,这篇说PHP,下一篇开始开始 bind相关知识 2开始解压 3 ./configure -help|grep mysql 帮助我们查找可以关于mysql的节点 4配置./configure --enable-fpm --with-mysql --with-mysqli --with-pdo-mysql 安装 5报错 6解决 7继续配…

word2vec训练词向量 python_使用Gensim word2vector训练词向量

注意事项Skip-Gram models:输入为单个词,输出目标为多个上下文单词;CBOW models:输入为多个上下文单词,输出目标为一个单词;选择的训练word2vec的语料要和要使用词向量的任务相似,并且越大越好&…

bind安装和主要配置

1 yum -y install bind bind-chroot 2rpm -qa|grep bind,查看安装状态 3service named start服务启动 4主配置文件name.conf Option{} 整个bind的全局选项 Logging{}日志输出选项 Zong 根域 这节比较简单,随便看看即可,持续更新bind相关知识…

为什么火狐浏览器中点击按钮失效_各种浏览器审查、监听http头工具介绍

一、谷歌内置的审查工具(v17.0)。右键点击审查(CtrlShirtAlt)浏览器下方会出现审查框,刷新网页就会出现下图所示,先后点击“netword”-->在下方选中资源(如下图的1.php)-->点击headers二、httpwatch。ShirtF2打开httpwatch点击Record按钮&#xff…

java 对象流_java 对象流的简单使用

对象的输入输出流的作用: 用于写入对象 的信息和读取对象的信息。 使得对象持久化。ObjectInputStream : 对象输入流ObjectOutPutStream :对象输出流简单的实例1 importjava.io.File;2 importjava.io.FileInputStream;3 importjava.io.FileOutputStre…

centos搭建ftp服务器

1安装vsftpd 2备份配置文件 3修改配置文件 vi /etc/vsftpd/vsftpd.conf anonymous_enableNO #允许匿名用户访问为了安全选择关闭 local_enableYES # 允许本地用户登录 write_enableYES # 是否允许写入 local_umask022 # 本地用户上传文件的umask dirmessage_enableYES #为YES…

ihtml2document能不能根据id获取dom_一段监视 DOM 的神奇代码

作者:Eddie Aich翻译:疯狂的技术宅原文:https://dev.to/eddieaich/spy-on-the-dom-3d47未经允许严禁转载通过使用此模块,只需将鼠标悬停在浏览器中,即可快速查看DOM元素的属性。基本上它是一个即时检查器。将鼠标悬停在…

centos7搭建apache服务器(亲测可用)

1安装apache yum install httpd httpd-devel -y 2开启服务器 systemctl start httpd.service 3开机自启 systemctl enable httpd.service 4关闭防火墙 5端口访问 6修改vi /etc/httpd/conf/httpd.conf,替换 7查看selinux 也可以不修改,放入/var/www/h…

Python爬去知乎上问题下所有图片

from zhihu_oauth import ZhihuClient from zhihu_oauth.exception import NeedCaptchaExceptionclient ZhihuClient()try:client.login(email_or_phone, password)print(u"登陆成功!") except NeedCaptchaException:# 保存验证码并提示输入,重新登录wit…

xshell连接突然报Connection closed by foreign host.

1问题描述报错 Connection closed by foreign host. Disconnected from remote host(yaoGS) at 155513. 2登入虚拟机 在linux系统操作中,经常需要连接其他的主机,连接其他主机的服务是openssh-server,它的功能是让远程主机可以通过网络访问…

java 爬虫_探索Java 多线程爬虫及分布式爬虫架构

在我们调试爬虫程序的时候,单线程爬虫没什么问题,但是当我们在线上环境使用单线程爬虫程序去采集网页时,单线程就暴露出了两个致命的问题:采集效率特别慢,单线程之间都是串行的,下一个执行动作需要等上一个…

docker小实战和应用

1运行一个docker 一开始docker进不去,需要去https://hub.docker.com注册一个 2docker info查看信息 3docker run ubuntu echo hello world 查看第一个命令输出 4docker images 查看本地的镜像 5查看开启的容器和没有开启的容器 Docker ps -a 6 docker pull ngi…

java垃圾回收机制_干货:Java 垃圾回收机制

什么是自动垃圾回收?自动垃圾回收是一种在堆内存中找出哪些对象在被使用,还有哪些对象没被使用,并且将后者删掉的机制。所谓使用中的对象(已引用对象),指的是程序中有指针指向的对象;而未使用中的对象(未引用对象)&…

linux修改网卡名(亲测有效)

1查看网卡ip addr 2cd /etc/sysconfig/network-scripts Ls查看 3mv ifcfg-eno16777736 ifcfg-eth0重命名,然后编辑 最后一行加入IPADDR192.168.30.136 NETMASK255.255.255.0 HWADDR00:0C:29:aa?2f BOOTPROTO改成static 4 vi /etc/default/grub 5 grub2-mkconfig…

读取html文件,让其中的内容和notepad打开这个html的样子一样。

然后我写了个python代码,让其读取这个html文件后,内容和这个一样: htmlfopen(13144815898.html,r,encoding"utf-8") htmlconthtmlf.read() print((htmlcont)) 转载于:https://www.cnblogs.com/www-caiyin-com/p/9447285.html