首页
分布式系统
分布式系统需要考虑的问题
- 安全:杜绝系统及业务上的漏洞是任何系统的基础;
- 监控、告警:随时监控系统状态,异常时能及时告警;
- 单点故障、系统弹性:尽可能的避免因为单点故障导致系统雪崩,保证系统的弹性;
- 优雅退出、灾难补偿:系统下线时能优雅退出,在灾难恢复后能做到补偿;
- 网络可靠性:不要忽视网络的可靠性问题,响应结果总会有丢失的可能;
- 网络带宽:网络带宽也是决定服务器处理能力的关键要素;
- 客户端适配:需要考虑与不同客户端的适配;
- 延时:特定情况下通信延时是决定系统可用性的最大影响因子;
- 传输成本:传输成本也是系统优化的一环,选择合适的传输协议。
接口幂等
幂等的产生来自重复的请求,一般有前端重复提交、接口超时重试、消息重复消费三种原因。
- 辅助措施:
- 前端控制:发送请求后,前端按钮置灰,不可靠,不能仅依赖于该措施;
- 悲观锁或分布式锁:请求处理前需要获取到锁,获取锁失败则快速失败,主要用于处理短时间内的重复调用。
- 有效方案:
- token机制:发送请求前需要向服务器申请一个token并保存到数据库(或redis),请求时将token提交到服务器,服务器从数据库中删除token成功才能处理请求;
- 唯一索引:数据库唯一索引可以保证新增操作不被重复执行,为每个请求添加一个数据库新增操作,可以直接使用业务字段或请求时自行创建的token;
- 乐观锁:适用于更新操作,为数据的更新增加版本号,请求时传入当前版本号,处理时判断版本号是否匹配。
- 状态机幂等:思想类似乐观锁,根据业务上的状态判断,多次重试则直接返回结果即可。
分布式ID生成
- 数据库自增:最常用的方案,存在数据库单点问题;
- 数据库号段模式:优点是减少数据库压力,缺点是ID非绝对递增,且仍然存在单点问题;
- 不同机器使用不同的初始值和相同的步长自增:优点是现有功能即可轻松实现,缺点是ID非绝对递增、ID资源浪费严重;
- UUID:实现简单、缺点是安全性不足、有重复的可能、是字符串ID;
- 雪花算法:实现简单、性能好、灵活可改造,缺点是强依赖机器时钟、同一时刻不同机器间的ID非严格递增;
- ID生成服务器:如使用Redis生成,优点是支持批量生成,缺点是对服务可靠性要求高。
网络可靠性问题
如果数据库接收到事务提交请求后,服务器与数据库之间的网络断开,则会导致事务提交但服务器接受不到响应,服务器会响应客户端失败,此场景虽然存在,但理论上不需要处理,因为请求执行的结果都应该以数据库为准。
分布式理论
CAP
一个系统不可能同时满足一致性(C: Consistency)、可用性(A: Availability)和分区容错性(P: Partition tolerance),最多只能同时满足两项。
P是分布式系统最基本要求,即分布式系统不能因为出现网络分区而导致A和C无法保证,所以分布式系统应该选择侧重A或者C,ZooKeeper和Consul保证CP,Eruka保证AP,Nacos支持AP(默认)和CP。
Base
Base Available、(基本可用)Sofe state(软状态)、Eventually consistent(最终一致性),核心思想是即使无法做到强一致性,也应该使系统做到最终一致性。
最终一致性的五种实现
- 因果一致性:进程对数据的更新会通知到与其有因果关系的其他线程,不会发生更新丢失;
- 读已之所写:进程更新完数据之后,总是能访问到最新值而不是旧值;
- 会话一致性:保证在同一个会话中实现‘读已之所写’;
- 单调读一致性:一旦进程读取到数据项的某个值后,后续都不应该读取到旧的值;
- 单调写一致性:保证同一个进程写操作被顺序执行。
分布式事务解决方案
刚性事务(强一致性)
- 2PC:二阶段提交,是同步阻塞协议,第一阶段有超时机制,第二阶段则不允许失败,只能不断重试。如根据XA规范实现的JTA,需要一个的协调者,分为准备和提交两阶段。
- 同步阻塞:会因为某个参与者而导致所有参与者阻塞;
- 单点:协调者为单点,一旦故障会导致资源一直锁定;
- 脑裂:提交阶段,如只有部分参与者收到了提交请求,会出现数据不一致。
- 3PC:三阶段提交,分为准备、预提交和提交三个阶段,准备阶段是询问参与者是否有条件接受事务,预提交阶段只记录日志不提交事务,如超时则中断事务,提交阶段如果超时会提交事务。暂无实现方案,因为并没有解决数据可能不一致的问题。
- 阻塞延后:预提交阶段判断参与者无法接受事务则立即中断;
- 单点解决:第二阶段和第三阶段都有超时机制;
- 脑裂依旧存在:第三阶段如果部分参与者未收到abort请求会在超时后提交。
柔性事务
分为两种思想TCC和Saga,柔性事务只能实现最终一致性,其关键就是保证操作的幂等。
-
TCC:类似2PC,实现复杂,需要三个操作try、confirm和cancel,try操作执行分支事务,将数据设置为软状态,能保证一致性;在所有try均成功后,confirm操作用于将软状态修改为正常提交状态;一旦有一个try操作失败,则对try操作已经成功的事务执行cancel操作,以将软状恢复。
-
Saga:实现简单,只需要实现执行模块和补偿模块即可,补偿模块分为向前补偿和向后补偿,如果是向后补偿则可以直接等同于执行模块,因为分支事务可能处于部分成功和部分失败的状态,故不满足一致性;
- 操作落库加补偿:使用新增操作记录或设置中间状态等方式保证操作信息落库后返回结果,一般会先异步处理一次以保证时效性,使用定时补偿机制保证分布式事务操作全部执行成功或回滚,可靠性高但实现复杂。
- 消息最终一致性:使用事务消息或本地消息表的方式保证消息能被成功发送,MQ再确保消费者消费成功即可,缺点是依赖消息中间件的可靠性、无法回滚、有隔离性问题。
分布式一致性算法
Paxos
强一致性,属于共识算法,无法解决拜占庭将军问题,缺点是角色过多,不容易实现。
- 角色:
- Proposal:提案,即更新请求,提案编号N为自增值。
- Proposer:负责帮客户端上交提案。
- Acceptor:负责为提案投票,同时记录自己的投票历史。
- Learner:负责记录通过的提案。
- 步骤:
- Propser准备一个编号为N的提案;
- Propser询问Acceptor是否接收过编号为N的提案,如果多数都没有接受过则进入下一步,否则本提案不被考虑;
- Propser向Acceptor发送真正的提案,Acceptor无条件同意从未接收过的提案,不同意比自己以前接收过的提案编号要小的提案,一旦提案被多数同意后进入下一步;
- 编号为N的提案通过,Propser向已接受了提案的Acceptor发送提交请求,向未接受的Acceptor发送提案要求他们强制接受;
- 最后Learner记录提案,并将结果返回客户端。
-
问题:假设存在多个Proposer,他们不断向Acceptor发出提案,还没等到上一个提案投票达到多数,下一个提案又来了,Acceptor接受了下一个提案后就会拒绝当前提案,于是所有提案都无法通过。
- 改进:只允许存在一个Proposer,称之为Leader,Acceptor只会处理最新的Leader发出的提案。
Raft
是对Paxos算法的简化和改进,算法分为Leader选举和状态复制。
- 角色:
- Leader:负责发出提案;发起心跳,响应客户端,创建日志,同步日志;
- Follower:负责同意Leader发出的提案;接受Leader的心跳和日志同步数据,投票给Candidate。
- Candidate:负责争夺Leader;临时角色复制,负责发起Leader竞选投票,由Follower转换。
- Leader选举:每个Follower都持有一个定时器,当定时器截止集群中仍然没有Leader,则会声明自己为Candidate并竞选Leader,并发送消息给其他节点争取投票,获取多数支持的Candidate将称为第M任Leader(M为最新任期),Leader在任期间会不断给其他节点发送心跳,其他节点收到心跳后会将定时器重置并回复心跳。
- 如果没有Candidate获取多数选票,他们将随机延后一段时间后重新发起投票。
-
状态复制:Leader接受到客户端的提案请求后,会将提案请求包含在发出的下一次心跳中,Follower接受到心跳后回复Leader,Leader在接受到多数回复后会确认提案并记录,然后回复客户端,Leader通知Follower确定提案并保存。
- 脑裂:如果发生脑裂,则非多数的子集群将无法确认提案,集群恢复之后,将只会接受最新任期的Leader的指挥,旧的Leader会退化成Follower。
ZAB
大部分和raft相同,但状态复制的过程中,心跳是由Follower向Leader发送。
Gossip
弱一致性算法,Redis集群使用Gossip协议传播节点的状态信息。没有角色之分,节点接受到数据变更,会随机传播给其他节点,收到变更后的节点再重复此过程,直到所有节点都被感染。
脑裂问题
Redis
指某个master突然脱离了集群网络导致出现了分区,则从节点会被升级为master,出现脑裂。
- 设置最小从节点数量,至少是1;
- 设置最小数据复制和同步的延迟;
ZooKeeper
- 集群网络出现异常导致集群被分隔,多数派的子集群选举出了自己的leader,出现脑裂;
- leader假死后,集群选举出新的leader,出现脑裂。
- 提案必须要超过半数节点投票才能确认,所以少数派的子集群永远不会成功确认提案;
- 旧leader恢复后向followers发出写请求会被拒绝,因为follower只接受最新选举出来的leader的请求。
Nacos
雪崩机制:即将注册信息保存到调用方本地。
CP:使用Raft协议解决脑裂问题,与ZAB协议基本相同。 AP:使用distro协议,基于临时数据的一致性协议,客户端注册后与一个Nacos节点建立会话,并维持心跳,服务器之间异步增量同步和定时全量同步来保证数据在集群内部的最终一致性,同时集群节点之间也会进行心跳探活。
分布式协调服务
ZooKeeper
使用场景
- 系统A发送消息后,在ZooKeeper指定节点上注册监听器,当消息被系统B消费后,系统B修改节点的值,ZooKeeper通知到系统A。
- 主机器注册临时节点到ZooKeeper,备用节点注册该节点的监听器,一旦主节点故障,ZooKeeper立马通知切换到备用节点。
特点
- 顺序一致性:从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
- 原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
- 单一视图:无论客户端连到哪一个ZooKeeper服务器上,其看到的服务端数据模型都是一致的。
- 可靠性:一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
- 实时性:仅能保证一段时间内最终一定会读到最新的数据。
组成
- znode:使用层次化的多叉树形式,一个节点都能保存数据,被称为znode,znode属性包括stat和data,分为容器节点(相当于文件夹)、持久节点、临时节点(生命周期随会话结束)和带ttl的临时节点,同时节点还可以是顺序节点。
- ACL:权限控制。
- Watcher:监听器,只能触发一次。
- Session:会话,可以视作一个TCP长连接,客户端通过定时发送心跳保持会话。
集群
- Leader:为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。
- Follower:为客户端提供读服务,如果是写服务则转发给 Leader,参与选举过程中的投票。
- Observer:为客户端提供读服务,如果是写服务则转发给 Leader,不参与选举过程中的投票,也不参与“过半写成功”策略,可以在不影响写性能的情况下提升集群的读性能。
Nacos
注册信息是保存在内存的map结构内(非文件系统),配置信息是基于数据库存储数据,支持内置数据库和MySQL等等。
分布式链路追踪
SkyWalking
结构
- Agent :负责从应用中,收集链路信息,发送给 SkyWalking OAP 服务器。
- SkyWalking OAP :负责接收 Agent 发送的 Tracing 数据信息,然后进行分析后,存储到外部存储器 Storage,最终提供查询功能。
- Storage :链路信息数据存储。目前支持 ES、MySQL、Sharding Sphere、TiDB、H2 多种存储器。
- SkyWalking UI :负责提供控台,查看链路等等。
负载均衡算法
- 轮询法:顺序的分配到每一台服务器;
- 随机法:随机算法指定;
- 源地址哈希法:针对源地址特定的哈希算法,同一IP会被转发到同一台服务器;
- URL哈希:同一URL会被分配到同一台服务器;
- 一致性哈希:使用一致性哈希算法,相同的IP和URL都会被分配到同一机器;
- 加权轮询:将请求顺序按照权重进行分配;
- 加权随机:按照权重随机请求;
- 最小连接数:每次都把请求分配到当前积压连接数最小的服务器。