DistributedSystem-CheatSheet
Distributed System CheatSheet | 分布式系统导论
A distributed system involves a set of distinct processes (e.g., computers) passing messages to one another and coordinating to accomplish a common objective (i.e., solving a computational problem). As I mentioned, there are hundreds of architectures for a distributed system. For example, a single computer can also be viewed as a distributed system: the central control unit, memory units, and input-output channels are separate processes collaborating to complete an objective.
分布式系统(确切地说应该是分布式计算机系统)从它诞生到现在已经过去了很长的时间。这种在多台计算机之间交换
分布式架构是数据库发展的大势所趋。分布式架构显著提升大容量数据存储和管理能力,既保障面对大量用户的高并发需求,又保障了面对业务变化的弹性增长能力。分布式数据库的使用成本,也远低于传统数据库。由于分布式架构主要使用
但是,一旦服务或系统的数量增加,这些服务之间的点到点连接就不再是可扩展和可维护的了。这催生了集中式“服务总线”概念的产生。服务总线通过类似集线器的架构将所有系统连接在一起。这个组件被称为
随着万维网的普及和模型的简化,基于
构建跨越多个地理区域和多个数据中心的分布式系统,他们不再把一台计算机当作一台计算机来看,而在同一台计算机内创建多台虚拟计算机。这催生了关于虚拟机的想法,即同一台计算机可以充当多台计算机并且全部并行运行。尽管这是一个还不错的主意,但在宿主计算机的资源利用方面,这并不是最好的选择。运行多个操作系统需要更多的资源,但在同一个操作系统里运行多个程序并不需要这些资源。容器只使用一个宿主操作系统(Linux)的内核,就可以运行多个程序并分别依赖于相互独立的运行时。这个概念在
基于容器的部署带来的轻量特性让跨多个容器的平台维护和编排变得非常复杂。随着微服务架构(MSA)的出现,单体式应用程序被分成更小块的微服务。这些微服务能够完成整个服务里的某一个特定功能并部署在容器中(在大多数情况下都可以
现代服务端架构,都可以称为分布式系统
微服务,分布式存储,分布式计算
Fallacies of Distributed Computing
网络是稳定的。网络传输的延迟是零。网络的带宽是无穷大。网络是安全的。网络的拓扑不会改变。只有一个系统管理员。传输数据的成本为零。整个网络是同构的。
分布式系统中的一致性问题其实是一个结果,本质是由于数据冗余导致的,如果没有冗余,也就不会有一致性问题了。一致性问题是结果,共识是为达到这个结果所要经过的过程,或者说一种手段。高可用的本质是通过相同数据存储多个副本,并都可对外提供服务。
分布式系统的产生我认为主要的目的就是“快”和“海量”。这个“快”可以分为两个方面:
第一个是系统的处理速度快。
第二个是开发的速度快(历时短
在考虑时间维度的情况下,不存在真正意义上的一致。况且我们在分布式系统中,也没有必要去达到真正的意义上的一致。因为越趋近于一致,系统相当于又归一成一个单体了,在某一个时刻,只能做一件事,完全丧失了分布式系统的两个目的之一“快”的优势。
系统中使用的大部分方案都是所谓的最终一致性,也就容忍一定条件下的不一致,优先保证局部一致,然后再通过一系列复杂的状态同步达到全局的一致。
分布式系统面临了几个问题:一致性问题,可终止性问题、合法性问题。
可终止性可以理解为系统必须在有限的时间内给出一致性结果,合法性是指提案必须是系统内的节点提出。当然其中面对的最重要也是最基础的问题,就是我们常说的一致性问题。
一致性是指在某个分布式系统中,任意节点的提案能够在约定的协议下被其他所有节点所认可。需要提醒你区分的一点是:这里的“认可”表示所有节点对外呈现的信息一致,而不是对信息的内容认可。一致性也分严格一致性、最终一致性。
非人为恶意的意外投票过程。非人为恶意篡改可归类为信鸽半路挂掉、信鸽迷路、信鸽送错目的地、信鸽送信途中下雨导致 信件内容模糊、接收信件的人不在家、天气变化信鸽延迟送达等等。这些对应到分布式系统面临的问题就是:消息丢包、网络拥堵、消息延迟、消息内容校验失败、节点宕机等。 人为恶意篡改投票过程。人为恶意篡改包括“精神分裂式投票”,中继篡改上一个村落的投票信息。对应到分布式系统面临的问题就是:消息被伪造、系统安全攻击等等。发生的人为恶意篡改的过程就可以称之为系统发生了拜占庭错误(Byzantine Fault),如果系统可以容忍拜占庭错误而不至于崩溃,也就是在发生系统被恶意篡改的情况下仍然可以达成一致,我们将这样系统称作为做拜占庭容错系统。
第一个是
严格一致性是指在约定的时间内,通常是非常短、高精度的时间内,系统达到一致性的状态,这种系统很难实现,即使实现也很难有高的性能。
所以人们从工程的角度提出了最终一致性,最终一致性不要求严格的短时间内达到一致。为了其他两个指标,我们 相当于让一致性在时间上做了妥协。区块链满足了最终一致性,而且处理过程时间比较长。
可用性其实是传统技术 后端架构上非常重要的指标,从单点到主备模式、从主备模式到异地多活,再到现在的
基础理论
CAP
对于大多数互联网应用来说
CA (consistency + availability),这样的系统关注一致性和可用性,它需要非常严格的全体一致的协议,比如“两阶段提交”(2PC)。
BASE
在分布式系统中,我们往往追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢?前人已经给我们提出来了另外一个理论,就是
Basically Available(基本可用)
Soft state(软状态)
Eventually consistent(最终一致性)
幂等性与分布式ID
本质是一个操作,无论执行多少次,执行结果总是一致的
幂等核心是全局唯一
分布式锁
在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增
悲观锁,先获取锁,再进行操作,吞吐量底 乐观锁,使用版本号方式实现,吞吐量高,可能出现锁异常,适用于多读情况 CAS,修改共享数据源的场景可以代替分布式锁
排他性,任意条件只有一个
Redis
1、为避免特殊原因导致锁无法释放
2、锁的生存时间默认比较短
3、系统级的锁当进程无论因为任何原因出现
4、但分布式锁不同。若一次性设置很长的时间,一旦由于各种原因进程
在分布式版本的算法里我们假设我们有
1、获取当前时间(单位是毫秒
2、轮流用相同的
3、客户端计算第二步中获取锁所花的时间,只有当客户端在大多数
4、如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
5、如果锁获取失败了,不管是因为获取成功的锁不超过一半(
1)读取当前状态
2)基于读取的状态进行计算得到新状态。
3)Conditional Update:如果计算结果基于的状态没有变,则更新,否则回到第一步。
MVCC
分布式事务
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
分布式系统区别于传统的单体应用,单体应用的服务模块和数据都在一个服务中,使用
分布式事务往往和本地事务进行对比分析,以支付宝转账到余额宝为例,假设有:
支付宝账户表:A(id,userId,amount)
余额宝账户表:B(id,userId,amount)
用户的
从支付宝转账
Begin transaction
update A set amount=amount-10000 where userId=1;
update B set amount=amount+10000 where userId=1;
End transaction
commit;
在Spring中使用一个@Transactional事务也可以搞定上述的事务功能:
@Transactional(rollbackFor=Exception.class)
public void update() {
updateATable(); //更新A表
updateBTable(); //更新B表
}
如果系统规模较小,数据表都在一个数据库实例上,上述本地事务方式可以很好地运行,但是如果系统规模较大,比如支付宝账户表和余额宝账户表显然不会在同一个数据库实例上,他们往往分布在不同的物理节点上,这时本地事务已经失去用武之地。
强一致性
分布式事务是指会涉及到操作多个数据库的事务。其实就是将对同一库事务的概念扩大到了对多个库的事务。目的是为了保证分布式系统中的数据一致性。分布式事务处理的关键是必须有一种方法可以知道事务在任何地方所做的所有动作,提交或回滚事务的决定必须产生统一的结果(全部提交或全部回滚
多阶段提交
两阶段提交,是实现分布式事务的成熟方案。第一阶段是表决阶段,是所有参与者都将本事务能否成功的反馈发给协调者;第二阶段是执行阶段,协调者根据所有参与者的反馈,通知所有参与者,步调一致地在所有分支上提交,或者在所有分支上回滚。
二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。第一阶段:准备阶段
2PC 两阶段提交
同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送
二阶段无法解决的问题:协调者在发出
两阶段提交协议

-
我们的应用程序
(client) 发起一个开始请求到TC ; -
TC 先将消息写到本地日志,之后向所有的 Si 发起消息。以支付宝转账到余额宝为例, TC 给A 的prepare 消息是通知支付宝数据库相应账目扣款1 万,TC 给B 的prepare 消息是通知余额宝数据库相应账目增加1w 。为什么在执行任务前需要先写本地日志,主要是为了故障后恢复用,本地日志起到现实生活中凭证 的效果,如果没有本地日志( 凭证) ,容易死无对证; -
Si 收到消息后,执行具体本机事务,但不会进行 commit ,如果成功返回,不成功返回 。同理,返回前都应把要返回的消息写到日志里,当作凭证。 -
TC 收集所有执行器返回的消息,如果所有执行器都返回yes ,那么给所有执行器发生送commit 消息,执行器收到commit 后执行本地事务的commit 操作;如果有任一个执行器返回no ,那么给所有执行器发送abort 消息,执行器收到abort 消息后执行事务abort 操作。
注:
现如今实现基于两阶段提交的分布式事务也没那么困难了,如果使用
不过但凡使用过的上述两阶段提交的同学都可以发现性能实在是太差,根本不适合高并发的系统。为什么?
3PC 三阶段提交
三阶段提交(Three-phase commit
如果因为协调者或网络问题,导致参与者迟迟不能收到来自协调者的
柔性事务与事务消息
根据
弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。
补偿机制TCC
TCC(Try、Confirm、Cancel)是两阶段提交的一个变种。
位于业务服务层而非资源层
没有单独的准备

Trying 阶段主要针对业务系统检测及作出预留资源请求,若预留资源成功,则返回确认资源的链接与过期时间。Confirm 阶段主要是对业务系统的预留资源作出确认,要求TCC 服务的提供方要对确认预留资源的接口实现幂等性,若Confirm 成功则返回204 ,资源超时则证明已经被回收且返回404 。Cancel 阶段主要是在业务执行错误或者预留资源超时后执行的资源释放操作,Cancel 接口是一个可选操作,因为要求TCC 服务的提供方实现自动回收的功能,所以即便是不认为进行Cancel ,系统也会自动回收资源。
本地消息表
消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过
消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
这种方案遵循
优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
消息重复投递
如果确认消息发送失败了怎么办?
如果
消费失败 解决超时问题的思路就是一直重试,直到消费端消费消息成功
消费超时
消费失败怎么办?阿里提供给我们的解决方法是:人工解决。大家可以考虑一下,按照事务的流程,因为某种原因
比如在北京很有名的姚记炒肝点了炒肝并付了钱后,他们并不会直接把你点的炒肝给你,往往是给你一张小票,然后让你拿着小票到出货区排队去取。为什么他们要将付钱和取货两个动作分开呢?原因很多,其中一个很重要的原因是为了使他们接待能力增强
还是回到我们的问题,只要这张小票在,你最终是能拿到炒肝的。同理转账服务也是如此,当支付宝账户扣除
还有一个很严重的问题就是消息重复投递,以我们支付宝转账到余额宝为例,如果相同的消息被重复投递两次,那么我们余额宝账户将会增加
解决方法很简单,在余额宝这边增加消息应用状态表
list for each msg in queue
Begin transaction
select count(\*) as cnt from message_apply where msg_id=msg.msg_id;
if cnt==0 then
update B set amount=amount+10000 where userId=1;
insert into message_apply(msg_id) values(msg.msg_id);
End transaction
commit;
业务与消息耦合的方式
支付宝在完成扣款的同时,同时记录消息数据,这个消息数据与业务数据保存在同一数据库实例里(消息记录表表名为message);
Begin transaction
update A set amount=amount-10000 where userId=1;
insert into message(userId, amount,status) values(1, 10000, 1);
End transaction
commit;
上述事务能保证只要支付宝账户里被扣了钱,消息一定能保存下来。当上述事务提交成功后,我们通过实时消息服务将此消息通知余额宝,余额宝处理成功后发送回复成功消息,支付宝收到回复后删除该条消息数据。
业务与消息解耦方式
上述保存消息的方式使得消息数据和业务数据紧耦合在一起,从架构上看不够优雅,而且容易诱发其他问题。为了解耦,可以采用以下方式。
优点:消息数据独立存储,降低业务系统与消息系统间的耦合;
缺点:一次消息发送需要两次请求;业务处理服务需要实现消息状态回查接口。
数据一致性
共识算法与分布式一致性算法
- 经典的分布式一致性算法
经典分布式一致性算法有
Raft 协议,Raft 协议是一种强Leader 的一致性算法,它的吞吐量基本就是Leader 的吞吐量,它无法抵御节点恶意篡改数据的攻击。
稍微复杂一点的就是
以上两种都是不提供拜占庭容错的系统,下面介绍一种具有拜占庭容错的一致性算法。
一致性分类
- 最终一致性,先保证局部一致,然后在未来的某个时刻达到顺序一致性
- 顺序一致性,保证所有进程以相同顺序看到所有的共享访问,与时间无关
- 线性化,在顺序一致性的基础上,还要保证在一个全局的相对时间下顺序 一致
- 绝对 一致性,所有共享访问按绝对时间排序,仅存在于理论中