# 简介

# 分布式系统

:是指将一个软件系统的各个进程部署不同主机上。

  • 小型的软件系统通常只部署在一台主机上,属于集中式系统。而大型的软件系统通常部署成分布式系统,从而提高性能。
  • 优点:
    • 便于横向增加系统节点,提高系统容量、性能,比如处理高并发流量。
    • 可以将同一个程序运行多个实例,一个实例挂掉了就用其它实例,实现服务的高可用。
    • 可以将一个数据存储多个副本。
  • 难点:
    • 系统规模变大、结构变复杂,维护麻烦。
    • 同一个程序要运行多个实例,冗余多、成本高。

# 共识

:Consensus ,指系统中不同节点作出的决策相同。

  • 脑裂(brain split)
    • :指一个系统中存在多个有权决策的节点,并且作出了不同决策。
  • 拜占庭将军问题(Byzantine Generals Problem)
    • :多个拜占庭将军自主观察敌情,然后通过投票决定进攻还是撤退。但可能存在不诚实的将军,或者投票信件被丢失、篡改。
    • 该问题代表系统中某些节点传播虚假的信息,导致其它节点作出了错误决策。

# Paxos

:一个共识算法,于 1990 年发布。

  • 系统中一些节点担任 Proposer ,有权发出提议(Proposal)、投票。
    • 其它节点担任 Acceptor ,有权投票。
  • 每个提议需要超过半数的节点投票同意,才能通过。
    • 这属于多数派(Majority)策略,允许低于半数的节点不可用。

# Raft

:一个共识算法,在 Paxos 算法的基础上作了改进。

  • 系统中有且仅有一个节点担任 Leader ,有权发出提议(Proposal)、投票。
    • 其它节点担任 Follower ,有权投票。
    • Leader 会定期发送心跳包给其它 Follower 节点。如果其它节点超过一定时长没有收到心跳包,则变为 Candidate 状态,选举一个节点担任新 Leader 。
      • 每次选出新 Leader ,就开始一个新任期,称为 Term 。
  • 每次准备修改集群数据时,Leader 会将该提议发送给所有 Follower ,等超过半数的节点同意并执行之后,才通过该提议,从而达成共识、数据一致性。
    • 超过半数,又称为达到法定成员数(Quorum)。
      • 当节点总数为 N 时,法定成员数为 Quorum = N/2 + 1 ,其中 / 为整除运算符。
      • Quorum 必须超过半数。如果允许 Quorum 等于集群半数,甚至小于半数,则集群就可能同时存在不止一个 leader ,发生脑裂。
    • 并不会等到所有节点都同意,因此属于最终一致性。
    • 该共识是容错的,允许 Quorum 之外的节点不可用。
      • 增加节点总数可以提供系统可用性,但是会增加每次达成共识的耗时。
    • 每个节点都信任其它节点发来的信息,因此不能实现拜占庭容错。
  • Raft 集群应该部署奇数个节点。如果部署偶数个节点,但 Quorum 不变,则并不会提高可用性。
    • 至少需要部署 1 个节点,但此时没有冗余节点,存在单点故障的风险。
    • 建议部署 3 或 5 个 节点,此时允许 1 或 2 个 节点故障,提高可用性。
    • 如果部署 7 个或更多节点,则可用性没有提高很多,反而增加了写操作的耗时。

# Bully

:一个共识算法,与 Raft 算法相似。

  • 如果 Leader 节点故障,则由 ID 最大的一个节点担任新 Leader 。

# Gossip

:一个广播消息的协议,常用于 P2P 服务。

  • 每个节点定期散播一次消息,最多发送给 k 个节点。
    • 发送消息之后,不必确保对方接收。
    • 其它节点收到消息之后,会散播给除了源节点之外的其它节点。
  • 消息被病毒式传播,能实现最终一致性。

# PoW

:工作量证明(Proof of Work),区块链的一个共识算法。

  • 例如比特币采用基于哈希函数的 PoW 算法:
    • 以区块为单位写入数据。
    • 一些节点担任矿工,负责生成新区块,即写入数据到区块链。
      • 每个矿工节点需要进行大量哈希运算,穷举猜测下一个区块的 nonce 随机数,第一个猜出来的节点有权生成该区块,然后广播给其它节点。
      • 生成新区块的矿工,可以获得该区块产生的一定量 BTC 作为奖励。
    • 非矿工的普通节点,只能读取区块链,或者向矿工发送写入请求。
  • 可实现顺序一致性,并实现拜占庭容错。

# PoS

