# 简介
# 分布式系统
:是指将一个软件系统的各个进程部署不同主机上。
- 小型的软件系统通常只部署在一台主机上,属于集中式系统。而大型的软件系统通常部署成分布式系统,从而提高性能。
- 优点:
- 便于横向增加系统节点,提高系统容量、性能,比如处理高并发流量。
- 可以将同一个程序运行多个实例,一个实例挂掉了就用其它实例,实现服务的高可用。
- 可以将一个数据存储多个副本。
- 难点:
- 系统规模变大、结构变复杂,维护麻烦。比如要考虑共识、一致性、可用性。
- 一个程序要运行多个实例,成本变高。
# 共识
:Consensus ,指系统中不同节点作出相同的决策。分布式系统的难点之一是如何达成共识。
- 例如 MySQL 主从集群采用一种很简单的共识方案:部署一个主节点、多个从节点,主节点每执行一个事务,就让从节点复制执行该事务。
- 这样只有主节点有权作出决策。如果主节点故障,则整个系统不可用。
- 脑裂(brain split)
- :指一个系统中存在多个有权决策的节点,并且作出了不同决策。
- 拜占庭将军问题(Byzantine Generals Problem)
- :拜占庭帝国进行战争时,多个将军会自主观察敌情,然后通过投票决定进攻还是撤退。但可能存在不诚实的将军,或者投票信件被丢失、篡改。
- 该问题常用于比喻:分布式系统中某些节点传播虚假的信息,导致其它节点作出了错误决策。
# 一致性
:Consistency ,指系统中不同节点拥有的数据副本一致(通常还应该是最新的数据)。
- 通常,需要各节点先实现数据一致性,才能掌握相同的情报,做出同样的决策,达成共识。
- 数据不一致性时,各节点可能因为不同的数据副本而作出不同的决策,不容易达成共识。
- 每个写操作之后,如果等所有节点复制完新数据,才开始下一个读操作,则称为同步复制,否则称为异步复制。
- 常见的几种一致性模型:
- 强一致性:采用同步复制,保证各节点的一致性。
- 严格一致性(Strict consistency)
- :每执行一个写操作,要求所有节点在一个 CPU 时钟周期内完成同步,实现数据一致。
- 它要求实时同步,使得该分布式系统变成一个单机系统,即使客户端并行一些写操作,也会变成串行操作。
- 这是最强的一致性模型,但只是理论上存在,不能在工程中实现,因为节点之间的网络通信总是存在延迟。
- 线性一致性
- :每执行一个写操作,要等所有节点都完成同步,才开始下一个操作。
- 这是工程中能实现的最强的一致性模型。与严格一致性相比,不要求实时同步。
- 又称为可线性化(Linearizability)、原子一致性。
- 顺序一致性(Sequential consistency)
- :执行一连串写操作时,所有节点会按相同顺序执行,但不保证同时执行。因此某些节点可能因为执行慢而数据滞后,但顺序并不会出错。
- 强弱程度:严格一致性 > 线性一致性 > 顺序一致性
- 严格一致性(Strict consistency)
- 弱一致性:采用异步复制,因此各节点可能不一致。
- 因果一致性(Causal consistency)
- :具有因果关系的多个操作(比如读写同一个数据),才保证顺序一致性,而其它并发操作则不限制。
- 最终一致性(Eventual consistency)
- :允许各节点暂时不一致,但保证在一定时间内实现一致。
- 因果一致性(Causal 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)
- 用法:
- 部署 Sentinel 。目前只需部署 sentinel-dashboard 这个 Java 进程,同时提供 API 端口和 Web 管理页面。
- 修改一些 Java 业务进程的代码,定义一些 Sentinel 资源,然后配置熔断规则。
- 启动 Java 业务进程,连接到 Sentinel ,获取熔断规则。
- 默认只会将熔断规则保存在 Java 业务进程的内存中,因此重启进程后会丢弃。建议二次开发 Sentinel ,将熔断规则推送到 Nacos 等位置存储。
- 功能:
- 支持在流量过大时自动熔断。例如 QPS、线程数超过阈值。
- 建议先进行压力测试,确定业务服务的负载上限,然后据此设置熔断阈值。
- 支持在服务降级时自动熔断。例如平均响应时间(RT)、抛出异常数超过阈值。
- 支持多种熔断措施。例如:
- 抛出 FlowException 异常,拒绝新的 HTTP 请求。
- 让新请求排队等待被处理,如果等待超时则拒绝请求,从而对流量数削峰。
- 预热:逐渐增加熔断阈值,避免业务服务突然从空载变成满载。
- 支持在流量过大时自动熔断。例如 QPS、线程数超过阈值。
# 分区容错性
: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)。
- RPO(Recovery Point Objective ,数据恢复点目标):发生灾难时,系统最多丢失最近多长时间的数据。