一次学够分布式:CAP理论,分布式事务,分布式锁,分布式ID,服务限流等。满满干货!!!

本文章为你收集了分布式的基础理论,罗列了常见的分布式应用场景的实现方案:分布式锁,分布式事务,分布式主键ID(美团的Leaf),服务限流算法,一致性hash算法。同时每一部份内容笔者都详细的进行了收集整理并分析了每一种方案的优缺点。笔者但愿该文章可以成为网速比较全面的汇总文章,能为读者带来比较系统的讲解。若是读者发现文章内还有收集不全以及错误的地方还请在评论区留言,笔者会尽快完善文章内容。谢谢!

CAP理论:

  • C(一致性) 若是在某个节点更新了数据,那么在其余节点若是都能读取到这个最新的数据,那么就称为强一致,若是有某个节点没有读取到,那就是分布式不一致。即全部的节点同时看到相同的数据
  • A (可用性):非故障的节点在合理的时间内返回合理的响应。任什么时候候,读写都是成功的。
  • P (分区容错性):当部分节点出现消息丢失或故障的时候,分布式系统仍能正常工做。

CAP理论认为:一个分布式系统最多只能同时知足,一致性,可用性,分区容错性的三项中的两项。因为分区容错性是必然存在的,因此大部分分布式软件系统都在CP和AP中作取舍redis

  • Zookeeper 采用CP一致性,强调一致性,弱化可用性。
  • Eureka 采用AP可用性,强调可用性,弱化一致性。

Base理论

Base理论:即基本可用(Basically Available),软状态(Soft State),最终一致性(Eventually Consistent)。既然没法作到强一致性,那么不一样的应用可用根据本身的业务特色,采用适当的方式来达到最终一致性。Base理论是对CAP理论的实际应用算法

  • 基本可用性:不追求强可用性,并且强调系统基本可以一直运行对外提供服务,当分布式系统遇到不可预估的故障时,容许必定程度上的不可用,好比:对请求进行限流排队,使得部分用户响应时间变长,或对非核心服务进行降级。
  • 软状态:对于数据库中事务的原子性:要么所有成功,要不所有不成功。软状态容许系统中的数据存在中间状态。
  • 最终一致性:数据不可能一直都是软状态,必须在一个时间期限以后达到各个节点的一致性。在此以后,全部的节点的数据都是一致的。系统达到最终一致性。

分布式一致性算法

WARO:Write All Read One

一种简单的副本控制协议,当客户端向一个分布式应用发送写请求的时候,只有当全部的副本节点都更新成功以后,此次写操做才算成功。不然视为失败。这下降了写操做的可用性,提升了读操做的可用性。sql

Quorm:最终一致性

假设有N个副本,客户端向一个分布式应用发送写请求的时候,若是有W个副本更新成功以后,此次写操做才算成功。则读操做最多须要读N-W个副本就能读取到最新的结果。
Quorm没法保证强一致性,它是分布式系统中经常使用的一种机制,用来保证数据冗余的最终一致性的投票算法。Kafka的ISR机制有点相似该机制。数据库

Paxos算法:分布式一致性算法

在Paxos协议中,一共有三类角色节点segmentfault

  • Proposer 提案者
    提案者能够有多个,在流程开始时,提案者提出操做(被称为value)(好比修改某个变量的值),多个提案者能够提出不一样的操做。但通过一轮的Paxos算法后,只有一个提案者的操做被运行执行。
  • Acceptor 批准者
    在集群中,批准者有多个(数量设为N)。批准者之间彻底独立对等。提案者提出的操做,必须得到半数以上(N/2+1)的批准者批准后才能经过执行
  • Learner 学习者
    学习者不参与选举,而是执行被批准者批准的操做。

分布式事务:

分布式事务解决方案有 两阶段提交协议,三阶段提交协议,TCC分段提交,基于消息队列的最终一致性缓存

两阶段提交协议(2PC)

  • 两阶段提交系统中,存在一个节点做为协调者,其余节点为参与者。
  • 全部的节点都采用预写式日志。日志记录不会丢失。
  • 全部的节点不会永久性的宕机,即便宕机后仍能够恢复。
  1. 第一阶段:事务管理器要求每一个涉及到事务的数据库预提交此操做,并反映是否能够提交.
  2. 第二阶段:根据第一阶段的反馈,事务协调器要求每一个数据库提交数据,或者回滚数据。

缺点安全

  1. 事务管理器为单点,故障之后整个数据库集群没法使用
  2. 在执行过程当中,全部参与事务的节点都是事务独占状态,当有参与者占用公共资源时,那么其余第三方节点对公共资源的访问会被阻塞。
  3. 第二阶段中,假设协调者发出了事务commit的通知仅被一部分参与者所收到并执行,其他的参与者则由于没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