:权益证明(Proof of Stake),区块链的一个共识算法。

  • 例如以太坊采用 PoS 算法:
    • 一些节点质押 32 个 ETH ,获得成为验证者的资格,负责投票验证新区块是否有效。
      • 如果行为不诚实,或者没有准时验证新区块,则质押的 ETH 销毁一部分,作为惩罚。
      • 如果工作称职,则质押的 ETH 少量增加,作为奖励。
    • 每个出块周期,随机选取一个验证者来打包新区块,然后广播给其它节点。
  • 与 PoW 相比,PoS 去掉了工作量成本,大幅降低了挖矿消耗的计算机硬件、电能。

# 一致性

:Consistency ,指系统中不同节点拥有的数据副本一致(通常还应该是最新的数据)。

  • 数据库的 ACID 指的是事务的一致性,而分布式系统中主要研究数据的一致性。
  • 通常,需要各节点先达成共识,才能实现数据一致性。
    • 不过数据不一致性时,各节点可能因为不同的数据副本而作出不同的决策,不容易达成共识。
  • 每个写操作之后,如果等所有节点复制完新数据,才开始下一个读操作,则称为同步复制,否则称为异步复制。
  • 常见的几种一致性模型:
    • 强一致性:采用同步复制,保证各节点的一致性。
      • 严格一致性(Strict consistency)
        • :每个写操作之后,各节点会立即变为一致,即实时复制。比如将写操作复制到各节点上同时执行。
      • 顺序一致性(Sequential consistency)
        • :当程序发出多个读写操作时,各节点会按相同顺序执行这些操作。因此某些节点可能因为执行慢而数据滞后,但顺序并不会出错。
      • 线性一致性
        • :具有实时性的顺序一致性,各节点同时遵守相同顺序。每个写操作在所有节点都生效之后,才会开始下一个操作。
        • 又称为可线性化(Linearizability)、原子一致性。
        • 强弱程度:严格一致性 > 线性一致性 > 顺序一致性
    • 弱一致性:采用异步复制,因此各节点可能不一致。
      • 因果一致性(Causal consistency)
        • :具有因果关系的多个操作(比如读写同一个数据),才保证顺序一致性,而其它并发操作则不限制。
      • 最终一致性(Eventual consistency)
        • :允许各节点暂时不一致,但保证在一定时间内实现一致。

# 可用性

  • 服务可用(Available)

    • :指客户端发出请求时,能收到正常的响应。
      • 响应时长不能超过正常范围。
      • 响应的内容不能是错误的,但可以不是最新的数据。
    • 服务不可用时,又称为服务中断、故障。
  • 可用性(Availability)

    • :又称为可用率。如果服务可用的时长,占提供服务的总时长的比例接近 100% ,则称为可用性高,否则称为可用性低。
    • 采用负载均衡、健康检查等措施可以实现服务的高可用性(High Availability ,HA)。
  • SLA (Service Level Agreement ,服务等级协议)

    • :由服务提供商承诺的服务质量指标,如果未达到则给客户一定赔偿。
    • 比如承诺服务的全年可用性为 99% ,即不可用的时长低于 3.65 天;全年可用性为 99.9% ,即不可用时长低于 0.365*24=8.76 小时。
  • 提高系统性能的常见方案:

    • 垂直扩展:增加单个服务的性能,比如增加服务器的 CPU 、内存等资源。
    • 水平扩展:增加服务实例的数量,比如在一组服务器上分别部署一个服务实例。

# 常见问题

  • 单点故障(Single Point of Failure)

    • :单个模块不可用,导致整个服务不可用。或者单个服务不可用,导致整个系统不可用。
  • 级联故障(Cascading failure)

    • :上游服务故障,导致下游调用它的服务故障。
  • 服务雪崩

    • :级联故障导致大量服务不可用。

# 常见措施

  • 服务熔断

    • :当上游服务可用性降低时,下游服务停止调用它,避免级联故障。
    • 服务熔断之后,下游服务可以拒绝提供服务,也可以开始服务降级。
  • 服务降级

    • :降低服务给客户端的响应质量,避免服务完全不可用。
    • 例如当服务负载过大时,停止次要功能、延时处理请求、减少响应内容、使用旧的响应内容,甚至拒绝服务。
    • 例如调用上游服务时总是设置超时时间,如果超时,则当前服务返回降级的响应。
    • 可以在配置平台增加一个参数开关,启用它则开始服务降级。也可以自动降级。
  • 服务限流

    • :属于服务降级,指服务限制一定时间内被调用的次数,超过限制时拒绝新的请求,避免因为负载过大而故障。
  • 重试

    • 一种业务操作执行之后,如果用户重复请求,是否允许重试,这需要实现幂等性。
    • 一种业务操作执行失败之后,如果用户不重复请求,是否自动重试。

# 健康检查

