A. 系统架构理论基础(理论) — 数据一致性
算法
- 事务算法
- 算法
- Redo算法
- Undo算法
- Redo/Undo算法
- 算法
- 一致性协议
- 两阶段提交协议
- 第一阶段请求
- 第二阶段提交
- 向量时钟
- RWN协议
- Paxos协议
- Raft协议
- 两阶段提交协议
一致性模型
- 强一致性
- 最终一致性:最终一致性不代表不能实时修改,而是异常情况下采用最终一致性,正常情况下可以实时一致性
- 因果一致性
- “读你所写”一致性
- 会话一致性
- 单调读一致性
- 单调写一致性
分布式事务算法
- 分布式事务算法类型
- 类型一
- 崩溃不可恢复的
- 崩溃可恢复的
- 类型二
- 拜占庭问题
- 非拜占庭问题
- 类型一
- 分布式事务算法场景
- 可靠的复制状态机
- 可靠的复制数据存储和配置存储
- 使用领头人选举机制实现高可用的处理系统
- 分布式协调和锁服务
- 可靠的分布式消息队列和消息传递
- 分布式事务算法性能
- 系统负载变化因素
- 吞吐量:在负载峰值时,单位时间内提出提议的数量
- 请求类型:需要修改状态的写请求的比例
- 读请求的一致性要求
- 如果数据大小可变,请求的大小
- 部署策略
- 局部区域部署,还是广域部署?
- 采用的是哪种仲裁过程,大部分进程的分布情况如何?
- 该系统是否使用分片、流水线和批处理技术?
- 等等
- 系统负载变化因素
- 强一致性 — 法定租约
- 针对数据的一部分给系统中的法定人数进程发送了一个租约,这个租约是带有具体时间范围的(通常很短)。在这个租约有限期间,任何对该部分数据的操作都必须要被法定租约中的所有进程响应。如果租约中的任何一个副本不可用,那么该部分数据在租约过期前将服务被修改。
分布式事务解决方案
- 两阶段提交
- 优点:尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。
- 缺点: 实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景,如果分布式系统跨接口调用,目前 .NET 界还没有实现方案。
- 补偿事务(TCC)
- 步骤
- Try 阶段主要是对业务系统做检测及资源预留
- Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
- Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
- 优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
- 缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
- 步骤
- 本地消息表(异步确保)
- 核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。
- 步骤
- 消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。
- 消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
- 生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
- 优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中 有现成的解决方案。
- 缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
- MQ 事务消息
- RocketMQ
- 第一阶段Prepared消息,会拿到消息的地址
- 第二阶段执行本地事务
- 第三阶段通过第一阶段拿到的地址去访问消息,并修改状态
- 优点: 实现了最终一致性,不需要依赖本地数据库事务
- 缺点: 实现难度大,主流MQ不支持,没有.NET客户端,RocketMQ事务消息部分代码也未开源
- RocketMQ
- Sagas 事务模型
- 核心思想就是拆分分布式系统中的长事务为多个短事务,或者叫多个本地事务,然后由 Sagas 工作流引擎负责协调,如果整个流程正常结束,那么就算是业务成功完成,如果在这过程中实现失败,那么Sagas工作流引擎就会以相反的顺序调用补偿操作,重新进行业务回滚。
幂等性
- 支付系统中的幂等机制
- 第一个因素是幂等令牌(Idempotency Key)。客户端和服务器端通过什么方式来识别,这实际上是同一个请求或是同一个请求的多次尝试。这往往需要双方有一个既定的协议, 比如账单号或者交易令牌,这种在同一个请求上具备唯一标识的元素,这种元素通常由客户端生成。
- 第二个因素是确保唯一性(Uniqueness Guarantee)。服务器端用什么机制去确保同一个请求一定不会被处理两次,也就是微信支付如何确保,同一笔交易不会因为客户端发送两 次请求就被处理多次。
- 机制
- 一是幂等令牌什么时候产生,怎样产生。这一点很重要。拿上面的例子来说,就算微信支付可以保证,每一个请求对应的支付交易一定只会被处理一次,但是这个请求的多次重复,一定要共有微信可以识别的某个标识。
- 二是令牌有没有被误删的可能。这是上面问题的一个特殊情况。幂等令牌是由客户端生成的,那如果生成的令牌在被使用后(一次微信支付请求中使用了),不小心因为数据库回滚 (DB Rollback) 等原因被删除了,那么客户端就不知道自己其实已经发过一次请求。这就有可能生成一个新的账单,并产生全新的令牌,而服务端对此则一无所知。
- 三是各种竞争条件。我在前面讲过,用数据库的读检查来确保唯一性经常因为竞争而不生效,其实一个需要幂等的系统中,保证唯一性的各个环节和实现,都要考虑竞争条件(Race Condition)。
- 四是对请求重试的处理。这大部分是服务器端要做的工作。一个常见的方法是:区分正在处理的请求、处理成功和处理失败的请求。这样当客户端重试的时候,根据情况或者直接返 回,或者再次处理。这就好像之前提到的微信支付的例子,微信支付服务需要知道每一笔交易的处理情况,只有这样,当面对再次转账请求时,才能知道应该用什么方式去处理相应 的请求。
- 五是一个系统中需要多层幂等。这是什么意思呢?A 发送请求给 B,B 处理的一部分是要发送请求给另一个系统 C,C 在处理的过程中还可能需要发请求给另一个系统 D … D 处理 完了返回给 C,C 返回给 B,B 返回给 A。在这个链条中,如果 A、B、C、D 中任何一个系统没有正确实现幂等,也就是出现了 “幂等漏洞”,那么一次请求还是有可能被多次执 行,产生区别于一次执行的影响。
部署
- 负载均衡
- 客户端集中的某个物理区域,最好把副本放在离客户端最近的地方。但是如果客户端突然流量猛增,可能导致最近的副本崩溃,然后是第二近的副本,以此类推,最终造成连锁故障。
- 当集群部署在不同的数据中心,有可能出现Leader失败,新的Leader所处的数据中心承受不住Leader的流量。因为Leader的流量会相对副本来说更大
- 多个集群的Leader,尽量平均部署在多个数据中心,防止一个数据中心的出口成为瓶颈。
- 副本的数量
- 对可靠性的要求
- 计划内维护的频率
- 危险性
- 性能
- 成本
- 副本的位置:距离越远,性能月底,但是承受失败的程度也会增加
- 故障承受能力
- 延迟的敏感性
- 故障恢复
- 客户端可见性能
监控
- 每个集群的成员数量,以及每个成员的状态(健康/不健康)
- 始终落后的副本
- 领头人角色是否存在
- 领头人角色变化的次数
- 共识交易记录数字
- 系统中的提议数量,以及系统中被接受的提议数量
- 吞吐量的延迟
- 针对提议接收时间的延迟分布
- 系统不同部分之间观察到的网络延迟
- 接收者在持久化日志上花费的时间
- 系统每秒处理的字节数