Youtube: https://m.youtube.com/playlist?list=PLeKd45zvjcDFUEv_ohr_HdUFe97RItdiB
Ref: https://www.cxyzjd.com/article/u014034683/89422804

CAP(帽子原理)

image.png
由于对系统或者数据进行了拆分,我们的系统不再是单机系统,而是分布式系统,针对分布式系统的 CAP 原理包含如下三个元素:

  • C: Consistency (一致性) 在分布式系统中的所有数据备份,在同一时刻具有同样的值,所有节点在同一时刻读取的数据都是最新的数据副本。
  • A: Availability(可用性) 好的响应性能。完全的可用性指的是在任何故障模型下,服务都会在有限的时间内处理完成并进行响应。
  • P: Partition tolerance(分区容忍性) 尽管网络上有部分消息丢失,但系统仍然可继续工作。

CAP 原理指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。因此在进行分布式架构设计时,必须做出取舍。

而对于分布式数据系统,分区容忍性是基本要求,否则就失去了价值。因此设计分布式数据系统,就是在一致性和可用性之间取一个平衡。对于大多数 web 应用,其实并不需要强一致性,因此牺牲一致性而换取高可用性,是目前多数分布式数据库产品的方向。

当然,牺牲一致性并不是完全不管数据的一致性,否则数据是混乱的,那么系统可用性再高分布式再好也没有了价值。牺牲一致性,只是不再要求关系型数据库中的强一致性,而是只要系统能达到最终一致性即可,考虑到客户体验,这个最终一致的时间窗口,要尽可能的对用户透明,也就是需要保障 “用户感知到的一致性”。通常是通过数据的多份异步复制来实现系统的高可用和数据的最终一致性的,“用户感知到的一致性” 的时间窗口则取决于数据复制到一致状态的时间。

BASE 理论

BASE 理论是指:

  • Basically Available(基本可用)
  • Soft-state( 软状态 / 柔性事务)
  • Eventual Consistency(最终一致性)

是基于 CAP 定理演化而来,是对 CAP 中一致性和可用性权衡的结果。
核心思想:即使无法做到强一致性,每个业务可根据自身特点,采用适当的方式使系统达到最终一致性。

1、基本可用 (Basically Available, BA)
指分布式系统在出现故障的时候,允许损失部分可用性,保证核心可用。但不等价于不可用。比如:搜索引擎 0.5 秒返回查询结果,但由于故障,2 秒响应查询结果;网页访问过大时,部分用户提供降级服务等。简单来说就是基本可用。

2、软状态 (Soft State, S)
软状态是指允许系统存在中间状态,并且该中间状态不会影响系统整体可用性。即允许系统在不同节点间副本同步的时候存在延时。简单来说就是状态可以在一段时间内不同步。

3、最终一致性 (Eventually Consistent, E)
系统中的所有数据副本经过一定时间后,最终能够达到一致的状态,不需要实时保证系统数据的强一致性。最终一致性是弱一致性的一种特殊情况。BASE 理论面向的是大型高可用可扩展的分布式系统,通过牺牲强一致性来获得可用性。ACID 是传统数据库常用的概念设计,追求强一致性模型。简单来说就是在一定的时间窗口内, 最终数据达成一致即可。

柔性事务和刚性事务

  • 刚性事务满足 ACID 理论
  • 柔性事务满足 BASE 理论(基本可用,最终一致)

柔性事务分为:

  • 两阶段型
  • 补偿型
  • 异步确保型
  • 最大努力通知型

由于支付宝整个架构是 SOA 架构,因此传统单机环境下数据库的 ACID 事务满足了分布式环境下的业务需要,以上几种事务类似就是针对分布式环境下业务需要设定的。

分布式一致性协议

XA 接口

XA 是由 X/Open 组织提出的分布式事务的规范。

  • (全局) 事务管理器 (Transaction Manager)
  • (局部) 资源管理器 (Resource Manager)

XA 规范主要定义了二者之间的接口,XA 接口是双向的系统接口,在事务管理器以及一个或多个资源管理器之间形成通信桥梁。

