后端研发工程师面经——分布式

7. 分布式

7.1 一致性算法
  • Paxos算法
    • Paxos 算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。一个典型的场景是,在一个分布式数据库系统中,如果各节点的初始状态一致,每个节点执行相同的操作序列,那么他们最后能得到一个一致的状态。为保证每个节点执行相同的命令序列,需要在每一条指令上执行一个“一致性算法”以保证每个节点看到的指令一致。zookeeper 使用的 zab 算法是该算法的一个实现。 在 Paxos 算法中,有三种角色:Proposer,Acceptor,Learners。
      • Proposer:只要 Proposer 发的提案被半数以上 Acceptor 接受, Proposer 就认为该提案里的 value 被选定了。
      • Acceptor:只要 Acceptor 接受了某个提案, Acceptor 就认为该提案里的 value 被选定了。
      • Learner:Acceptor 告诉 Learner 哪个 value 被选定, Learner 就认为那个 value 被选定。
    • Paxos 算法分为两个阶段。具体如下:
      • 阶段一(准 leader 确定 ) :
      • (a) Proposer 选择一个提案编号 N,然后向半数以上的 Acceptor 发送编号为 N 的 Prepare 请求。
      • (b) 如果一个 Acceptor 收到一个编号为 N 的 Prepare 请求,且 N 大于该 Acceptor 已经响应过的所有 Prepare 请求的编号,那么它就会将它已经接受过的编号最大的提案(如果有的话)作为响应反馈给 Proposer,同时该 Acceptor 承诺不再接受任何编号小于 N 的提案。
      • 阶段二(leader 确认) :
      • (a) 如果 Proposer 收到半数以上 Acceptor 对其发出的编号为 N 的 Prepare请求的响应,那么它就会发送一个针对[N,V]提案的 Accept 请求给半数以上的 Acceptor。注意: V 就是收到的响应中编号最大的提案的 value,如果响应中不包含任何提案,那么 V 就由 Proposer 自己决定。
      • (b) 如果 Acceptor 收到一个针对编号为 N 的提案的 Accept 请求,只要该 Acceptor 没有对编号大于 N 的 Prepare 请求做出过响应,它就接受该提案。
  • Zab方法
    • ZAB( ZooKeeper Atomic Broadcast , ZooKeeper 原子消息广播协议)协议包括两种基本的模式:崩溃恢复和消息广播。
    • 当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断崩溃退出与重启等异常情况时, ZAB 就会进入恢复模式并选举产生新的 Leader 服务器。
    • 当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后, ZAB 协议就会退出崩溃恢复模式,进入消息广播模式。
    • 当有新的服务器加入到集群中去,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器会自动进入数据恢复模式,找到 Leader 服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
    • 以上其实大致经历了三个步骤:
      • 1.崩溃恢复:主要就是 Leader 选举过程。
      • 2.数据同步: Leader 服务器与其他服务器进行数据同步。
      • 3.消息广播: Leader 服务器将数据发送给其他服务器。
  • Raft方法
    • 与 Paxos 不同 Raft 强调的是易懂(Understandability), Raft 和 Paxos 一样只要保证 n/2+1 节点正常就能够提供服务; raft 把算法流程分为三个子问题:选举(Leader election)、日志复制(Log replication)、安全性(Safety)三个子问题。
    • 角色
      • Raft 把集群中的节点分为三种状态: Leader、 Follower 、 Candidate,理所当然每种状态负责的任务也是不一样的。
      • Raft 运行时提供服务的时候只存在 Leader 与 Follower 两种状态;
      • Leader(领导者-日志管理)
        • 负责日志的同步管理,处理来自客户端的请求,与 Follower 保持这 heartBeat 的联系;
      • Follower(追随者-日志同步)
        • 刚启动时所有节点为Follower状态,响应Leader的日志同步请求,响应Candidate的请求,把请求到 Follower 的事务转发给Leader;
      • Candidate(候选者-负责选票)
        • 负责选举投票, Raft 刚启动时由一个节点从 Follower 转为 Candidate 发起选举,选举出Leader 后从 Candidate 转为Leader 状态;
    • Term(任期)
      • 在 Raft 中使用了一个可以理解为周期(第几届、任期)的概念,用 Term 作为一个周期,每个 Term 都是一个连续递增的编号,每一轮选举都是一个 Term 周期,在一个 Term 中只能产生一个 Leader;当某节点收到的请求中 Term 比当前 Term 小时则拒绝该请求。
    • Election(选举)
      • Raft 的选举由定时器来触发, 每个节点的选举定时器时间都是不一样的,开始时状态都为Follower 某个节点定时器触发选举后 Term 递增,状态由 Follower 转为 Candidate,向其他节点发起 RequestVote RPC 请求,这时候有三种可能的情况发生:
      • 1:该 RequestVote 请求接收到 n/2+1(过半数)个节点的投票,从 Candidate 转为 Leader,向其他节点发送 heartBeat 以保持 Leader 的正常运转。
      • 2:在此期间如果收到其他节点发送过来的 AppendEntries RPC 请求,如该节点的 Term 大则当前节点转为 Follower,否则保持 Candidate 拒绝该请求。
      • 3: Election timeout 发生则 Term 递增,重新发起选举在一个 Term 期间每个节点只能投票一次, 所以当有多个 Candidate 存在时就会出现每个Candidate 发起的选举都存在接收到的投票数都不过半的问题,这时每个 Candidate 都将 Term递增、重启定时器并重新发起选举,由于每个节点中定时器的时间都是随机的,所以就不会多次存在有多个 Candidate 同时发起投票的问题。
      • 在 Raft 中当接收到客户端的日志(事务请求)后先把该日志追加到本地的 Log 中,然后通过heartbeat 把该 Entry 同步给其他 Follower, Follower 接收到日志后记录日志然后向 Leader 发送ACK,当 Leader 收到大多数(n/2+1) Follower 的 ACK 信息后将该日志设置为已提交并追加到本地磁盘中,通知客户端并在下个 heartbeat 中 Leader 将通知所有的 Follower 将该日志存储在自己的本地磁盘中。
