参考《高可用可伸缩微服务架构》
CAP:
C(一致性 Consistency),所有节点上的数据时刻保持一致
A(可用性 Avaliability),每个请求都能够收到一个响应,无论响应成功或者失败
P(分区容错 Partition-tolerance),系统出现脑裂以后,可能导致某些server 与集群中的机器失去联系
BASE:
XA事务虽然可以保证数据库在分布式系统下的ACID特性,但会带来性能方面的影响
eBay提出了BASE理论
Basically available:数据库采用分片模式,把100w的用户数据分布在5个实例上,如果破坏了其中一个实例,仍然可以保证80%的用户可用
Soft-state:在基于client-server模式的系统中,server端是否有状态,决定了系统是否具备良好的水平扩展、负载均衡、故障恢复等特性。Server端承诺会维护client端状态数据,这个状态仅维持一小段时间,这段时间以后,server端会丢弃这个状态,恢复正常状态
Eventually consistent:数据最终一致性
由于分布式系统中存在网络的不稳定因素(网络故障、丢包等),因此在分布式系统中P(分区容错性)是首先要满足的。此时,如果要保证C(一致性),当A更新了数据,必须等待B同步更新数据才能响应客户端,如果中间出现网络问题,就不能响应客户端,这时就无法保证A(可用性);
如果要保证A(可用性),就需要立即响应客户端,但A、B数据可能会不一致,无法保证C(一致性)
SOA即面向服务架构,关注点是服务,现有的分布式服务化技术有Dubbo等
- 微服务是一种经过改良架构设计的SOA解决方案,是面向服务的交互方案
- 微服务更趋向于以自治的方式产生价值
- 微服务与敏捷开发的思想高度结合在一起,服务的定义更加清晰,同时减少了企业ESB开发的复杂性
- 微服务是SOA思想的一种提炼
- SOA是重ESB,微服务是轻网关
-
乐观锁,使用版本号
-
唯一索引,可以把作为唯一索引的键单独称为一个表,作为去重表
-
分布式锁,使用setnx,如果setnx返回0就代表重复请求,同时在业务逻辑处理完成后,删除缓存
首先,一般来说,个人建议是,你们从业务逻辑上设计的这个系统最好是不需要这种顺序性的保证,因为一旦引入顺序性保障,比如使用分布式锁,会导致系统复杂度上升,而且会带来效率低下,热点数据压力过大等问题。
下面我给个我们用过的方案吧,简单来说,首先你得用 dubbo 的一致性 hash 负载均衡策略,将比如某一个订单 id 对应的请求都给分发到某个机器上去,接着就是在那个机器上,因为可能还是多线程并发执行的,你可能得立即将某个订单 id 对应的请求扔一个内存队列里去,强制排队,这样来确保他们的顺序性。
但是这样引发的后续问题就很多,比如说要是某个订单对应的请求特别多,造成某台机器成热点怎么办?解决这些问题又要开启后续一连串的复杂技术方案......曾经这类问题弄的我们头疼不已,所以,还是建议什么呢?
最好是比如说刚才那种,一个订单的插入和删除操作,能不能合并成一个操作,就是一个删除,或者是其它什么,避免这种问题的产生。
-
2PC两阶段提交
分准备阶段、提交阶段,由事务管理协调器发起
准备阶段:事务管理器向参与者发起指令,参与者评估自己的状态,如果参与者评估指令可以完成,则会写redo或者undo日志,然后锁定资源,执行操作,但并不提交。如果其中一个参与者返回准备准备失败,则协调者向参与者发起中止指令,参与者取消已经变更的事务,执行undo日志,释放锁定的资源
提交阶段:如果每个参与者明确返回准备成功,也就是预留资源和执行操作成功,则协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源;
缺点:
阻塞,对于任何一次指令都必须收到明确的响应,才会继续下一步,否则处于阻塞状态,占用的资源被一直锁定,不会释放
单点故障,协调者宕机,参与者没有协调者指挥,会一直阻塞,需要自己实现协调者选举
脑裂,协调者发送指令,有的参与者可能会没有接收到指令,导致多个参与者事务状态不一致
-
3PC三阶段提交
是二阶段提交的改进版本,通过超时机制解决了阻塞问题
询问阶段:协调者询问参与者是否可以完成指令,协调者回复是或者不是,不做真正的操作,如果超时会导致中止
准备阶段、提交阶段:与二阶段相同
-
TCC补偿事务
将一个任务拆分成Try、Confirm、Cancle三个步骤,主业务先发起请求执行Try,如果没有问题,则提交任务到TCC事务管理器,由事务管理器执行Confirm,如果出现问题,再执行逆操作Cancel
优点:解决了阻塞问题,通过TCC自动化Cancel降低了不一致的情况
缺点:实现还是臃肿,在极端情况下会出现不一致和脑裂问题
-
RocketMQ事务
系统A发送一个事务消息到MQ,MQ会反馈消息接收成功,系统A会收到消息接收成功回调,此时系统A执行本地事务,执行成功后向MQ发送确认消息,否则向MQ取消消息,MQ收到确认消息后,消费系统B就能够接收到该事务消息,执行操作
-
Saga模式
将长事务拆分为多个可以交错运行的子事务集合,其中每个子事务都保证事务的一致性。整个运行过程由Saga事务协调器来协调,如果每一个子事务都正常结束,则整个事务正常完成;如果某个子事务失败,则整个事务失败,会根据相反顺序执行补偿操作。
每个Saga由一系列子事务T组成,每个T都对应一个补偿动作C,补偿动作C用于撤销T造成的结果
Saga的执行顺序有两种(0< j < n)
- 成功的执行顺序:T1,T2,T3,...,Tn
- 最终失败的执行顺序:T1,T2,T3,...,Tj, Cj,...C3,C2,C1。 Tj 代表执行失败,执行失败后,会执行补偿动作来撤销执行
Saga 定义了以下两种恢复策略
- backward recovery: 向后恢复,补偿所有已完成的事务。如果任一子事务失败,则撤销之前所有成功的sub-transation,整个Saga的执行结果被撤销
- forward recovery: 向前恢复,重试失败的事务,假设每个子事务最终都会成功。适用于必须成功的场景,该情况不需要Cj
-
Paxos算法模式
该算法模式有3个角色:
- Proposer,提议者,提出提案(Proposal),Proposal信息包括提案编号和提议的值
- Acceptor,决策者,参与决策,回应Proposers的提案,如果收到的Proposers获得了多数Acceptor接受,则Acceptor称该Proposal被批准
- Learner,学习者,不参与决策,从Proposers/Acceptors学习最新达成一致的提案(Value)
Paxos 算法运行在运行宕机故障的异步系统中,它不要求可靠的消息传递,也容忍消息丢失,延迟,乱序和重复,它利用大多数机制保证了“2F+1”的容错能力,即“2F+1”个节点的系统最多允许F个节点同时出现故障
- AT模式,基于2PC实现,适用高并发、对性能敏感的业务场景
- TCC模式,基于TCC实现,侵入业务,需要实现TCC的逻辑
- Saga模式,基于Saga实现,适用业务流程长的场景
- XA模式,基于XA协议(2PC实现),适用使用XA模式的老应用迁移到Seata平台,以及AT模式未适配的数据库应用
- 查询模式,通过查询了解调用服务的最终处理情况,决定下一步做什么
- 补偿模式,有了查询模式,就可以知道服务所在状态,通过补偿模式修正操作
- 定时校对模式
-
更新操作,先删除缓存,再修改数据库,如果数据库失败,数据库中的还是旧数据,缓存是空的,在查询的时候,发现缓存是空的,就会从数据库取数据,然后更新到缓存中
-
方案一在高并发下,先删除了缓存,但数据库还没修改成功,此时读请求过来,发现没有缓存,就会去查询数据库,放入缓存,随后更新数据库操作完成了,此时缓存中的数据是旧数据,与数据库中的数据不一致。
方案如下:
在请求的时候,发现没有缓存,就发一个更新操作,到JVM队列,主线程循环判断缓存是否更新,并设置超时时间,如果超时,就直接取数据库数据,返回旧数据。JVM队列中,如果之前已经有更新操作,自动丢弃后面的更新操作,防止频繁更新。
如果实例服务是分布式部署,需要将同一请求路由到同一个实例,可以通过某个请求参数的hash路由,也可以通过Nginx的hash路由功能
要保持模块模块大小适中,尽可能减少调用深度,多扇入少扇出,保持高内聚低耦合,单入口、单出口,模块的作用域要在模块内,模块功能是可预测的,另外面向对象设计要遵守如下原则:
- 单一责任原则:一个类只做一种责任类型
比如,一个“用户类”不应同时负责用户信息管理和订单生成,这两项职责应拆分到“用户类”和“订单类”中
- 开放-封闭原则:支持扩展,不支持修改
比如,一个“支付接口”可以通过新增“支付宝实现类”,“微信支付实现类”来扩展支付方式,而不是修改原有的接口
- 里氏替换原则:子类可以替换父类
比如,一个支付接口,子类无论是“支付宝实现类”还是“微信支付实现类”,都能正常实现支付
- 依赖倒置原则:细节依赖抽象
比如,订单服务调用物流服务时,依赖的是物流服务的抽象接口,而非具体的“顺丰物流”、“圆通物流”
- 接口分离原则:不强迫适用,依赖抽象,不依赖具体
一个 “多功能设备接口” 不应包含 “打印”“扫描”“复印” 所有方法,而应拆分为 “打印接口”“扫描接口” 等,让仅需打印功能的客户端只依赖 “打印接口”
