分布式系统的演进和定义
要理解分布式系统的定义,必须了解应用如何从单体到分布式的演进过程。
单体应用
- 一台机器上部署单一应用
- 受单机性能容量限制
- 单点故障
- 扩展性差
集群应用
- 无状态集群应用
- 任何一个请求都与之前的请求无关。
- 集群中的节点可以任意伸缩。
- 有状态的集群应用
- 单一服务的节点集群
- 节点间完全隔离,只服务对应的用户。
- 容错性较差。
- 共享信息池的节点集群
- 多个节点共享信息池。
- 受信息池容量、性能影响。
- 存在信息池单点故障问题。
- 信息一致的节点集群
- 每个节点有独立的信息池。
- 信息池间同步,存在延迟和一致性问题。
- 适用于读多写少的场景。
- 单一服务的节点集群
分布式应用
- 将应用拆分成多个子应用。
- 不同节点上可能部署不同的子应用。
- 子应用按需扩展集群。
集群与分布式
- 集群指多个节点做相同的任务。
- 分布式指多个节点协同做一种任务。
广义的分布式
- 判断依据:多个节点是否使用一致的信息池。
- 无论节点部署相同还是不同应用。
- 都面临信息池的同步及数据一致性问题。
分布式系统的基本问题
- 网络通信问题,网络存在抖动断网丢包,通信延迟超时等。
- 节点故障问题,机器宕机、重启等。
- 网络分区问题,集群因为网络问题分裂成多个完全独立的集群。
- 三态问题,每一次请求响应,除成功失败外还存在超时,无法确定请求是否被成功处理。
CAP 定律
- C是Consistency,一致性
- 指写入成功后,必须保证后续读取到的是最新的数据。如果仍然读取到过期的数据,就是不一致的。
- A是Availability,可用性
- 指提供正常可用的服务能力,不会发生超时或错误。
- P是Partition tolerance,分区容忍性
- 指允许集群分裂成多组完全不连通的分区。
- CAP 定律指:分布式系统无法同时满足CAP三种特性,只能满足其二。
- 要保证一致性和可用性,则不能保证分区容忍性,即必须保证网络的可靠性不允许出现分区。
- 事实上,网络是不可靠的,分区容忍性是分布式系统的基本前提。
- 所以只能在一致性和可用性之间做选择。
一致性级别
- 强一致性:承诺始终能读取到最新写入的数据,代价是相对高的延迟。
- 弱一致性:不承诺可以立刻读取到最新写入的数据,但尽可能保证到某个时间级别后读到最新的数据。弱一致性又可分:
- 会话一致性:保证在同一客户端会话的强一致性,其他会话不保证。
- 用户一致性:保证在同一用户中的强一致性,其他用户不保证。
- 最终一致性:保证在经过一段时间后最终会达到一致性的特殊弱一致性。
分布式事务
事务具有 ACID 特性:
- A 原子性:要么成功,要么失败,没有中间状态。
- C 一致性:事务执行前后,数据库完整性约束不会被破坏。
- I 隔离性:不同事务之间不会相互影响。
- D 持久性:一旦事务提交,数据将持久保存。
分布式事务是指在分布式系统上实现事务,同样需要保证 ACID,尤其是一致性。 分布式事务保证强一致性,但牺牲可用性。
2PC 协议
- 2PC协议用于实现分布式事务,用于保证分布式系统的强一致性。
- 2PC是两阶段提交,分为准备阶段和提交阶段。
- 其关键点是,在准备阶段尽可能完成所有工作,而提交阶段将是耗时极短失败概率小的操作。
- 其实现,必须存在一个中心化的协调者,负责协调参与者的行为。
- 如果协调者单点故障将出现数据不一致现象。
3PC 协议
- 3PC协议是2PC的改进,目标与2PC相同。
- 3PC是三阶段提交,分为准备阶段、预提交阶段和提交阶段。
- 对比2PC,3PC对协调者和参与者都设置了超时时间,在参与者等待提交超时后会自动提交。
- 在提交阶段,如果协调者发出回滚,但参与者失联会提交,则出现数据不一致情况。
TCC 协议
- TCC是业务层面的2PC,而2PC和3PC都是分布式数据库事务的协议。
- TCC是Try-Confirm-Cancel。
- Try 阶段是准备阶段尽可能完成所有工作,都成功则 Confirm,否则 Cancel。
- Confirm 和 Cancel 必须保证幂等,而且 Cancel 允许空回滚。
BASE 理论
BASE 是对 CAP中一致性和可用性权衡的结果,核心思想是即使无法做到强一致性,但可以根据自己的业务特点,采用适当的方式达到最终一致性。 BASE 来源于对大规模分布式系统实践的总结。
- BA是 Basically Available,基本可用
- 如损失响应时间
- 如保证核心功能可用,损失部分功能的可用性
- S是 Soft State,软状态
- 允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性。
- E是 Eventually Consistent,最终一致性
- 所有数据副本经过一段时间后,最终会达到一致
共识算法
- 共识算法是对 BASE 理论的实践。
- 目标是使分布式系统在出现各种异常(网络故障、节点故障、网络分区等)时,仍然能够达成最终一致。
- 共识算法和分布式事务区别:分布式事务保证绝对一致性牺牲可用性,而共识算法通过保证共识达成最终一致性。
- 常见共识算法有 Paxos、Raft。
- Paxos 和 Raft 都是解决非拜占庭容错问题,即不存在恶意节点伪造信息。
- Paxos 是最早提出的共识算法,比较复杂。
- Raft 比较新,相对简单。Raft 是当前分布式系统的首选算法。
Paxos 算法
- Paxos 算法是分布式系统中实现一致性的算法。
- 基本思想
- 将节点分为提议者、接受者、学习者。
- 提议者提出提案,接受者接受提案并投票,学习者学习已经达成一致的提案。
- 需经过多轮的消息交换和投票产生一个大家认可的值,然后所有节点执行操作最终达成一致。
- 优点
- 较高容错性,能容忍节点故障和网络延迟
- 较高扩展性,适应不同规模的分布式系统,从几个节点到几千个节点
- 缺点
- 复杂型,理解和实现有一定难度
- 性能,多轮消息交换有性能开销
- Zookeeper 的zab 算法基于 Paxos 算法改进而来。
Raft 算法
- 比 Paxos 更具可理解性。
- 通过 Leader 选举、心跳机制、日志复制等方式来维护数据一致性。
- Etcd 采用 Raft。
算法细节:
- 日志复制
- 基于复制状态机模型实现
- 相同的初始状态 相同的输入=相同的结束状态
- 相同的输入通过操作日志实现
- Leader 选举
- 节点角色
- 领导,负责提交日志,并广播日志
- 跟随者,负责日志复制
- 候选人,竞选 Leader
- 任期
- 作为逻辑时钟,通过任期 ID 识别过期信息
- 节点之间通信会对比任期,更新到最大
- 心跳机制
- 领导节点通过广播心跳给其他节点
- 其他节点收到心跳后重置定时器,定时器过期后认为领导不存在开始选举
- 选举过程
- 跟随者将自己的任期 ID 加 1,标记自己为候选人,向其他节点拉票。
- 其他节点根据先到先得原则投票,票数超过节点数的一半则当选。
- 收到宣称领导的信息时,对比任期 ID 来接受或拒绝。
- 如果一轮出现多个候选人且票数一样则无法得出领导人,会再次选举。
- 节点角色
- 写入过程
- 只有 Leader 节点可以处理写入请求。
- Leader 收到请求后,将请求添加到日志中,然后向其他节点复制日志。
- 只有超过一半节点复制成功 Leader 才认为写入成功。
- 读取过程
- Raft 共识算法本身并不保证读取的强一致性,需使用额外的手段。
- 如确保 Leader 的最新日志已复制到当前节点再读取,才能保证强一致性。
一致性哈希
哈希算法在分布式集群中的应用场景:
- 请求的负载均衡,对于请求IP进行哈希,然后对集群节点数取模,可以映射到具体节点上。
- 分布式存储,对 key 值取哈希,然后对集群节点数取模,可以锁定到具体节点上。
普通哈希算法,如果节点数发生变更(故障或扩容),则映射关系会大量失效:
- 请求的负载均衡,会路由到其他节点,导致原会话丢失。
- 分布式存储,需要迁移大量数据。
使用一致性哈希,可以避免映射关系大规模失效。
算法要点:
- 将节点标识和请求 key 的都进行哈希,然后对 2 ^32 取模,映射到 [0, 2^32-1] 上一个值上。
- [0, 2^32-1]形成了一个哈希环,从 key 的位置在环中顺时针找到第一个节点则是映射的目标节点。
- 为防止分布不均,将节点映射成多个虚拟节点,再将虚拟节点映射到环上。
幂等
- 幂等是指对同一操作发起多次请求时,对系统状态的影响是一致的。
- 分布式系统中,接口有三态问题(成功、失败、超时),为提高系统可靠性,重试是不可避免的。
- 通常查询和删除操作具有幂等性,而新增和修改操作不具有幂等性。
- 将非幂等操作转换为幂等的方法:
- 唯一约束,生成全局唯一ID(UUID 或雪花算法),利用数据库唯一约束进行防重。
- 乐观锁,对数据加版本号,写入时把之前读取的版本号作为条件同时对版本号加 1。
- 防重token,操作前先获取 token,操作时服务端通过token 判断是否重复提交。
分布式锁
- 锁用于控制多线程对同一个资源的并发访问,将访问串行化,避免相互干扰。
- 分布式锁是在分布式系统中实现的锁,控制不同节点上对同一资源的并发访问。
- 分布式锁的实现,本质是通过将锁保存在节点外的共享存储中来实现,如:
- 数据库悲观锁
- 数据库乐观锁
- 基于Redis的分布式锁
- 基于Zookeeper或 Etcd 的分布式锁
分布式系统的通信
RPC
RPC(Remote Procedure Call),远程过程调用,用于将网络通信简化为本地函数调用。 RPC 可以使分布式应用内部通信更加简单高效。 RPC 是一种方法论,RPC 协议是一组规范或标准,而 RPC 框架则提供了基于协议的实现。
RPC 协议构成
- 接口标准,如通过特定IDL 来定义接口。
- 传输协议标准,如通过TCP、HTTP进行网络传输。
- 序列化协议,如通过JSON、ProtoBuf等进行序列化。
RPC 框架构成
- 客户端接口存根,代理客户端调用。
- 网络传输模块,利用传输协议处理客户端和服务端的数据传输。
- 序列化模块,将请求和返回值转换为网络传输的数据。
- 服务端接口存根,监听网络请求处理服务端调用发送处理结果。
- 框架以客户端和服务端的依赖库、代码生成工具集等形式提供。
一次 RPC 调用的流程
- 客户端以本地函数调用方式调用服务。
- 客户端存根收到请求将方法、入参等信息序列化成能够网络传输的消息体。
- 客户端存根找到远程的服务地址,将消息通过网络发送给服务端。
- 服务端存根收到消息进行反序列化,然后调用本地服务进行处理。
- 服务端本地服务处理后返回结果给服务端存根。
- 服务端存根序列化结果并发送给客户端。
- 客户端存根收到消息进行反序列化。
- 客户端获得最终结果。
服务端IO模型
- 同步阻塞IO:线程阻塞,直到有数据才恢复。
- 同步非阻塞IO:线程不阻塞,需要轮询判断是否有数据。
- 同步多路复用IO:一个线程监听多个 IO,哪个有数据了就交给对应线程处理。
- 异步IO:内核直接将数据复制到用户空间。
服务端线程模型
- 单线程模型
- 一个线程处理所有请求
- 同个时间只能处理一个请求
- 仅开发调试用
- 多线程短连接模型
- 一个线程处理一个请求,完成后关闭连接
- 有多少并发请求就有多少线程,线程数有限
- 频繁建立网络连接开销大
- 多线程长连接模型
- 一个线程处理一个连接上的多个请求,服务端不关闭连接。
- 请求频率低时浪费资源,并发量大时无法处理。
- 线程池模型
- 提前创建好一定数量的线程,请求来了分配给空闲线程处理。
- 本质上是生产者-消费者模型
- 同样可以支持短连接或长连接。
服务端 reactor 模式
- 在多路复用IO模型的epoll方式基础上的模式。
- reactor 为反应堆,表示一个多路复用器。
- 单 reactor 单线程模式:
- 只有一个主线程运行 reactor(一个epoll) 和处理事件。
- 单 reactor 多线程(协程)模式:
- 一个主线程运行 reactor(一个epoll),通过工作线程(或协程)处理事件。
- 主从 reactor 多线程模式:
- 一个主reactor负责建立连接,多个从reactor负责处理 IO 操作。
常用 RPC 框架
- gRPC
- Google 开发的开源 RPC 框架,属于云原生 CNCF 的一部分。
- 通过 protobuf 定义接口,支持为各种开发语言自动生成客户端和服务端存根。
- 高性能,支持二进制序列化协议。
- 通信协议基于 HTTP2,支持双向流,消息头压缩,多路复用,服务端推送等。
- 支持请求响应式和流式 RPC。
消息队列
- 消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递。
- 消息发布者只管发布消息,消息是否到达队列取决于消息队列本身的功能和稳定性。
- 消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。
- 常用的消息队列有 RabbitMQ,Kafka,RocketMQ 等。
分布式系统中间件
Etcd
- 是分布式一致性的键值对存储系统。
- 适用于存储分布式系统中的控制数据或少量变更频繁的应用数据。
- Go开发,Apache开源协议。
- 特点:
- 简单:基于 HTTP JSON 的 API,用 curl 命令就可以使用。
- 安全:可选SSL认证机制。
- 快速:官方给的 QPS 测试数据相对较高。
- 可信:使用 Raft 算法实现分布式一致性。
- 适用场景:
- 服务发现。
- 消息发布与订阅。
- 负载均衡。
- 分布式锁,分布式队列
- 业界应用:
- k8s使用etcd存储docker集群的配置信息。
- apisix使用etcd存储配置信息。
- etcd对比zookeeper:
- 一致性协议:etcd使用raft,zk基于paxos,raft更简单。
- 运维:etcd容易,zk难。
- 社区活跃度,etcd比较活跃。
- api:etcd提供 http json,gRPC接口,zk 需要使用客户端。
- 安全:etcd支持ssl,zk不支持。
- etcd对比redis:
- 定位不同,etcd目标是分布式系统配置数据一致性,redis目标是内存数据库。
- 数据持久性:前者可以确保数据不丢失,后者虽然也提供持久化机制但极端场景仍然会丢失。
- 数据一致性:前者通过raft保证分布式各节点强一致性,后者属于单一结点设计,仅支持主从复制不支持强一致性。
- 数据类型和模型:前者模型相对简单,后者提供复杂结构适应更多场景。
- etcd 的 watch 原理:
- v2:使用 HTTP1协议,每一个 Watcher 对应一个TCP长连接,通过轮训来获取最新的变化事件。
- v3:基于 HTTP2的 gRPC 自己双向流的 WatchAPI,服务端向客户端流式推送。