三阶段提交协议(3PC)

为解决两阶段提交协议中,公共资源占用堵塞的问题,三阶段提交协议中协调者和参与者都引入了超时机制,而后把两阶段提交协议里的第一个阶段拆分为两步:先询问(CanCommit),再锁资源(PreCommit),再最后提交(DoCommit)。服务器

  1. CanCommit:协调者向参与者发送Commit请求,参与节点反映是否能够调节。
  2. PreCommit:根据CanCommit响应状况有如下两种执行状况。网络

    • 若是全部的参与节点返回Yes,则进行事务的预执行:协调者向参与者发送PreCommit请求,使参与者进入Prepare阶段。并向协调者反馈ACk。
    • 若是任意一个节点返回了NO,或者等待超时进进行中断操做。则协调者向全部的参与者发送abort请求,参与者执行abort请求放弃事务的执行。
  3. DoCommit:阶段根据PreCommit的响应也有两种执行状况。并发

    • 若是协调者收到全部参与者的ACk响应,则发送doCommit请求,全部的参与者提交事务释放资源,并向协调者反馈ACK响应。
    • 若是协调者没有到全部参与者的ACK响应,则会执行中断事务

缺点:在DoCommit阶段中,假设协调者发出了事务commit的通知仅被一部分参与者所收到并执行,其他的参与者则由于没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

TCC

TCC:TCC是支付宝提出的分布式事务解决方案,每一个分布式事务的参与者都须要实现3个接口:try、confirm、cancel。

  1. Try阶段:调用方调用各个服务的 try 接口,各个服务执行资源检查和锁定,看本身是否有能力完成,若是容许,则锁定资源.
  2. Confirm阶段:各个服务的 try 接口都返回了 yes,则进入提交阶段,调用方调用各个服务的 confirm 接口,各个服务对 try 阶段锁定的资源进行处理。若是 try 阶段有一方返回了 no,或者超时,调用方调用各个服务的 cancel 接口,释放锁定的资源
  3. Cancel阶段:取消执行,释放Try阶段预留的业务资源
  • Confirm阶段和Cancel阶段是互斥的,只能进行一个,并且都是幂等性的,容许失败重试。

优势

1. TCC解决了跨服务的业务操做原子性问题,可让应用本身定义数据库操做的粒度,下降锁冲突,提升系统的业务吞吐量。
2. TCC的每一阶段都由业务本身控制,避免了长事务,提升了性能。

缺点

1. 业务侵入性强:业务逻辑必须都要实现Try,Confirm,Cancel三个操做

异常状况

  • 空回滚
    现象是 try 没被执行,就调用了 cancel:调用 try 时出现异常,try 接口实际没有被调用,天然没有返回 yes,那么会按照正常流程进入第2阶段,调用 cancel 接口,这就造成了空回滚。
    解决方法:让 cancel 可以识别出这是一个空回滚,能够记录事务执行状态,cancel 中判断 try 是否执行了。

  • 重复调用
    提交阶段异常时可能会重复调用 confirm 和 cancel,因此要实现幂等,保证屡次执行效果一致。

解决方法:记录事务执行状态,若是执行过了,就再也不执行。

接口幂等性:指的是在调用方屡次调用的状况下,接口最终获得的数据是一致的。查询接口具备自然的幂等性。
  • 悬挂
    现象是先执行了 cancel,后执行的 try,形成资源没人释放:调用 try 时网络拥堵超时,被认为失败,而后调用 cancel,这时事务至关于结束了,但后来网络好点以后 try 开始执行了,锁定了相关资源,由于事务已经结束,confirm、cancel 都不会再调用了,就形成资源悬挂,没法释放。
    解决方法:仍是记录事务执行状态,try 执行时判断 cancel 是否执行了。

MySql内部的XA事务规范

XA事务是基于两阶段提交协议的,XA规范主要定义了事务协调者和资源管理器之间的接口。

  1. 事务协调者:用来保证全部的事务参与者都完成了准备工做。若是事务协调者收到全部参与者都准备好的消息,就会通知全部的事务能够提交。
  2. 资源管理器:负责控制和管理实际资源。

XA事务执行流程与两阶段提交协议差很少。

  1. Prepare阶段: 事务管理者向全部的资源管理器发送prepare指令,管理器收到指定后执行数据操做和日志记录。而后反馈结果。
  2. Commit阶段:事务协调者接收到全部的资源管理器的结果,选择执行RollBack命令或者Commit命令。完成一次事务操做。

