麒麟v10 上部署 TiDB v5.1.2 生产环境优化实践
1776
2023-10-23
分布式数据库定义为一群分布在计算机网络上、逻辑上相互关联的数据库。分布式数据库管理系统(分布式DBMS)则是支持管理分布式数据库的软件系统,它使得分布对于用户变得透明。有时,分布式数据库系统(Distributed Database System,DDBS)用于表示分布式数据库和分布式DBMS这两者。
二、 分布式数据库架构及原理
1. CAP理论
首先,分布式数据库是基于单点关系数据库的基本特性的继承,主要涉及事务的ACID特性、事务日志的容灾恢复性、数据冗余的高可用性几个要点。
其次,分布式数据设计必须遵循CAP定理:分布式系统不能同时满足 一致性(Consistency) 、可用性(Availability) 、分区容忍性(Partition tolerance) 这三个基本需求,最多满足其中的两项, 由于分区容错性是绝对不能抛弃的,因此需要考虑业务情况在可用性和一致性之间做出权衡。
CAP 三个特性描述如下 :
一致性(Consistency):确保分布式群集中的每个节点都必须返回相同的 、最近更新的数据 。一致性是指每个客户端具有相同的数据视图。有多种类型的一致性模型 , CAP中的一致性是指线性化或顺序一致性,是强一致性。
可用性(Availability):每个非失败节点在合理的时间内返回所有读取和写入请求的响应。为了可用,网络分区两侧的每个节点必须能够在合理的时间内做出响应。
分区容忍性(Partition tolerance):尽管存在网络分区,系统仍可继续运行并 保证 一致性。网络分区已成事实。保证分区容忍度的分布式系统可以在分区修复后从分区进行适当的恢复。
根据实际业务情况, 分布式系统会根据业务对一致性和可用性需求的重要等级提供最佳解决方案,但通常一致性等级会更高,也是最有挑战的 。
2. BASE理论
权衡CAP定理演进出了 BASE理论 ,BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。
BASE理论的核心思想:由于某些原因无法做到强一致性,但每个应用都可以根据其自身业务特点,采用适当的方式来达到最终一致性。
BA(Basically Available): 基本可用,分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。
S(Soft State): 软状态,允许系统存在中间状态,而该中间状态不会影响系统整体可用性。
E(Consistency):最终一致性,系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
BASE 理论本质上是对 CAP 理论的延伸,是对 CAP 中 AP 方案的一个补充。
这里补充说明一下什么是强一致性:
Strict Consistency ( 强一致性 ) 也称为Atomic Consistency ( 原子一致性) 或 Linearizable Consistency(线性一致性) ,必须满足以下 两个要求:
1、任何一次读都能读到某个数据的最近一次写的数据。
2、系统中的所有进程,看到的操作顺序,都和全局时钟下的顺序一致。
对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。简言之,在任意时刻,所有节点中的数据是一样的。BASE 理论的最终一致性属于弱一致性。
接下来介绍另一个分布式数据库重要的概念:分布式事务。浏览了几篇介绍分布式事务的文章,发现会有不同的描述,但大致含义是相同的。分布式事务首先是事务, 需要满足事务的ACID的特性。主要考虑业务访问处理的数据分散在网络间的多节点上,对于分布式数据库系统而言, 在保证数据一致性的要求下,进行事务的分发、协同多节点完成业务请求。
多节点能否正常、顺利的协同作业完成事务是关键,它直接决定了访问数据的一致性和对请求响应的及时性。从而就需要科学有效的一致性算法来支撑。
3. 一致性算法
一致性算法 包括2PC 、3PC 、Paxos 、Raft 等四种:
2PC / 3PC 协议用于保证属于多个数据分片上操作的原子性。
这些数据分片可能分布在不同的服务器上,2PC / 3PC 协议保证多台服务器上的操作要么全部成功,要么全部失败。
二阶段提交2PC (Two-Phase Commit ): 用来保证分布式系统数据的一致性的一致性协议,大部分基于关系型数据库的分布式事务处理都是采用二阶段提交协议来完成。其优点是原理简单且实现方便,缺点是同步阻塞、单点问题、数据不一致、太过保守。
2PC主要包括以下两个阶段:
第一阶段:提交事务请求(投票阶段)
第二阶段:执行事务提交(执行阶段)
为了避免在通知所有参与者提交事务时,其中一个参与者 crash 不一致时,就出现了三阶段提交的方式。
三阶段提交 3PC(Three- Phase Commi ):包括 CanCommit、PreCommit、doCommit 三个阶段。显而易见,三阶段提交是在两阶段提交的基础上增加了一个 preCommit 的过程,当所有参与者收到 preCommit 后,并不执行动作,直到收到 commit 或超过一定时间后才完成操作。
其优点是降低参与者阻塞范围,并能够在出现单点故障后继续达成一致;缺点是引入preCommit 阶段,在这个阶段如果出现网络分区,协调者无法与参与者正常通信,参与者依然会进行事务提交,造成数据不一致。
Paxos 、 Raft 算法用于保证同一个数据分片的多个副本之间的数据一致性 。以下是两种种算法的描述 :
Paxos (强一致性)算法属于多数派算法,主要目的是解决数据分片的单点问题, 通过该算法可以让整个集群的节点对某个值的变更达成一致。任何一个节点都可以提出要修改某个数据的提案,是否通过这个提案取决于这个集群中是否有超过半数的节点同意,因此Paxos算法需要集群中的节点必须是单数 。
Raft算法是简化版的Paxos, 定义了三种角色 Leader、Follower、Candidate:最初都是Follower,当Follower监听不到Leader,就自动成为Candidate,然后发起投票选出新的Leader 。因此,Raft 划分成三个子问题:
我们目前使用的数据库中间件UDAL,据文档描述,底层使用的是阿里的开源Cobar,网上了解Cobar社区几年前早已经不更新了,现在比较火热的是Mycat中间件。UDAL与Mycat类似,此外根据自身业务需求,增加了全局序列,切片索引,日志,web统一管理界面等。
1,核心问题—事务
分布式数据库的核心问题的是事务的管理,Cobar是不支持分布式事务的,Mycat也只支持弱分布式事务。市面上其他各种分布式数据库中间件,包括 360 Atals、美团点评的***、携程的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事务会严重影响应用服务器的效率。其次,事务是通过网络在节点之间传输,具有一定不可靠性,如果严格保证事务一致性,将牺牲系统本身很大的性能。所以,软件领域的一句话很实用,解决一个问题的最好方式就是绕过它。尽可能保证所有操作在同一事务下,无法避免的再讨论了。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。