分布式
1.基础理论
常见的三个概念:
-
CAP定理
CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer’s theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
-
一致性Consistence:
所有节点访问同一份最新的数据副本
-
可用性Availability:
每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据
-
容错性Partition tolerance:
分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。
CAP仅适用于原子读写的NOSQL场景中,并不适合数据库系统。现在的分布式系统具有更多特性比如扩展性、可用性等等,在进行系统设计和开发时,我们不应该仅仅局限在CAP问题上。
当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能2选1。也就是说当网络分区之后P是前提,决定了P之后才有C和A的选择。也就是说分区容错性(Partition tolerance)我们是必须要实现的。
-
-
BASE理论
BASE理论由eBay架构师Dan Pritchett提出,在2008年上被分表为论文,并且eBay给出了他们在实践中总结的基于BASE理论的一套新的分布式事务解决方案。
-
基本可用(Basically Available)
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。例如:
响应时间上的损失:正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒。
系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
-
软状态 (Soft-state)
软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
- 最终一致性(Eventually Consistent)
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的,它大大降低了我们对系统的要求。
核心思想:
即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。
-
-
分布式系统设计理念
分布式系统的目标是提升系统的整体性能和吞吐量另外还要尽量保证分布式系统的容错性(假如增加10台服务器才达到单机运行效果2倍左右的性能,那么这个分布式系统就根本没有存在的意义)。
即使采用了分布式系统,我们也要尽力运用并发编程、高性能网络框架等等手段提升单机上的程序性能。
1.1 分布式系统设计俩大思路
1.1.1 中心化
-
俩个角色
分布式集群中的节点机器按照角色分工,大体上分为两种角色。领导和干活的;
-
角色职责
“领导”通常负责分发任务并监督“干活的”,发现谁太闲了,就想发设法地给其安排新任务,确保没有一个“干活的”能够偷懒,如果“领导”发现某个“干活的”因为劳累过度而病倒了,则是不会考虑先尝试“医治”他的,而是一脚踢出去,然后把他的任务分给其他人。其中微服务架构 Kubernetes 就恰好采用了这一设计思路。
-
中心化的问题
- 中心化的设计存在的最大问题是“领导”的安危问题,如果“领导”出了问题,则群龙无首,整个集群就奔溃了。但我们难以同时安排两个“领导”以避免单点问题。
- 中心化设计还存在另外一个潜在的问题,既“领导”的能力问题:可以领导10个人高效工作并不意味着可以领导100个人高效工作,所以如果系统设计和实现得不好,问题就会卡在“领导”身上
-
领导安危问题的解决办法
大多数中心化系统都采用了主备两个“领导”的设计方案,可以是热备或者冷备,也可以是自动切换或者手动切换,而且越来越多的新系统都开始具备自动选举切换“领导”的能力,以提升系统的可用性。
1.1.2 去中心化
-
众生地位平等
在去中心化的设计里,通常没有“领导”和“干活的”这两种角色的区分,大家的角色都是一样的,地位是平等的,全球互联网就是一个典型的去中心化的分布式系统,联网的任意节点设备宕机,都只会影响很小范围的功能。
-
**“去中心化”**不是不要中心,而是由节点来自由选择中心
集群的成员会自发的举行“会议”选举新的“领导”主持工作。最典型的案例就是ZooKeeper及Go语言实现的Etcd。
-
去中心化设计的问题
- 脑裂问题:一个集群由于网络的故障,被分为至少两个彼此无法通信的单独集群,此时如果两个集群都各自工作,则可能会产生严重的数据冲突和错误。
- 脑裂解决:一般的设计思路是,当集群判断发生了脑裂问题时,规模较小的集群就“自杀”或者拒绝服务。
2.分布式事务
2.1 什么是分布式事务
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
2.2 分布式事务产生的原因
2.2.1 数据库分库分表
当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表。这时候,如果一个操作既访问01库,又访问02库,而且要保证数据的一致性,那么就要用到分布式事务。
2.2.2 应用SOA化
所谓的SOA化,就是业务的服务化。比如原来单机支撑了整个电商网站,现在对整个网站进行拆解,分离出了订单中心、用户中心、库存中心。对于订单中心,有专门的数据库存储订单信息,用户中心也有专门的数据库存储用户信息,库存中心也会有专门的数据库存储库存信息。这时候如果要同时对订单和库存进行操作,那么就会涉及到订单数据库和库存数据库,为了保证数据一致性,就需要用到分布式事务。
2.2.3 分布式事务的应用场景
-
支付
最经典的场景就是支付了,一笔支付,是对买家账户进行扣款,同时对卖家账户进行加钱,这些操作必须在一个事务里执行,要么全部成功,要么全部失败。而对于买家账户属于买家中心,对应的是买家数据库,而卖家账户属于卖家中心,对应的是卖家数据库,对不同数据库的操作必然需要引入分布式事务。
-
在线下单
买家在电商平台下单,往往会涉及到两个动作,一个是扣库存,第二个是更新订单状态,库存和订单一般属于不同的数据库,需要使用分布式事务保证数据一致性。
2.2.4 常见的分布式事务解决方案
-
基于XA协议的俩阶段提交
XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:
总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。
缺点:
- 性能低下,无法满足高并发场景
- mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。
2PC存在的问题:
-
阻塞
从上面的描述来看,对于任何一次指令必须收到明确的响应,才会继续做下一步,否则处于阻塞状态,占用的资源被一直锁定,不会被释放。
-
单点故障
如果协调者宕机,参与者没有了协调者指挥,会一直阻塞,尽管可以通过选举新的协调者替代原有协调者,但是如果之前协调者在发送一个提交指令后宕机,而提交指令仅仅被一个参与者接受,并且参与者接收后也宕机,新上任的协调者无法处理这种情况。
-
脑裂
协调者发送提交指令,有的参与者接收到执行了事务,有的参与者没有接收到事务,就没有执行事务,多个参与者之间是不一致的
-
消息事务+最终一致性
所谓的消息事务就是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性,具体原理如下:
对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析:
- 步骤一出错,整个事务失败,不会执行A事务提交
- 步骤二出错,整个事务失败,不会执行A事务提交
- 步骤三出错,这时候需要回滚预备消息,怎么回滚?答案是A系统实现一个消息中间件的回调接口,消息中间件会去不断执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息。
- 步骤四出错,这时候A的本地事务是成功的,那么消息中间件要回滚A吗?答案是不需要,其实通过回调接口,消息中间件能够检查到A执行成功了,这时候其实不需要A发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务。
基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。原理如下:
虽然上面的方案能够完成A和B的操作,但是A和B并不是严格一致的,而是最终一致的,我们在这里牺牲了一致性,换来了性能的大幅度提升。当然,这种玩法也是有风险的,如果B一直执行不成功,那么一致性会被破坏,具体要不要玩,还是得看业务能够承担多少风险。
3.TCC编程
所谓的TCC编程模式,也是两阶段提交的一个变种。TCC提供了一个编程框架,将整个业务逻辑分为三块,以在线下单为例:
- try:扣库存
- confirm:更新订单状态
- cancel:更新失败则恢复库存
总结
分布式事务,本质上是对多个数据库的事务进行统一控制,按照控制力度可以分为:不控制、部分控制和完全控制。不控制就是不引入分布式事务,部分控制就是各种变种的两阶段提交,包括上面提到的消息事务+最终一致性、TCC模式,而完全控制就是完全实现两阶段提交。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,完全控制则是牺牲了性能,保障了一致性,具体用哪种方式,最终还是取决于业务场景。作为技术人员,一定不能忘了技术是为业务服务的,不要为了技术而技术,针对不同业务进行技术选型也是一种很重要的能力!
3.分布式系统一致性
4.一致性协议/算法
- Paxos一致性算法
- 分布式协议RAFT
- Zookeeper ZAB协议
4.1 Paxos一致性算法
- proposer 提案者
- accpetor 提案接收者
- learners
算法分为俩个阶段
-
阶段一:
- proposer选择一个提案编号N,向半数以上的accpetor发送 prepare请求;
- 如果一个accpetor接收到编号N的prepare的请求,且N大于该accpetor以响应过的所有prepare请求的编号,那么它将会将它接受过的编号最大提案作为响应反馈给accpetor,如果没有提案则响应NULL提案;
- 同时该accpetor保证不再接受任何编号任何编号小于N的proposer
-
阶段二:
- 如果proposer收到超过半数的accpetor的响应(编号为N的提案的prepare响应)
- 则会发送一个[N,V]的proposer给超过半数的accpetor(accpetor可以和响应的accpetor不一致)
- V就是收到的响应中编号最大的提案的value,如果响应中不包含任何提案,那么V就由Proposer自己决定。
- 如果accpetor收到一个proposer[N,V]的提案,如果accpetor没有对编号大于N的提案做出过prepare响应(阶段一的prepare响应),则接受该提案。
4.1.1 learner接收value的方案:
- accpetor接受了一个提案(阶段二)则向所有learner发送该提案
- 优点:learner能快速获取提案的V
- 缺点:通信的次数为M*N
- accpetor接受了一个提案,则向主learner发送该提案,主learner在通知其他learner
- 优点:通信次数减少M+N-1
- 缺点:单点问题,主节点可能出现故障
- accpetor接受了一个提案,则向learner集合发送该提案,learner集合在通知其他learner
- 集合中learner越多,可靠性越好
- 网络通信复杂
4.1.2 如何保证Paxos算法的活性
通过选取主Proposer,就可以保证Paxos算法的活性。至此,我们得到一个既能保证安全性,又能保证活性的分布式一致性算法——Paxos算法。只有主Proposer才能提案,避免死循环。
4.2 RAFT 算法
- leader: 处理所有客户端交互,日志复制等,一般一次只有一个Leader
- follower 类似选民,完全被动
- candidate: 候选人, 类似Proposer律师,可以被选为一个新的领导人
算法分为俩个模块
- 阶段一(选举)
- 阶段二(日志复制)
4.2.1 选举过程:
- 三个节点初始状态都为follower
- 每个follower都有对应的leader心跳超时时间(150ms-300ms)
- 第一个超时的follower发起选举,将自己状态变成candidate,同时通知其他节点,发送投票请求
- 节点如果在同一个选举过程中未收做出其他投票请求返回,则同意投票;
- 当candidate收到超过一半的投票请求返回时,则将自己变成leader
4.2.2 日志复制流程:类似与2PC 二阶段提交过程
- client 发送请求给leader ,例如请求为:‘Set 5’
- leader接受到请求后,leader将set 5 写入本身节点log中,写入成功后,通知其他节点
- 其他节点接收到通知,则将set 5也写入log中,并返回结果
- 当leader收到半数以上的成功返回,则认为写入成功,此时将set 5 commit到真正的数据中(事务提交),并返回client请求
- 同时通知其他节点,commit 结果,将set 5 commit
4.2.3 选举超时问题
- 当A和B都为follower时,同时进入candidate状态,并向其他节点发起选举
- 当A和B都没有获得超过半数的票数时
- A和B互相竞争为leader
- 等到选举超时时(150ms-300ms),重新发选票,直到获取半数以上的candidate成为leader
4.2.4 心跳超时
- 当follower超过一定时间没有接收到leader节点的心跳数据时,第一个超时的follower会重新发起选举,选举新的leader
4.2.5 集群中断
- 当集群之间的部门节点失去通讯时,leader节点的日志不能复制给多个follower,就不能提交
- A.B.C.D.E中A&B&C 和D&E失去通信(网络分区),leader节点为D
- 这是当A.B.C中心跳超时时,会重新产生新的leader:此处假设为A
- client1请求A,A能收到3个(包括自身)的日志复制回复,则A进行提交,并通知B.C且返回数据给client1
- client2请求D,D不能收到超过半数的日志复制的回复,则不能提交
- 当网络分区恢复后,D发现自身不是选票最多的节点,则变成follower节点,并回滚client2的请求(日志复制)
- 当下次A向D.E发送心跳时,则将client1请求的日志复制给D.E
4.3 ZAB 协议
ZAB是zookeeper atomic broadcast 原子消息广播协议,为zookeeper 设计的崩溃可恢复的原子广播消息算法。
4.3.1 核心
所有事务请求必须由全局的唯一服务器来协调处理(leader),其他的服务器为follower.
leader负责将一个客户端的事务请求转换成一个事务proposal(提议),并将proposal转发给所有follower,
r若leader收到超过半数的follower的正确反馈,则leader会向所有的follower分发commit请求,要求将上一个proprosal进行提交。
4.3.2 俩个模式
- 崩溃恢复
- 消息广播