不如了解一下分布式系统?

分布式

什么是分布式?什么是微服务?

  1. 分布式是把项目分布到多个服务器中,每个服务器可能运行一个或多个业务。微服务是把项目切分成若干个可独立运行的微服务,可以分布在一个或多个服务器上。本质上说,是两个不同层面的东西,但是因为实际工程中,微服务常常要利用到分布式。所以:微服务是架构设计方式,分布式是系统部署方式
  2. 微服务框架逻辑视图

    REST其实就是向外界提供了由一个语言无关的调用API,因为微服务就是一个独立的服务,任何请求都可以调用。微服务因为要被调用,因此要实现RPC。微服务本身可能需要集群或者分布式部署。一个大项目可以切分成很多微服务,然后让不同的团队进行开发,这就是微服务架构。
  3. 集群是把同一个业务拆分成多个子业务,部署在不同的服务器上。分布式的每一个节点都可以做集群。
  4. SOA,Service-Oriented Architecture,面向服务的架构。举例来说,比如有多个客户端向访问同一个数据库,你应该怎么做呢?方案A:多个客服端写程序访问数据库服务器。方案B:服务器写程序,多个终端通过服务器提供的接口(REST+HTTP Method,或者基于Socket的RPC)来完成远程调用。注意,虽然我这里说的是多个客户端向服务端请求,但是本质上,也可以是不同服务之间互相请求 好处是各个客户端(或服务端)可以由不同的语言处理,因为相互调用的API是语言无关的,每个客户端(或服务端)都是可以由独立的团队开发维护的。除此之外,SOA还要有服务治理,当服务越来越多时,作为服务的提供方希望知道为谁提供了服务,哪些服务是热点,这些都是服务治理需要去做的。

分布式锁

为什么要有分布式锁?

  1. 在单机情况下,可以用语言层面的锁实现进程同步,但是在分布式场景中,需要同步的进程可能位于不同节点上,那么就需要使用分布式锁。
  2. 阻塞锁通过互斥量来实现。

分布式锁的实现方案概述

数据库的唯一索引

  1. 获得锁时向表中插入一条记录,释放锁时删除记录。唯一索引可以保证记录只被插入一次,因此这个记录可以被用来判断是否处于锁定状态。
  2. 存在以下问题:
    • 锁没有失效时间,解锁失败而话,其他进程无法获得锁。
    • 只能是非阻塞锁,插入失败直接报错,无法重试。
    • 不可重入,已经获得锁的进程必须重新获取锁。

Redis的SETNX指令

  1. 使用SETNX(set if not exist)指令插入一个键值对,如果key已经存在,那么就返回false,插入成功返回true。setnx本质上和数据库插入唯一索引类似,保证了只存在一个key的键值对,那么就可以用一个key的键值对判断是否处于锁定状态。但是和数据库项目,可以通过expire指定为键值对设置一个过期时间,从而避免释放锁失败的问题。注意,因为redis的删除方式是惰性删除(被访问时发现失效则删除,周期性的从失效的主键中选择一部分删除)+注定删除(超过一定限制是,触发主动情理)。

Redis的ReadLock算法

  1. 使用多个redis来实现分布式锁,保障单点故障时依然可用。尝试从N个互相独立的Redis实例获取锁,计算获取锁消耗的时间,只有当这个时间小于锁的过期时间,并且从大多数实例上获取了锁,才认为是获取成功了,如果锁获取失败,那每个redis实例都需要释放锁。

ZooKeeper的有序节点

  1. Zookeeper抽象模型 一种树形结构的命名空间
  2. 节点类型
    • 永久节点:不会因为会话结束或超时而消失
    • 临时节点:如果会话结束或超时就会消失
    • 有序节点:会在节点名的后面加一个数字后准,并且有序的。比如有序节点为/lock/node-000,那下一个就是/lock/node-001
  3. 监听器 为一个节点注册监听器,在节点状态发生改变时,会给客户端发送消息
  4. 分布式锁实现
    • 创建一个锁目录/lock
    • 当一个客户端需要获取锁时,在/lock下创建临时的且有序的子节点
    • 客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听自己的前一个子节点,获得子节点的变更通知后- 重复此步骤直到获取锁。
    • 执行代码,完成后,删除相应的子节点。
    • 这TM根本就是AQS完全一样的思路啊,所谓的同层临时有序节点不就是同步队列么,监听不就是CAS一直判断自己是否在头结点么,根本就是完全一样的思路啊。
  5. 会话超时 如果一个已经获得锁的会话超时了,因为创建的是临时节点,所以该会话对应临时节点会被删除,其他会话就可以获得锁。可以看到,Zookeeper分布式锁不会出现数据库的唯一索引实现的分布式锁释放锁失败的问题
  6. 羊群效应 一个节点未获得锁,只需要监听自己的前一个子节点。解释那么多,这TM就是AQS的思路,只有头结点能获得同步状态就完事儿了。

分布式事务