7.2 分布式锁
7.2.1 什么是分布式锁
  • 分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
  • 分布式锁应该具备哪些条件:
    • 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
    • 高可用的获取锁与释放锁
    • 高性能的获取锁与释放锁
    • 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
    • 具备锁失效机制,即自动解锁,防止死锁
    • 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
7.2.2 实现分布式锁的方式
  • 基于数据库实现排他锁
    • 通过数据库字段的唯一性限制,实现排他锁。
    • 缺点:
      • 1、这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
      • 2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
      • 3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
      • 4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
    • 解决方案:
      • 1、数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
      • 2、没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
      • 3、非阻塞的?搞一个while循环,直到insert成功再返回成功。
      • 4、非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。
  • 基于Redis的实现
    • redis命令说明:
    • (1)setnx命令:set if not exists,当且仅当 key 不存在时,将 key 的值设为 value。若给定的 key 已经存在,则 SETNX 不做任何动作。
      • 返回1,说明该进程获得锁,将 key 的值设为 value
      • 返回0,说明其他进程已经获得了锁,进程不能进入临界区。
      • 命令格式:setnx lock.key lock.value
    • (2)get命令:获取key的值,如果存在,则返回;如果不存在,则返回nil
      • 命令格式:get lock.key
    • (3)getset命令:该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。
      • 命令格式:getset lock.key newValue
    • (4)del命令:删除redis中指定的key
      • 命令格式:del lock.key
  • 基于zookeeper的实现
    • 基于zookeeper临时有序节点可以实现的分布式锁。每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。 (第三方库有 Curator,Curator提供的InterProcessMutex是分布式锁的实现)
    • Zookeeper实现的分布式锁存在两个个缺点:
      • (1)性能上可能并没有缓存服务那么高,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同步到所有的Follower机器上。
      • (2)zookeeper的并发安全问题:因为可能存在网络抖动,客户端和ZK集群的session连接断了,zk集群以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。
7.3 负载均衡算法
  • 轮询
    • 每次的请求到达时,对每个服务器都轮询访问,保证每个服务器命中概率相同,实现简单但无法解决不同服务器之间性能差异问题
  • 加权轮询
    • 权重高的服务器请求命中的概率更高,根据不同服务器的性能调整权重比可以降低服务器性能差异带来的问题
    • 算法实现上可以将所有的服务器连接对象放到一个list中,按权重比例放不同数量的连接对象到list,比如有三台服务器权重比是,1:2:3,list中的连接对象数量数可以是1,2,3,轮询访问这个list即可
  • 随机
    • 所有服务器随机访问,实现简单,服务器的命中概率取决于随机算法,无法解决不同服务器之前性能差异问题
  • 加权随机
    • 实现上可以参照加权轮询,生成的随机数作为list列表的索引值,也可以降低服务器性能差异带来的问题
  • IP Hash
    • 对请求的ip地址用hash算法映射到服务器上,保证一个客户端的所有请求都命中到一台服务器上,适合服务端保存客户端的状态,开启session会话的情况,但是不能跨服务器会话,如果服务器有新上线,下线,重启等导致服务器序号发生改变时会导致此种策略异常
  • URL Hash
    • 对url请求使用hash映射到指定服务器,可以配合缓存使用,在各服务器不共享缓存的情况下,对需要缓存的请求都打到一台服务器上避免其他服务器重复缓存
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值