MySQL中的XA事务有两种状况,内部XA和外部XA。若是事务发生在MySQL服务器单机上使用内部XA,若是事务发生在多个外部节点上,使用外部XA。

内部XA: Mysql会同时维护binlog日志与InnoDB的redolog,为了保证两个日志一致性,MySql使用了XA事务。当有事务提交时:

1. 第一步:InnoDB进入Prepare阶段,将事务的XID写入redo日志。binlog不作任何操做。
2. 第二步:进行写binlog日志,也将XID写入binlog。
3. 第三部:调用InnoDB引擎的Commit完成事务的提交。而后将Commit信息写入redo日志。

分布式锁实现方案汇总

基于数据库的主键实现方案

得到锁:当要锁住某一个资源时,就在表中插入对应的一条记录。
释放锁:删除对应的记录。
基于数据库实现分布式锁的方案实现简单,但有不少的问题存在。

  1. 存在单点故障:一旦数据库挂掉,整个业务系统不可用。能够配置主从,防止单点故障。
  2. 超时问题:若是解锁操做失败,会致使锁一直在数据库中,其余线程没法得到锁。能够添加独立的定时任务,经过时间戳等方式删除超时数据。
  3. 不可重入:同一个线程在没有释放锁以前不能再次得到该锁。实现可重入须要改造加锁方法,增长存储和判断线程信息。
  4. 阻塞等待问题:其余线程在请求对应的资源时,插入数据失败,会直接返回,不会阻塞线程。故线程内要作循环插入判断,对数据库操做较大的资源浪费。
  5. 主从问题:在高并发的场景下,数据库主从延时增大,线程读取的数据非最新版,致使锁重复。

基于ZooKeeper的实现方案

利用Zookeeper支持的临时顺序节点的特性,能够实现分布式锁。

独占锁-使用临时节点实现

得到锁: 当要对某个资源加锁时,Zookeeper在该资源对应的指定的节点目录下,生成一个惟一的临时节点。其余客户端对该节点设置一个Watcher通知。
释放锁:Zookeeper删除对应的临时节点,其余客户端能够监听到节点被删除的通知,并从新竞争锁。

读写锁-使用临时有序节点实现

得到读锁:

  1. 得到临时有序节点,并标记为读锁
  2. 获取资源路径下全部的子节点,从小到大排序。
  3. 获取当前临近节点前的临近写锁节点。
  4. 若是不存在临近写锁节点,则成功得到读锁
  5. 若是存在临近写锁节点,则设置Water监听该节点的删除事件。
  6. 一旦监听到删除事件,重复2,3,4,5的步骤。

得到写锁

  1. 建立临时有序节点,并标记为写锁。
  2. 获取路径下的全部子节点,并进行从小到大排序。
  3. 获取当前节点的临近的写锁节点和读锁节点。
  4. 若是不存在临近节点,则成功获取锁。
  5. 若是存在临近节点,对其进行监听删除事件。
  6. 一旦监听到删除事件,重复2,3,4,5的步骤(递归)。

释放锁
删除对应的临时节点。

基于Redis的实现方案

原理:在获取锁以前,先查询一下以该锁为key对应的value是否存在,若存在,说明该锁被其余客户端获取了。

改进1:为了防止获取锁的客户端忽然宕机,须要在设置key的时候,指定一个过时时间,以确保即便宕机了,锁也能最后释放。经过SETNX命令设置key的值,经过EXPIRE命令设置过时时间。

改进2:因为SETNX和EXPIRE命令的执行不是原子性的,多个客户端在检验锁是否存在时会致使多个客户端都认为本身能获取到锁。Redis提供了
Set原子性命令,在设置值的同时指定过时时间。

改进3:客户端获取锁之后任务未执行完,但锁已通过期,被别的客户端获取了,这时客户端扔会释放锁,致使锁失效。能够在设置key的时候,设置value为一个随机值r,删除的时候先比较一下redis里的value是否为r再决定删除。

改进4:客户端先比较一下redis的value是否为r再决定删除,但因为比较后再删除锁不是原子的。在比较过程当中,key有可能因过时而被清除了致使一个客户端就能够获取到key。Redis并无提供相关的比较后删除的原子操做。此时释放锁的过程可使用lua脚本,redis将lua脚本的命令视为一个原子操做。

分布式惟一ID生成系列

UUID:

UID使用以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字来生成一串惟一随机32位长度数据。
优势:性能好,本地生成,全局惟一。
缺点

  1. UUID长度固定为32位,对于Mysql索引来讲,全部的非主键索引都会包含一个主键,UUID长度过长会不利于MySql的存储和性能。
  2. UUID是乱序的,每一次UUID数据的插入都会对主键地城的b+树进行很大的修改。
  3. 信息不安全,UUID里包含了MAC地址,芯片ID码能信息。会形成信息泄露。