XA 之所以需要引入事务管理器,是因为在分布式系统中,从理论上讲(参考 Fischer 等的论文),两台机器理论上无法达到一致的状态,需要引入一个单点进行协调。

  • 事务管理器控制着全局事务,管理事务生命周期,并协调资源
  • 资源管理器负责控制和管理实际资源(如数据库或 JMS 队列)

    JTA 规范

    作为 java 平台上事务规范 JTA(Java Transaction API)也定义了对 XA 事务的支持,实际上,JTA 是基于 XA 架构建模的,在 JTA 中,事务管理器抽象为 javax.transaction.TransactionManager 接口,并通过底层事务服务(即 JTS)实现。

像很多其他的 java 规范一样,JTA 仅仅定义了接口,具体的实现则是由供应商 (如 J2EE 厂商) 负责提供,目前 JTA 的实现主要由以下几种:

  • J2EE 容器所提供的 JTA 实现 (JBoss)
  • 独立的 JTA 实现:如 JOTM,Atomikos. 这些实现可以应用在那些不使用 J2EE 应用服务器的环境里用以提供分布式事务保证。如 Tomcat, Jetty 以及普通的 java 应用。

    两阶段提交协议

    image.png
    第一阶段(准备阶段)
    协调者向参与者发起指令,参与者评估自己的状态,如果参与者评估指令可以完成,则会写 redo 或者 undo 日志,也就是说所有参与者都将本事务能否成功的信息反馈发给协调者,然后锁定资源,执行操作,但并不提交。

第二阶段(执行阶段)
如果每个参与者明确返回准备成功,则协调者向参与者发送提交指令,参与者释放锁定的资源,如果任何一个参与者明确返回准备失败,则协调者会发送终止指令,参与者取消已经变更的事务,释放锁定的资源。

两阶段提交方案应用非常广泛,几乎所有商业 OLTP 数据库都支持 XA 协议。但是两阶段提交方案锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。
缺点:如果协调者宕机,参与者没有协调者指挥,则会一直阻塞。

三段提交协议

三阶段提交协议是两阶段提交协议的改进版本。它通过超时机制解决了阻塞的问题,并且把两个阶段增加为三个阶段:

  • 询问阶段:协调者询问参与者是否可以完成指令,协调者只需要回答是还是不是,而不需要做真正的操作,这个阶段超时导致中止。
  • 准备阶段:如果在询问阶段所有的参与者都返回可以执行操作,协调者向参与者发送预执行请求,然后参与者写 redo 和 undo 日志,执行操作,但是不提交操作;如果在询问阶段任何参与者返回不能执行操作的结果,则协调者向参与者发送中止请求,这里的逻辑与两阶段提交协议的准备阶段是相似的,这个阶段超时导致成功。
  • 提交阶段:如果每个参与者在准备阶段返回准备成功,也就是预留资源和执行操作成功,协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源;如果任何一个参与者返回准备失败,也就是预留资源或者执行操作失败,协调者向参与者发起中止指令,参与者取消已经变更的事务,执行 undo 日志,释放锁定的资源,这里的逻辑与两阶段提交协议的提交阶段一致。

    2PC 与 3PC 提交区别

  • 增加了一个询问阶段,询问阶段可以确保尽可能早的发现无法执行操作而需要中止的行为,但是它并不能发现所有的这种行为,只会减少这种情况的发生在准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,协调者和参与者都继续提交事务,默认为成功,这也是根据概率统计上超时后默认成功的正确性最大。

  • 三阶段提交协议与两阶段提交协议相比,具有如上的优点,但是一旦发生超时,系统仍然会发生不一致,只不过这种情况很少见罢了,好处就是至少不会阻塞和永远锁定资源。

    基础理论

    Ref: https://zhuanlan.zhihu.com/p/34553701

    中心化和去中心化

  • 中心化:中心化的设计思想在自然界和人类生活中是如此的普遍和自然,它的设计思想也很简单,分布式集群中的节点按照角色分工,可以分为两种角色 —“领导” 和 “干活的”,中心化的一个思路就是 “领导” 通常分发任务并监督 “干活的”,谁空闲了就给它安排任务,谁病倒了就一脚踢出去,然后把它的任务分给其他人; 中心化的另一个思路是领导只负责生成任务而不再指派任务,由每个 “干活的” 自发去领任务。

  • 去中心化:全球 IP 互联网就是一个典型的去中心化的分布式控制架构,联网的任意设备宕机都只会影响很小范围的功能。去中心化设计通常没有 “领导” 和 “干活的”,角色一样,地位平等,因此不存在单点故障。实际上,完全意义的去中心化分布式系统并不多见,很多看起来是去中心化但工作机制采用了中心化设计思想的分布式系统正在不断涌现,在这种架构下,集群中的领导是动态选择出来的,而不是人为预先指定的,而且在集群发生故障的情况下,集群的成员会自发举行会议选举新的领导。典型案例如:zookeeper、以及 Go 语言实现的 Etcd。

    分布式一致性原理

    在说明一致性原理之前,先了解一下 cap 理论和 base 理论,具体见《事务与柔性事务》中的说明。