:Health Check ,通过软件检查集群中各个服务器的状态,自动发现故障的服务器。

  • 发现故障节点之后,需要及时将它下线,避免客户端访问它而服务不可用。或者通过重启等方式修复。

  • keepalived :一个命令行工具,用于对多个服务器进行健康检查,自动去除故障服务器。

    • 在集群的每个服务器上部署一份,相互之间通过 VRRP(Virtual Router Redundancy Protocol ,虚拟路由冗余协议)通信,实现路由的高可用。
    • 工作在第三层时,是基于 ICMP 协议检查服务器是否在线。
    • 工作在第四层时,是基于 TCP 协议检查服务器的端口是否开通。
    • 工作在第七层时,是基于 HTTP 协议检查服务器是否正常工作。

# 负载均衡

:Load Balance ,将服务器部署多个实例,用一个反向代理服务器接收所有客户端的访问流量,然后按特定的策略分发给各个实例。

  • 优点:
    • 容易横向扩容。
    • 均衡各个服务器的负载压力,降低单点故障的风险。
    • 有的负载均衡服务器能对各个服务器进行健康检查,避免将流量转发给故障的服务器,从而避免单点故障。

# 分区容错性

:Partition tolerance ,指系统出现网络分区时,能否继续提供服务。

  • 如果任意两个节点之间不能在指定时间内将数据同步一致(比如网络延迟较大、节点故障),则视作网络中断,出现了网络分区。

# CAP 定理

:一个流行的理论,认为在分布式系统中,一致性(C)、可用性(A)、分区容错性(P) 三种性能通常不能同时满足,最多满足两种。

  • 假设分布式系统中存在两个节点 N1、N2 ,两者的网络通信必然存在一定延迟。先在 N1 处写入数据 D ,然后在 N2 处读取数据 D 。此时:
    • 如果 N2 等同步 N1 的数据之后再返回响应,则满足了 C ,但不满足 A 。
    • 如果 N2 不同步 N1 的数据就返回响应,则必然是错误的响应,满足了 A ,但不满足 C 。
    • 如果 N1 或 N2 因为网络分区而不能提供服务,则满足了 C ,但不满足 A、P 。

# 部署架构

假设部署一个数据库系统,常见的架构如下:

  • 单实例
    • :将系统只部署一套实例,使用一个或多个主机。
    • 为了避免磁盘损坏,需要定期备份数据到其它主机。
    • 优点:
      • 架构简单,成本最低。
    • 缺点:
      • 没有冗余实例,每个组件都存在单点故障的风险。
      • 发生故障时,需要在新的主机上部署一套实例,再恢复数据,需要消耗大量人力、时间。
  • 主从实例
    • :将系统部署一套主实例、一套或多套从实例,并实时同步主实例的数据到从实例。当主实例故障时,能快速启用从实例。
    • 可以手动切换到从实例,也可以通过程序自动切换。
    • 优点:
      • 主实例负责处理用户的读、写请求,从实例可以不被用户访问,也可以处理用户的读请求,减轻主实例的负载。
      • 从实例会实时同步数据,有能力随时担任主实例。不必花时间、人力从备份点恢复数据。
    • 缺点:
      • 需要实现分布式一致性。
      • 需要运行多个实例,冗余多、成本高。

# 容灾

  • 软件、硬件系统可能因为断电、断网、火灾、地震等不可抗力而故障,为了容忍灾难,通常将系统部署多套实例,当一套实例故障时能使用其它实例。

  • 多套实例通常部署在不同城市,地理位置较远,减小被同一个灾难同时波及的风险。比如:

    • 同城的不同机房
    • 异地的不同机房
      • 网络延迟较大,因此同步数据的难度大。
  • 根据实例是否被用户使用,分为几种情况:

    • 主从灾备
      • :部署一套主实例、一套或多套从实例。平时让用户只使用主实例,而从实例处于待机状态。
      • 主实例故障时,需要花时间启用从实例,并且从实例不一定可用,比如突然接收大量请求可能故障。
    • 双活
      • :部署两套实例,平时让用户同时使用。又称为双主实例。
      • 每个实例都实时可用,也减少了冗余成本,但实现分布式一致性的难度更大。
    • 多活
  • 相关概念:

    • RPO(Recovery Point Objective ,数据恢复点目标):发生灾难时,系统最多丢失最近多长时间的数据。
      • 例如系统每隔 1 小时备份一次,则最多丢失最近 1 小时内的数据,更早的数据可以从备份点恢复。
    • RTO(Recovery Time Objective ,恢复时间目标):发生灾难时,系统需要多长时间才能恢复。又称为故障恢复时间(Mean Time To Repair ,MTTR)。