分布式系统理论
Posted 买糖买板栗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式系统理论相关的知识,希望对你有一定的参考价值。
目录
1 基础
1.1 一致性hash算法
深入一致性哈希(Consistent Hashing)算法原理
一致性Hashing在分布式系统中经常会被用到, 用于尽可能地降低节点变动带来的数据迁移开销
- hash算法缺陷:先来简单理解下Hash是解决什么问题。假设一个分布式任务调度系统,执行任务的节点有n台机器,现有m个job在这n台机器上运行,这m个Job需要逐一映射到n个节点中一个,这时候可以选择一种简单的Hash算法来让m个Job可以均匀分布到n个节点中,比如 hash(Job)%n ,看上去很完美,但考虑如下两种情形:1、n个节点中有一个宕掉了,这时候节点数量变更为n-1,此时的映射公式变成 hash(Job)%(n-1);2、由于Job数量增加,需要新增机器,此时的映射公式变成 hash(Job)%(n+1), 1、2两种情形可以看到,基本上所有的Job会被重新分配到跟节点变动前不同的节点上,意味着需要迁移几乎所有正在运行的Job,想想这样会给系统带来多大的复杂性和性能损耗。
- 一致性hash算法:见上面链接
1.2 CAP 定理、 BASE 理论、2PC、3PC
1.2.1 CAP理论
- 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
- 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
- 分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
zookeeper基于CP、Eureka(SpringCloud基于它实现服务注册发现)则是AP。
1.2.2 BASE 理论
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。接下来我们着重对BASE中的三要素进行详细讲解。
1.2.3 2PC(分布式事务)
1. 事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交。
2. 事务协调器要求每个数据库提交数据,或者回滚数据。
(第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段))
优点: 尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于 mysql 是从 5.5 开始支持。
缺点:单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能。比如在第二阶段中,假设协调者发出了事务 Commit 的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了 Commit 操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。总的来说,2PC 协议比较简单,成本较低,但是其单点问题,以及不能支持高并发(由于同步阻塞)依然是其最大的弱点。
1.2.4 3PC(分布式事务)
针对两阶段提交存在的问题,三阶段提交协议通过引入一个“预询盘”阶段,以及超时策略来减少整个集群的阻塞时间,提升系统性能。三阶段提交的三个阶段分别为:can_commit,pre_commit,do_commit。
第一阶段:can_commit
该阶段协调者会去询问各个参与者是否能够正常执行事务,参与者根据自身情况回复一个预估值,相对于真正的执行事务,这个过程是轻量的,具体步骤如下:
1. 协调者向各个参与者发送事务询问通知,询问是否可以执行事务操作,并等待回复。
2. 各个参与者依据自身状况回复一个预估值,如果预估自己能够正常执行事务就返回确定信息,并进入预备状态,否则返回否定信息。
第二阶段:pre_commit
本阶段协调者会根据第一阶段的询盘结果采取相应操作,询盘结果主要有三种:
1. 所有的参与者都返回确定信息。
2. 一个或多个参与者返回否定信息。
3. 协调者等待超时。
针对第一种情况,协调者会向所有参与者发送事务执行请求,具体步骤如下:
1. 协调者向所有的事务参与者发送事务执行通知。
2. 参与者收到通知后,执行事务,但不提交。
3. 参与者将事务执行情况返回给客户端。
在上面的步骤中,如果参与者等待超时,则会中断事务。 针对第二、三种情况,协调者认为事务无法正常执行,于是向各个参与者发出abort通知,请求退出预备状态,具体步骤如下:
1. 协调者向所有事务参与者发送abort通知
2. 参与者收到通知后,中断事务
第三阶段:do_commit
如果第二阶段事务未中断,那么本阶段协调者将会依据事务执行返回的结果来决定提交或回滚事务,分为三种情况:
1. 所有的参与者都能正常执行事务。
2. 一个或多个参与者执行事务失败。
3. 协调者等待超时。
针对第一种情况,协调者向各个参与者发起事务提交请求,具体步骤如下:
1. 协调者向所有参与者发送事务commit通知。
2. 所有参与者在收到通知之后执行commit操作,并释放占有的资源。
3. 参与者向协调者反馈事务提交结果。
针对第二、三种情况,协调者认为事务无法正常执行,于是向各个参与者发送事务回滚请求,具体步骤如下:
1. 协调者向所有参与者发送事务rollback通知。
2. 所有参与者在收到通知之后执行rollback操作,并释放占有的资源。
3. 参与者向协调者反馈事务提交结果。
在本阶段如果因为协调者或网络问题,导致参与者迟迟不能收到来自协调者的commit或rollback请求,那么参与者将不会如两阶段提交中那样陷入阻塞,而是等待超时后继续commit。相对于两阶段提交虽然降低了同步阻塞,但仍然无法避免数据的不一致性。
1.3 TCC(Try-Confirm-Cancel)理论
TCC 将事务提交分为 Try - Confirm - Cancel 3个操作。
- Try:预留业务资源/数据效验
- Confirm:确认执行业务操作
- Cancel:取消执行业务操作
TCC事务处理流程和 2PC 二阶段提交类似,不过 2PC通常都是在跨库的DB层面,而TCC本质就是一个应用层面的2PC。
原本的一个接口,要改造为3个逻辑,Try-Confirm-Cancel。
-
先是服务调用链路依次执行Try逻辑
-
如果都正常的话,TCC分布式事务框架推进执行Confirm逻辑,完成整个事务
-
如果某个服务的Try逻辑有问题,TCC分布式事务框架感知到之后就会推进执行各个服务的Cancel逻辑,撤销之前执行的各种操作
先来Try一下,不要把业务逻辑完成,先试试看,看各个服务能不能基本正常运转,能不能先冻结我需要的资源。如果Try都ok,也就是说,底层的数据库、redis、elasticsearch、MQ都是可以写入数据的,并且你保留好了需要使用的一些资源(比如冻结了一部分库存)。接着,再执行各个服务的Confirm逻辑,基本上Confirm就可以很大概率保证一个分布式事务的完成了。那如果Try阶段某个服务就失败了,比如说底层的数据库挂了,或者redis挂了,等等。此时就自动执行各个服务的Cancel逻辑,把之前的Try逻辑都回滚,所有服务都不要执行任何设计的业务逻辑。保证大家要么一起成功,要么一起失败。这就是所谓的TCC分布式事务。
如果有一些意外的情况发生了,比如说订单服务突然挂了,然后再次重启,TCC分布式事务框架是如何保证之前没执行完的分布式事务继续执行的呢?所以,TCC事务框架都是要记录一些分布式事务的活动日志的,可以在磁盘上的日志文件里记录,也可以在数据库里记录。保存下来分布式事务运行的各个阶段和状态。问题还没完,万一某个服务的Cancel或者Confirm逻辑执行一直失败怎么办呢?那也很简单,TCC事务框架会通过活动日志记录各个服务的状态。举个例子,比如发现某个服务的Cancel或者Confirm一直没成功,会不停的重试调用他的Cancel或者Confirm逻辑,务必要他成功!
TCC优点:让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。
TCC不足之处:
- 对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高。
- 实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口必须实现幂等。
2 设计
2.1 限流策略
- 计数器算法:设置一个计数器统计单位时间内某个请求的访问量,在进入下一个单位时间内把计数器清零,对于单位时间内超过计数器的访问,可以放入等待队列、直接拒接访问等策略
- 漏斗算法:一个固定容量的漏桶,按照常量固定速率流出水滴;可以以任意速率流入水滴到漏桶;如果流入水滴超出了桶的容量,则流入的水滴溢出了,而漏桶容量是不变的。
- 令牌桶算法:令牌将按照固定的速率被放入令牌桶中。比如每秒放10个。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。当令牌桶满时,新添加的令牌被丢弃或拒绝
漏桶算法与令牌桶算法在表面看起来类似,很容易将两者混淆。但事实上,这两者具有截然不同的特性,且为不同的目的而使用。漏桶算法与令牌桶算法的区别在于,漏桶算法能够强行限制数据的传输速率,令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。需要注意的是,在某些情况下,漏桶算法不能够有效地使用网络资源,因为漏桶的漏出速率是固定的,所以即使网络中没有发生拥塞,漏桶算法也不能使某一个单独的数据流达到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。而令牌桶算法则能够满足这些具有突发特性的流量。通常,漏桶算法与令牌桶算法结合起来为网络流量提供更高效的控制。
我们公司:
单机限流:google的guava令牌桶算法实现;
集群限流:Redis的计数实现精确;
以上是关于分布式系统理论的主要内容,如果未能解决你的问题,请参考以下文章