# 简介

# 分布式系统

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

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

# 共识

:Consensus ,指系统中不同节点作出相同的决策。分布式系统的难点之一是如何达成共识。

  • 例如 MySQL 主从集群采用一种很简单的共识方案:部署一个主节点、多个从节点,主节点每执行一个事务,就让从节点复制执行该事务。
    • 这样只有主节点有权作出决策。如果主节点故障,则整个系统不可用。
  • 脑裂(brain split)
    • :指一个系统中存在多个有权决策的节点,并且作出了不同决策。
  • 拜占庭将军问题(Byzantine Generals Problem)
    • :拜占庭帝国进行战争时,多个将军会自主观察敌情,然后通过投票决定进攻还是撤退。但可能存在不诚实的将军,或者投票信件被丢失、篡改。
    • 该问题常用于比喻:分布式系统中某些节点传播虚假的信息,导致其它节点作出了错误决策。

# 一致性

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

  • 通常,需要各节点先实现数据一致性,才能掌握相同的情报,做出同样的决策,达成共识。
    • 数据不一致性时,各节点可能因为不同的数据副本而作出不同的决策,不容易达成共识。
  • 每个写操作之后,如果等所有节点复制完新数据,才开始下一个读操作,则称为同步复制,否则称为异步复制。
  • 常见的几种一致性模型:
    • 强一致性:采用同步复制,保证各节点的一致性。
      • 严格一致性(Strict consistency)
        • :每执行一个写操作,要求所有节点在一个 CPU 时钟周期内完成同步,实现数据一致。
        • 它要求实时同步,使得该分布式系统变成一个单机系统,即使客户端并行一些写操作,也会变成串行操作。
        • 这是最强的一致性模型,但只是理论上存在,不能在工程中实现,因为节点之间的网络通信总是存在延迟。
      • 线性一致性
        • :每执行一个写操作,要等所有节点都完成同步,才开始下一个操作。
        • 这是工程中能实现的最强的一致性模型。与严格一致性相比,不要求实时同步。
        • 又称为可线性化(Linearizability)、原子一致性。
      • 顺序一致性(Sequential consistency)
        • :执行一连串写操作时,所有节点会按相同顺序执行,但不保证同时执行。因此某些节点可能因为执行慢而数据滞后,但顺序并不会出错。
        • 强弱程度:严格一致性 > 线性一致性 > 顺序一致性
    • 弱一致性:采用异步复制,因此各节点可能不一致。
      • 因果一致性(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 、内存等资源。
    • 水平扩展:增加服务实例的数量,比如在一组服务器上分别部署一个服务实例。

# 健康检查

  • 如何判断一个服务是否可用?人工检查的效率太低,通常用软件自动检查服务的状态,该操作称为健康检查(Health Check)。
    • 例如,向一个 HTTP 服务器每秒发出一个 HTTP 请求,如果没收到 HTTP 200 响应,则认为服务器故障。

# 负载均衡

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

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

# 常见故障

  • 单点故障(Single Point of Failure)

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

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

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

# 常见措施

  • 服务熔断

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

    • :降低服务给客户端的响应质量,避免服务完全不可用。
    • 例如:
      • 通过 API 网关限制所有服务的被调用次数、网速,避免负载过大。
      • 当服务负载过大时,停止次要功能、延时处理请求(这会增加响应耗时)、减少响应内容、使用旧的响应内容,甚至拒绝服务。
      • 调用上游服务时都应该设置超时时间,如果超时,则当前服务返回降级的响应。
      • 可以在配置中心增加一个参数开关,开启它之后,各个服务会开始服务降级。也可以让各个服务自动判断是否需要降级。
  • 服务限流

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

    • 一种业务操作执行之后,如果用户重复请求,是否允许重试,这需要实现幂等性。
    • 一种业务操作执行失败之后,如果用户不重复请求,是否自动重试。
  • 例如 Sentinel 是阿里巴巴公司开源的一个熔断框架。

    • GitHub (opens new window)
    • 用法:
      1. 部署 Sentinel 。目前只需部署 sentinel-dashboard 这个 Java 进程,同时提供 API 端口和 Web 管理页面。
      2. 修改一些 Java 业务进程的代码,定义一些 Sentinel 资源,然后配置熔断规则。
      3. 启动 Java 业务进程,连接到 Sentinel ,获取熔断规则。
    • 默认只会将熔断规则保存在 Java 业务进程的内存中,因此重启进程后会丢弃。建议二次开发 Sentinel ,将熔断规则推送到 Nacos 等位置存储。
    • 功能:
      • 支持在流量过大时自动熔断。例如 QPS、线程数超过阈值。
        • 建议先进行压力测试,确定业务服务的负载上限,然后据此设置熔断阈值。
      • 支持在服务降级时自动熔断。例如平均响应时间(RT)、抛出异常数超过阈值。
      • 支持多种熔断措施。例如:
        • 抛出 FlowException 异常,拒绝新的 HTTP 请求。
        • 让新请求排队等待被处理,如果等待超时则拒绝请求,从而对流量数削峰。
        • 预热:逐渐增加熔断阈值,避免业务服务突然从空载变成满载。

# 分区容错性

: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 。

# 部署架构

# 分类

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

  • 单实例
    • :将系统只部署一套实例,使用一个或多个主机。
    • 为了避免磁盘损坏,需要定期备份数据到其它主机。
    • 优点:
      • 架构简单,成本最低。
    • 缺点:
      • 没有冗余实例,每个组件都存在单点故障的风险。
      • 发生故障时,需要在新的主机上部署一套实例,再恢复数据,需要消耗大量人力、时间。
  • 主从集群
    • :将系统部署一个主实例、一个或多个从实例,并实时同步主实例的数据到从实例。当主实例故障时,能快速启用从实例。
    • 可以手动切换到从实例,也可以通过程序自动切换。
    • 优点:
      • 主实例负责处理用户的读、写请求,从实例可以不被用户访问,也可以处理用户的读请求,减轻主实例的负载。
      • 从实例会实时同步数据,有能力随时担任主实例。不必花时间、人力从备份点恢复数据。
    • 缺点:
      • 需要实现分布式一致性。
      • 需要运行多个实例,冗余多、成本高。
  • 多主集群
    • :将系统部署多个主实例。
    • 优点:
      • 每个实例都可供客户端访问,读写数据。容易分散负载,大幅提高可用性。
    • 缺点:
      • 多个主实例之间,实现分布式一致性的难度很大。
  • 副本集群
    • :将系统部署一个主实例、一个或多个副本实例。
    • 像主从集群,每个实例都拥有完整的一份数据。但区别在于,当主实例故障时,副本实例能根据某种共识算法,选出一个副本实例,担任新的主实例。
    • 优点:
      • 可用性比主从集群高。
    • 缺点:
      • 实现分布式一致性的难度,比主从集群高。
      • 客户端可能需要知道所有副本实例的 IP 地址,自动发现新的主实例。或者部署一个 proxy 服务器,自动发现新的主实例,并进行反向代理,然后让客户端访问 proxy ,而不是直接连接数据库实例。

# 容灾

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

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

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

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

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