对于多副本的一致性处理,通常有几种方法:

  • 同步更新:即写操作需要等待两个节点都更新成功才返回,这样的话如果一旦发生网络分区故障,写操作便不可用,牺牲了 A。
  • 异步更新:即写操作直接返回,不需要等待节点更新成功,节点异步地去更新数据,这种方式,牺牲了 C 来保证 A。
  • 折衷:只要保证集群中超过半数的节点正常并达到一致性即可满足要求,此时读操作只要比较副本集数据的修改时间或者版本号即可选出最新的,所以系统是强一致性的。如果允许 “数据一致性存在延迟时间”,则是最终一致性。

如 Cassandra 中的折衷型方案 QUORUM,只要超过半数的节点更新成功便返回,读取时返回多数副本的一致的值。然后,对于不一致的副本,可以通过 read repair 的方式解决。read repair:读取某条数据时,查询所有副本中的这条数据,比较数据与大多数副本的最新数据是否一致,若否,则进行一致性修复。此种情况是强一致性的。

又如 Redis 的 master-slave 模式,更新成功一个节点即返回,其他节点异步地去备份数据。这种方式只保证了最终一致性。最终一致性:相比于数据时刻保持一致的强一致性,最终一致性允许某段时间内数据不一致。但是随着时间的增长,数据最终会到达一致的状态。此种情况只能保证最终一致性。著名的 DNS 也是最终一致性的成功例子。

强一致性算法:1989 年就诞生了著名的 Paxos 经典算法(zookeeper 就采用了 Paxos 算法的近亲兄弟 Zab 算法),但由于 Paxos 算法难以理解、实现和排错,所以不断有人尝试优化算法,2013 年终于有了重大突破:Raft 算法的出现,其中 Go 语言实现的 Raft 算法就是 Etcd,功能类似于 zookeeper。

Base 的思想:基本可用、柔性状态、最终一致性,主要针对数据库领域的数据拆分,通过数据分片(如 Mycat、Amodeba 等)来提升系统的可用性。由于分片拆分后会涉及分布式事务,所以接下来看一下如何用最终一致性的思路来实现分布式事务,也就是柔性事务。

基础设施

Zookeeper

分布式系统的关键 Zookeeper

  • 目标是解决分布式系统的几个问题:集群集中化配置,集群节点动态发现机制,简单可靠的节点 Leader 选举机制,分布式锁。
  • ZNode 有一个 ACL 访问权限控制列表,提供对节点增删改查的 API,提供监听 ZNode 变化的实时通知接口 —Watch 接口。
  • ZNode 类型:持久节点(可以实现配置中心)、临时节点(和创建这个节点的客户端会话绑定,可实现集群节点动态发现,可以实现服务注册中心)、时序节点(创建节点时会加上数字后缀,通过选择编号最小的 ZNode 可以实现 Leader 选举机制)、临时性时序节点(同时具备临时节点和时序节点的特性,主要用于分布式锁的实现)。

    基础服务层

    RPC 和对象序列化。

    分布式内存缓存和内存计算

    缓存系统常用的缓存淘汰策略:

  • Least Frequently Used(LFU)策略: 计算使用频率,优先淘汰最不常用的缓存条目,CPU 的 cache 所采用的淘汰策略即为 LFU 策略;

  • Least Recently Used(LRU)策略: 淘汰最近最少使用的条目;
  • Adaptive Replacement Cache(ARC)策略: 该策略由两个 LRU 组成,第 1 个 LRU 包含的条目是最近只被使用过一次的条目,第 2 个 LRU 包含的是最近被使用过二次的条目;
  • 其他还有一些基于缓存时间的淘汰策略,比如淘汰存活时间超过 5 分钟的缓存条目。