数据库自增ID

对于多台数据库,经过每台数据库的起始值增值和自增值的跨度,能够实现全局的自增ID。以4台数据库为例,以下表

数据库编号 起始值增值 自增值的跨度 生成的主键序列
1 1 4 [1,5,9,13,17,21,25,29.....]
2 2 4 [2,6,10,14,18,22,26,30....]
3 3 4 [3,7,11,15,19,23,27,31....]
4 4 4 [4,8,12,16,20,24,28,32....]

优势:容易存储,能够直接用数据库存储。
缺点

  1. 统水平扩展比较困难,定义好步长和机器台数以后,再增长数据库须要重调整全部的数据库起始值增值和自增值的跨度。
  2. 数据库压力大,每次获取ID都会写一次数据库。
  3. 信息不安全,递增性太强。很容根据两个ID的差值判断竞争对手的中间的出单量。

Snowflake

snowflake生成id的结果是一个64bit大小的整数。由一位标识位,41个比特位的时间戳,10位的机器位,能够标识1024台机器,还有就是10比特位的自增序列化组成。结构以下:

image

优势:趋势递增,不依赖第三方组件(数据库等),性能更高,能够根据自身业务特色动态分配bit位。
缺点:强依赖机器时钟,若是出现时钟回拨,那么整个系统生成的ID将会不可用。

美团Leaf

leaf提供了的两种模式。

Segment模式

Segment模式在以前数据库方案基础之上进行了优化。该模式不是每次都获取ID都操做一次数据库,而是异步的一次性的从数据库中取N个ID组成一个号段,而后放入本地缓存。同时采用双buffer 方法,在第一个号段下发了必定的百分比时,就会有另外一个线程启动来获取并更新下一个号段的缓存数据。

优势

  1. Id单调递增,经过内部有号段缓存,数据库挂了依旧可以支持一段时间。

缺点

  1. 号段太短会致使DB宕机容忍时间变短,号段过长会致使ID号跨度过大。能够根据号段使用状况动态调整号段跨度。
  2. 最终仍是强依赖DB

Snowflake模式

美团Leaf的Snowflake像较于普通的Snowflake,有两点改进。

  1. 10位的workID是应用在zookeeper注册的顺序节点的序号。
  2. 时钟回拨问题:应用会将定时将本身的时间写入zookeeper中,同时会将本地时间和zookeeper的存储的时间作比较。若是差值超过设置阈值则认为是本地服务的时间发生回拨。

服务限流算法:

计数器

从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,若是累加的数字达到了100,那么后续的请求就会被所有拒绝。等到1s结束后,把计数恢复成0,从新开始计数.可以使用redis的incr原子自增性和线程安全便可轻松实现。
若是我在单位时间1ms内的前10ms,已经经过了100个请求,那后面的990ms,请求所有会被拒绝,即:突刺现象。

滑动窗口算法

滑动窗口算法是将时间周期分为N个小周期,分别记录每一个小周期内访问次数,而且根据时间滑动删除过时的小周期,滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确

漏桶算法

算法内部有一个容器,无论上面流量多大,下面流出的速度始终保持不变。能够准备一个队列,用来保存请求,另外经过一个线程池按期从队列中获取请求并执行,能够一次性获取多个并发执行。

令牌桶算法:

算法中存在一种机制,以必定的速率往桶中放令牌。每次请求调用须要先获取令牌,只有拿到令牌,才有机会继续执行,不然选择选择等待可用的令牌、或者直接拒绝。能够准备一个队列,用来保存令牌,另外经过一个线程池按期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。guava的RateLimiter能够简单生成一个令牌限流器。
集群限流:每次有相关操做的时候,就向redis服务器发送一个incr命令,好比须要限制某个用户访问/index接口的次数,只须要拼接用户id和接口名生成redis的key,每次该用户访问此接口时,只须要对这个key执行incr命令,在这个key带上过时时间,就能够实现指定时间的访问频率。

一致性Hash算法:

使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求起到负载均衡的做用。可是普通的hash算法伸缩性不好,当新增或者下线服务器机器时候,用户id与服务器的映射关系会大量失效。一致性hash则利用hash环对其进行了改进。
一致性hash:将全部的服务器散列值当作一个从0开始的顺时针环,而后看请求的hash值落到了hash环的那个地方,在hash环上的位置顺时针找距离最近的ip做为处理服务器

image.png

相关文章
相关标签/搜索