分布式数据库原理解析

一,分布式数据库的简单了解

我们目前使用的数据库中间件UDAL,据文档描述,底层使用的是阿里的开源Cobar,网上了解Cobar社区几年前早已经不更新了,现在比较火热的是Mycat中间件。UDAL与Mycat类似,此外根据自身业务需求,增加了全局序列,切片索引,日志,web统一管理界面等。

1,核心问题—事务

分布式数据库的核心问题的是事务的管理,Cobar是不支持分布式事务的,Mycat也只支持弱分布式事务。市面上其他各种分布式数据库中间件,包括 360 Atals、美团点评的DBproxy、携程的DAL等均只支持单库的事务。即使是单库的事务,是不是跟操作单个数据库的原理一样?当然对于我们用户感知而言,确实跟操作单库是没有区别。但在事务实现上,跟传统的操作单个数据库是有区别的。接下来讨论下传统数据库里面是如何实现事务,然后再分析分布式事务的复杂性,为啥这么多开源社区大牛暂时都不能解决。

2,分片及路由规则

分布式数据库把原来单个库的数据分开存储,其过程就是分片,其结果就是得到多个数据库,每个数据库称为片。一般而言,会预先设计好合理的分片键,使数据尽可能的均匀落在每一个片上。具体分片算法主要是hash算法和hash一致性算法等,算法内容不是本次讨论重点就暂时忽略。在管理分片时,有一个数据存储节点的概念,这是一个逻辑上的划分,一个节点可以包含一个或者多个片,也可以跨多个物理服务器。这里是部署上的规则,默认地,我们认为单个数据库节点指的是单个物理片。对于用户而言,感知的是一个库,这是由于中间件提供了统一管理的方式,对外界暴露一个访问地址,请求过来后,根据路由规则再跳转到具体的某个库里面。

3,路由规则

中间件如何知道用户要操作哪个库,使用的是之前定义好的分片键。当用户发送数据库操作语句时,中间件会根据分片算法得到具体数据落在哪一个片上。中间件统一管理每个物理片,会在元数据管理层上建立一个映射关系,即当分片算法得出操作的数据节点为dn1时,中间件就会根据映射关系获得节点dn1的连接,并把经过初步解析的sql送给dn1节点。这里就跟我们平时操作单库一样,只不过这个过程由中间件完成,对用户不可见。注意select与delete,insert,update的区别,select 根据分片键得到具体执行sql的数据节点,如果没有分片键,中间件会默认把sql发送到每一个节点上去执行,就是广播。如果是insert语句,默认是必须带有路由标识,因为中间解析sql的时候,如果没有分片建,插入数据的时候广播去插入每个数据节点,显然是不合理的。同理,delete, update也需要带有路由标识。

二,传统单个数据库的事务控制

事务的目的是为了保证一系列操作的原子性,要么同时成功,要么同时失败。数据库本身已实现事务,用户使用数据库需要事务的时候,只需要开启一个数据库的事务,并在此事务期间执行一些操作,提交事务后由数据库保证事务的完整性。这里通过Spring配置的显示事务,只是开启一个数据库事务,就好比告诉数据库,接下来的操作你必须保证要么全部成功,要么全部失败。具体做事由数据库完成,Spring不管这事,他只在有需求的时候,去通知某个人去做这件事。那么数据库怎么实现事务的?每次用户在执行DML语句时,数据库会有日志记录下,在整个事务期间所有的操作和对应变更的数据,有多条就记录多条,并以二进制的方式存储在数据库存储系统中。这里大批量的数据操作涉及到很大IO开销,如何存储,备份以及效率问题不做延伸。当最后某条语句失败时,数据库就开始回滚,实现过程就是解析之前记录的日志信息,进行逆操作。主从同步,分布式全局序列的生成也可以采用解析日志的方式实现。强调,用户在操作层面上只负责开启一个事务事件,具体事务执行由数据库完成。对于不在同一个事务里面,但又要保证所有操作的原子性,就要自己手动写代码回滚了。

三,分布式数据库的事务控制

在应用层,我们像操作单库时开启一个事务,现在问题就来了,开启一个事务后,中间件怎么知道用户接下来即将操作的是哪一个库。对于用户来说,开启事务后执行sql的节点完全随机。当然你说,怎么获得事务由中间件完成,我们不关注,只需要开启事务就行了。没错,这个确实是中间完成的,但是如果要自己去实现,该怎么办?

1,同一节点的事务
这里提供一个思路,当用户需要事务时,就向中间件发出一个消息,意思是接下来我要开启一个事务了,请各单位做好准备,但此时中间件名下这么多数据库,也不知道你要操作的是哪个啊。中间件正犯愁时,然后用户就发送insert语句来了,中间件一看带有分片键,这事好办了,立马根据映射关系拿到了某个节点的事务。中间件一想,既然路由到具体的数据库了,那么接下来操作就跟单库一样了,但是用户接下来所有操作必须保证都在同一个库里面。等所有操作执行完毕,用户发送commit,中间件收到信息就知道事务结束了,于是就通知底层那个库结束事务。这里问题又来了,事务如果失败了,到底是回滚还是不回滚?因为某些操作可以容忍失败的情况,不能一棒子打死,所以这里还需要用户判断是否回滚。如果不回滚,那执行错误的数据咋办?就得有补偿机制来操作了。默认地,如果是由于网路抖动带来的异常,一般重复操作失败步骤并限制重复次数,保持幂等性,直到成功为止。但对于数据本身或者代码问题,就属于故障了。这里重复写表的操作实现有多种,需要实时更新的数据,可以用异步消息队列来处理,对于非实时数据,半夜没人的时候用定时任务写也可以。

2,分布式事务
2.1 分布式事务的产生
为啥会有分布式事务这个概念,比如我们开始获得一个事务事件,进行以下操作,首先在dn1上update表a,然后又在dn2上insert表b,这下中间件犯愁了,我不是告诉你必须得在同一个节点里面吗?中间件还是开启节点dn1的事务,并默认你接下来操作都是在节点dn1里面的。这时忽然来了个节点dn2的操作,中间件又跑到节点dn2上执行,由于默认只开启dn1的事务,如果此时节点dn2失败,中间件不处理,他只能保证dn1上所有操作要么失败要么成功。所以,更新dn1上的a表和插入dn2上表b并不在同一个事务里面,无法保证原子性,就产生了分布式事务。这里指的是,如同Cobar不提供分布式事务的情况。

2.2 弱XA事务
Mycat有提供弱分布式事务的解决方案,如同上述情况,在dn1上update表a后,需要在在dn2上insert表b时,在dn2节点开启一个事务,此时我们的事务由事务dn1和事务dn2组成。如果dn1事务成功,并commit,接下来进行事务dn2,如果事务2失败,立马回滚事务dn2的操作,但是不回滚dn1的操作。这就是弱事务的解决方案。

2.3强XA事务
如同上述情况,如果事务dn2失败,那么连同之前事务dn1也同时回滚,严格保证分布式事务的原子性。这里原理很简单,实现过程也不是很难。但是考虑到使用业务背景的复杂性,在一个分布式事务中可能需要包含很多个子事务,一方面,如果由于某个原因导致最后一步失败,就立马回滚整个操作,尤其是复杂的组合事务,采用强XA事务会严重影响应用服务器的效率。其次,事务是通过网络在节点之间传输,具有一定不可靠性,如果严格保证事务一致性,将牺牲系统本身很大的性能。所以,软件领域的一句话很实用,解决一个问题的最好方式就是绕过它。尽可能保证所有操作在同一事务下,无法避免的再讨论了。

没有更多推荐了,返回首页