分布式缓存都采用 Hash 算法进行数据分片,将数量庞大的缓存项均匀分布到集群中的每个节点上,比如 Redis3.0 开始实现的分布式集群功能就采用了 Hash 算法,将缓存项均匀分布到 16384 个 Slot 上去。以 Redis2.x 为基础改造的 Codis 是国内分布式缓存开源的一个典范,出自豆瓣网。Memcache 本身并没有提供集群功能,但很多客户端 Driver 实现了 Hash 算法分配逻辑,因此也可以看成是一种分布式缓存的解决方案。

内存计算产品:商业的 SAP Hana、开源的 VoltDB 等。VoltDB 是一种开源的高性能的内存关系型数据库,提供社区版和商业版,是一种 NewSql,是一个借鉴并基于 HSQL 的分配内存数据库集群。

分布式存储

分布式计算

消息队列

消息队列迭代:

  • 第一代消息队列:J2EE 时代的产物,强调企业级特性,比如消息持久存储与事务的要求,都遵循 JMS 规范,最著名的是开源 Apache ActiveMQ。虽然当前基于 J2EE 架构的企业软件少了,但依然有不少商业软件仍然采用了企业级的 J2EE 架构。值得一提的是,ActiveMQ Artemis 是 ActiveMQ 的下一代产品,它已经融合了多种 MQ 的特性,成为 Java 领域无法超越的 MQ 之王。
  • 第二代消息队列:制定了一个开放性、免费的消息中间件协议 AMQP 标准,第一个也是最重要的开源 AMQP 消息中间件产品 RabbitMQ,同时 ActiveMQ 目前也支持 AMQP 协议,Apache 还专门开源了 Qpid 这个基于 AMQP 协议的消息中间件产品。
  • 第三代消息队列:分布式系统设计理念,采用 Zookeeper 实现去中心化的集群管理,以 Kafka 为代表。

    任务调度

全文检索

  • Lucence Core:Java 编写的核心类库,提供全文检索功能的底层 API 与 SDK。
  • Solr:基于 Lucence Core 开发的高性能搜索服务,提供了 REST API 的高层封装接口,还提供了一个 Web 管理界面。
  • ElasticSearch:也是基于 Lucence 的分布式全文检索中间件。

    容器

微服务架构

当前主流的微服务架构可以分为三类:
1、基于传统高性能 RPC 技术和服务治理的微服务框架,这个领域的王者是 ZeroC IceGrid;
2、以 HTTP REST 为通信机制的通用性微服务架构,最典型的为 Spring Cloud;
3、基于容器技术,没有提过特定的 RPC 通信机制,理论上任何分布式应用都可以运行在微服务架构平台上,言外之意就是要选择合适的通信协议,比如 REST、Thrift、gRPC 等,这个领域的王者是 Google 的 Kubernetes。

微服务架构的项目在实施过程中经常需要考虑的问题:

  • 引入自动化工具与集中运维管理工具,用于程序的编译打包、自动化部署和升级等工作;
  • 需要研究、测评大量相关开源工具并引入微服务架构中,原因是之前的很多中间件工具只适合于单体应用;
  • 团队重构,包括展现层和微服务层,建议团队中的骨干技术人员成为微服务层的开发主力;
  • 高质量的文档。

基于消息队列的微服务架构:网易的蜂巢平台采用了基于消息队列的微服务架构,但基于消息队列的微服务架构案例少,没有知名的开源平台,因此实施成本高、风险大。

参考:
https://blog.pragmaticengineer.com/distributed-architecture-concepts-i-have-learned-while-building-payments-systems/