什么是分布式事务

  1. 事务的操作位于不同的节点上,但是仍然可以保证事务的ACID特性,比如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
  2. 如下图,购买的核心是减库存,生成订单。如果减库存和插入订单是在两个微服务中完成的,那么可能存在库存减少但插入订单失败的可能,由此产生了不一致的情形。

2PC

什么是2PC

  1. 两段式提交(2-phase commit,2PC),通过引入协调者Coordinator来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。

运行过程

  1. 准备阶段,协调者询问参与者事务是否执行成果,参与者返回事务执行结果
  2. 提交阶段,如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务,否则,协调者发送通知,让参与者回滚事务。需要注意的是,在准备节点,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发类似的通知后,才进行提交或者回滚。 那如果在协调者发送通知后发生了问题,不还是没办法保证事务的一致性么?
    [外链图片转存失败(img-6LBsdfyW-1566629277159)(https://www.kunzhao.org/blog/2018/06/22/consistency-problem-of-the-distrubuted-system/two_phase_commit_protocol.jpg)]
    因为本质上数据库通过日志先行的方式记录了,哪怕是提交失败,也会通过日志一起回滚。

存在问题

  1. 同步阻塞 所有事务参与者在登台其他参与者响应时,都处于同步阻塞状态,无法进行其他操作。
  2. 单点问题 协调者在2PC中起到非常大的作用,发生故障会造成很大影响,特别是阶段二发生故障,会导致所有参与者一直阻塞
  3. 数据不一致 如果在阶段二,只有部分参与者收到了协调者发送的Commit消息,那么只会有部分参与者提交了事务,使得系统数据不一致
  4. 过于保守 任意一个节点失败就会导致整个事务失败,没有完善的容错机制

3PC

  1. CanCommit阶段。 事务协调者确认所有参与者是否可以完成事务。
  2. PreCommit阶段。 如果阶段一全部返回yes,就进入PreCommit阶段预提交。此时,协调者会通知所有参与者执行事务,并将redo和undo记录到事务日志中,向协调者返回ack代表可以提交。注意! 如果阶段一中有任何一个参与者节点返回的结果是No,或者协调者在等待参与者节点反馈的过程中超时,整个分布式事务中断。
  3. DoCommit阶段 协调者通知所有参与者进行提交。
  4. 相比于2PC优化的点在哪里? 对协调者和参与者都设置了超时时间,一旦超时,协调者和参与者都会提交事务。
  5. 存在问题: 如果因为网络原因没有收到协调者的Abort则会提交事务,从而产生数据不一致的现象。

TCC

  1. TCC是一种补偿型柔性事务,在一个长事务中,比如服务器A发起事务,服务器B参与事务,那么A执行后先提交,B执行后后提交,所谓补偿是指,如果B回滚,那就对A执行反操作。
  2. TCC指的是try,confirm,cancel三个方法,这个是写在业务里面的,而2PC和3PC都是资源层面的。那么优势就很明显了,TCC控制的粒度是可自定义的,降低了锁冲突,提高了吞吐量。但是因为对业务有侵入性,所以实现难度比较大。

本地消息表

  1. 本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对着两个表的操作满足事务特性,并且使用消息队列来保证最终一致性。
    • 在分布式操作的一方完成写业务数据的操作后,向本地消息表发送一个消息,本地事务能保证这个消息一定被写入本地消息中
    • 之后,将本地消息表中的消息转发到消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发
    • 在分布式事务操作的另一方,从消息队列读取一个消息,并执行消息中的操作。
  2. 上图也很明显了,这种模式可以保障的是最终一致性,因为需要用消息队列来保证一致性。

CAP

什么是CAP

  1. C:一致性 A:可用性 P:分区容错性,CAP最多只能满足两项。
  • 一致性 多个数据副本能否保持一致性,在一致性的条件下,系统在执行数据更新操作之后能够从一个一致性状态转移到另一个一致性状态。如果对系统的一个数据更新成功后,所有用户都能够读取到最新的值,就被人为是强一致性的。
  • 可用性 在分布式系统面对各种异常时可以提供正常服务的能力,对于用户的每一个请求总能在有限时间内返回结果。
  • 分区容错性 网络分区指的是分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信(这里的无法通行是被动地,本来整个分布式系统都可以连通,但是因为网络问题而导致变成了一个个独立的分区)。在分区容忍性条件下,分布式系统在遇到任何网络分区故障时,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。
  • 权衡:分区容忍性必须要,那就是在可用性和一致性间做取舍。为了保证一致性,不能访问未同步完成的节点,失去了部分可用性。为了保证可用性,允许读取所有节点的数据,但是可能不一致。
  • 举例说明:N1的数据已经更新到y,但是N2因为网络的问题还未更新,此时N2应该返回什么呢?如果返回x,就满足了可用性,但是不满足一致性,如果不返回,就满足了一致性,但是不满足可用性。

实际案例

  1. 交易系统必然采用关系型数据库+强悍硬件。NoSQL用于数据分析,日志,推荐等
  2. 以用户管理为例,在实现账号数据时,可以采用消息队列来实现CA,实时性较好,但实现较为复杂,用户信息可以用过数据库通过来实现CA,但是延迟较高。

BASE

什么是BASE

  1. BA:基本可用 S:软状态 E:最终一致性。核心思想:既然无法做到强一致性,但是每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
  • 基本可用 分布式系统出现故障时,保证核心可用,允许损失部分可用性。 比如电商促销时,为了保证购物的稳定性,部分页面或部分功能将被降低等级。
  • 软状态 允许分布式系统中多个节点的数据存在中间状态,即允许不同节点的数据同步之间存在时延
  • 最终一致性 所有节点的数据,经过一段时间的同步后,最终总能达到一致。
  1. 牺牲强一致性而获得可用性,要求系统在处理请求的过程中可以存在短暂的不一致,在这个时间窗口内,请求的每一步操作,都要记录下来,

一致性算法

分布式系统可能存在的一致性该问题

  1. 举例说明
    (1) 先下订单还是先扣库存?下订单成功扣库存失败则超卖;下订单失败扣库存成功则多卖。
    (2) 系统 A 同步调用系统 B 服务超时后,这个时候系统 A 应该做什么?
    (3) 系统 B 异步回调系统 A 超时后,系统 A 迟迟没有收到回调结果怎么办?
    (4) 某个订单在系统 A 中能查询到,但是系统 B 中不存在。
    (5) 系统间都存在请求,只是状态不一致。
    (6) 交易系统依赖于数据库的 ACID,缓存和数据库之间如何保持一致性?强一致性还是弱一致性?
    (7) 多个节点上缓存的内容不一致怎么办?请求恰好在这个时间窗口进来了。
    (8) 缓存数据结构不一致。某个数据由多个数据元素组成,如果其中某个子数据依赖于从其它服务中获取数据,假设这部分数据获取失败,那么就会导致数据不完整,可能会出现 NullPointerException 等。
  2. 个人理解:分布式系统中,可能有某些业务数据需要在不同的系统中得到统一,即一致。比如说A,B,C三个系统,现在需要通过一致性算法,使得在有限时间内内,A,B,C的数据相同。A,B,C的数据最终必须统一为一个,不允许A,B相同但是C不同。

Paxos

算法目的

  1. 一致性算法,对于多个节点产生的值,该算法能保证只选出唯一一个值。
  2. 实际案例:在异步环境下,比如一个值A,要向三台服务器写,但是因为网络、故障、乱序等问题,会导致三台服务器可能出现缺写、顺序不一致等现象。

https://mp.weixin.qq.com/s?__biz=MzI4NDMyNTU2Mw==&mid=2247483695&idx=1&sn=91ea422913fc62579e020e941d1d059e&scene=0#wechat_redirect
我们需要转换一下切入点,也许我们需要paxos确定的值,并不一定是我们真正看到的数据。我们观察大部分存储系统,如LevelDB,都是以AppendLog的形式,确定一个操作系列,而后需要恢复存储的时候都可以通过这个操作系列来恢复,而这个操作系列,正是确定之后就永远不会被修改的。到这已经很豁然开朗了,只要我们通过paxos完成一个多机一致的有序的操作系列,那么通过这个操作系列的演进,可实现的东西就很有想象空间了,存储服务必然不是问题。

执行过程

  1. 规定一个提议包括两个字段[n,v],其中n为序号(具有唯一性),v为提议值。
  2. Prepare阶段 两个Proposer和三个Acceptor。首先两个Proposer向Acceptor发送prepare请求,携带一组[n,v]。当Acceptor收到[n1,v1]后,不再接受比n1更小的序号。如果n1之前没有序号,就返回[no previous],否则返回[n0,v0].
  3. Accept阶段 一个Proposer接收到超过一半的Acceptor的Prepare响应后,就可以发送Accept请求。那proposer在Acceptor阶段发送的不再是[n1,v1],而是——如果Proposer收到的超过一半的响应为[n2,v2] [n2,v2] [n3,v3]且n3>n2,此时发送的为[n1,v3],即proposer在accept阶段会改变自己的提案,改成获得到最大序号的V值。

Raft

算法目的

  1. 分布式一致性协议,主要用于竞选主节点。

单个Candidate的竞选

  1. 有三种节点:Follower,Candidate和Leader。Leader会周期性的发送心跳包给Follower。每个Follower都设置了一个随机的竞选超时时间,一般为150ms~300ms,如果在这个时间内没有收到Leader的心跳包,就会变成Candidate,进入竞选阶段。重点:一段时间内未收到Leader发来的心跳包,则进入竞选阶段

    变为Candidate的节点会发送投票请求给其他所有节点,如果有超过一半的节点回复,则Candidate变为Leader。

多个Candidate的竞选

  1. 多个Follower变成Candidate,且获得同样的票数,则需要重新开始投票,直到选出一个票数多的。

数据同步

  1. 客户端的修改会被传入Leader。此时修改还未被提交,只是写入日志。
  2. Leader会把修改复制到所有Follower
  3. Leader会等待大多数的Follower进行修改,然后才将修改提交。
  4. Leader会通知所有Follower让他们也提交修改,此时所有的节点的值达成一致。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章