作者:沈畅棉多多_574 | 来源:互联网 | 2023-07-25 13:11
本文参考文献----《微服务架构与实践(第2版)》电子工业出版社出版 王磊 等著
服务接入
-
边缘服务:边缘服务的作用是简化外部消费者对系统的调用。它可以是纯前端的页面,用于获取多个服务的数据后提供给消费者,也可以是聚合类服务,将结果进行聚合后返回给消费者。
边缘服务的典型场景是支撑多样化的消费者,这种情况有时候也称作 BFF(Backend for Frontend)。譬如在支撑多种终端设备的应用中,通常会对服务的结果做必要的聚合或者裁剪,然后提供给不同的设备,如 PC 端或者其他设备等。Netflix 在其微服务的实现中,通过 BFF 为几十种设备提供不同的视频数据。
-
API 网关(API Gateway):同边缘服务类似,API 网关的存在也是为了简化消费者对系统的调用。不过它更关心的是将请求有效路由到服务,而非业务逻辑的聚合。
API 网关的主要功能如下所示:
- 请求路由。API 网关位于服务的外层,消费者端先将请求发送到网关,网关再路由至各服务。
- 协议转换。对于某些场景,服务内部实现的接口可能与消费者端期望的接口不一致。如内部使用 RPC 或私有协议,而消费者端期望 REST 接口。此时就需要通过网关完成协议的转换。
- 公共功能。因为 API 网关是微服务系统中的集中化部分(它同所有服务交互)。因此在实际落地过程中,可以将服务内部所需的一些公共功能,如认证、鉴权、限流、流量统计等,移至 API 网关实现,降低修改和升级成本。
API网关的实现如下:
另外,在某些系统中,会将 API 网关作为聚合服务使用。不过笔者建议,避免将复杂的聚合逻辑放在网关内。主要原因是:
- 确保网关职责单一,聚焦于请求路由等功能,而非复杂逻辑的实现。
- 降低其替换成本。网关是服务系统中的支撑组件,随着技术的演进,应持续演进并使用更有效的技术替换。
使用 API 网关,具有如下明显的优势:
- 屏蔽服务的接口变化。API 网关的存在,使消费者端使用的接口与服务内部的接口隔离开。当服务内部的接口发生变化时,可以通过网关屏蔽对消费者端的影响。
- 降低公共功能的维护成本。对于某些微服务系统的公共功能,如认证、授权、SSL、流量统计、流控等,通过在网关内统一处理,降低了维护成本。
虽然 API 网关有如上所述的优点,但它也会带来新的问题:API 网关是对外的唯一入口,其发生故障将导致整个系统的不可用,API 网关性能下降会导致所有服务的性能下降。因此,需要做好 API 网关的无状态、可用性设计,并对 API 网关进行性能实测,保障具备足够的应对突发流量的能力。
-
数据一致性:
分布式系统与数据一致性:单体应用通常会使用单一的关系型数据库来存储数据,事务的 ACID 机制会保障数据的一致性。
- 原子性(Atomic):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable)。
- 持久性(durability):事务处理结束后,对数据的修改就是永久的,即便系统有故障时也不会丢失。
微服务架构的分布式特性带来了数据一致性问题。通常在微服务架构中,每个服务可能拥有独立的数据库。当服务间调用引发数据的修改时,如果中间的步骤出错,就会出现数据不一致的情况。
CAP 原则是指在分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错(Partition Tolerance)三者不可兼得,只能选择其中两个。
- 一致性:对于同样的请求,访问到不同的节点时,返回的结果是相同的。数据一致性还包括强一致性和弱一致性。强一致性是指,更新完成后,任何后续的请求都返回更新过的值,这需要牺牲可用性。弱一致性是指,在写入完成后,不保证后续访问都会返回更新过的值。
- 可用性:在任何时候,节点都能对请求做出响应。
- 分区容错:当网络出现问题时,系统整体仍然可以工作。
虽然上面提到 CAP 原则是三选二,但并不是说另外一个维度就无法满足需求。以 Google 的 Spanner 为例,它是满足「C」和「P」的系统,但其可用性依然可以达到 5 个 9。
最终一致性及实现方式:基于 CAP 理论以及互联网一些产品的分布式实践,技术人员发现,在无法实现强一致性的情况下,可以采用适当的方法达到最终一致性。于是出现了 BASE 理论,即基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventual Consistency)。
- 基本可用。在分布式系统出现故障时,允许系统的可用性下降或者部分可用,譬如因为网络故障导致延迟的增加,或者当请求过多时,系统提供熔断或降级的功能。
- 软状态。系统中的数据可以存在中间状态。在不影响整体可用性的情况下,可以容忍数据的同步延迟。
- 最终一致性。系统在没有后续更新的前提下,返回上一次更新后的值,保证最终返回数据的一致性。
最终一致性的实现方式包括 2PC(2 Phase Commitment Protocol,两阶段提交协议)、3PC(3 Phase Commitment Protocol,三阶段提交协议)、TCC(Try-Confirm-Cancel,尝试-确认-取消)以及 Sagas(补偿交易)。
-
两阶段提交:两阶段提交引入协调服务(协调者),其根据所有参与者反馈内容决定是提交事务操作,还是中止事务操作。分布式系统中的节点(参与者)虽然知道自己的操作状态(成功或者失败),但是无法知道其他节点的操作结果。为了保证数据的一致性,需要引入协调的组件来统一协调节点的操作结果,并最终指明节点是否要把操作结果进行真正的提交(比如将更新后的数据写入数据库)。两阶段提交,顾名思义就是分为两个阶段:准备阶段和提交阶段。
在准备阶段,协调者查询所有参与者(数据库),确认是否准备好事务提交(将事务执行到准备提交的阶段),协调者只有在收到肯定的回答后才进入下一个阶段。
在提交阶段,协调者通知参与者执行提交,每个参与者执行提交,并释放事务占用的锁和资源,返回成功的确认。协调者收到所有的参与者成功的确认后,才认为本次分布式事务完成。
如果在准备阶段出错,协调者通知所有参与者回滚事务,释放事务的资源和锁,并返回确认结果。如果所有回滚都成功,则协调者回滚此次事务。
两个阶段提交的原理和实现比较容易理解。实际上,在两阶段提交中可能存在一些挑战:首先,协调者存在单点故障问题,一旦协调者失败,参与者的事务将会处于阻塞状态,妨碍参与者对外提供服务。参与者的提交都需要通过协调者调度,中间处于同步阻塞状态,效率比较低。此外,两阶段提交不能完全解决数据一致性问题,因为在提交阶段如果出现网络问题,某些参与者可能由于没有收到提交的消息而阻塞。两个阶段的提交示意图,如下图所示。
-
三阶段提交:针对两阶段提交的问题,主要是协调者失败引发的问题,三阶段提交进一步将准备阶段又划分为两个阶段,并引入了超时策略来缓解阻塞的问题。所以三阶段提交就变成了:确认能否进行事务操作、预提交和提交。
在事务执行的确认阶段,协调者发送查询请求,如果参与者拒绝或者返回超时,则协调者认为事务失败,给所有参与者发送取消的消息。反之,参与者进入预提交阶段的准备状态。预提交阶段和 2PC 的准备阶段区别不大,如果收到协调者的取消事务消息,好预提交失败或者等待超时,参与者都会取消操作。如果收到协调者发来的预提交消息,则参与者返回确认(成功/失败),并等待最终协调者发送的提交的消息。如果协调者出现错误、超时或者收到参与者否定的消息,协调者取消事务并给所有参与者发送取消消息。反之则发送提交的消息给所有参与者。
提交阶段,如果协调者在等待参与者的确认消息时超时,会取消事务。参与者收到预提交消息后,如果协调者超时或者失败,参与者继续完成提交的操作。
3PC 虽然解决了 2PC 中同步阻塞的问题,以及在协调者和部分参与者失败的情况下出现的数据不一致的问题,但是如果在提交阶段,参与者因为网络原因没有收到取消事务的消息,在超时的情况下继续提交,仍然会出现数据不一致的情况。三个阶段的提交示意图
-
TCC模式:在微服务的分布式场景下,业务运行流程可能较长,采用传统 ACID 的事务方式会造成运行等待时间过长。除了前面提到的 2PC/3PC 的事务处理方式,还可以采用事务补偿的方式,即先执行正常的业务逻辑,当出现问题时,如流程中某些环节出错,再执行补偿的动作,即取消或者重试。TCC(Try-Cancel-Confirm)就是补偿交易的一种方式,它通过对业务逻辑的调度实现分布式事务。整个过程分为两个阶段:Try、Cancel 或者 Confirm
第一阶段,执行正常流程中的业务尝试操作(Try),并在协调者中注册所有的操作,即确认操作(Confirm)和取消操作(Cancel),同时修改子业务在数据库中的状态
第二阶段,如果第一阶段的操作都成功,则协调者执行各子业务的确认操作。反之如果失败或者超时,则执行取消操作。这一阶段的操作应该是幂等的,以保证在操作出错、超时的时候可以重试
-
Sagas模式:Sagas 是一种失败管理模式,用来解决运行时间较长的事务,如分布式事务的问题。
Sagas 模式基于 Hector Garcia-Molina 等发表于 1987 的论文Sagas。Sagas 由子事务的集合组成,每个子事务(称为 Saga,或者请求)都具有事务补偿的功能(撤销操作)。子事务要么全部执行成功,要么因为某些子事务执行失败,会导致所有子事务回滚,这里对于回滚操作的要求是满足幂等性。
Sagas 损失了事务的原子性,但保证了分布式系统的可用性。Sagas 可以解决 2PC 在分布式系统场景下扩展性不足的问题。以出行场景为例,一个行程规划的事务就相当于一个 Saga,其中包含四个子事务:机票预订、租车、酒店预订、支付,如下图所示。根据上面的 Sagas 运行原理,可以定义出行程中关联事务以及相应的补偿,如下表所示。
Sagas 中的事务相互关联,应作为(非原子)单位执行。任何未完全执行的 Saga 是不满足要求的——如果产生事务,必须得到补偿。要修正未完全执行的部分,每个 Saga 子交易 T1 应提供对应补偿事务 C1。当每个 Saga 子事务 T1,T2,…,Tn 都有对应的补偿定义 C1,C2,…,Cn-1 时,在最佳情况下 Saga 系统可以保证子事务序列 T1,T2,…,Tn完成,或者完成序列 T1,T2,…,Tj,Cj,…,C2,C1,0
通过上述定义的事务/补偿,Sagas 可以保证数据的一致性,并满足如下业务规则:
- 所有预定都被执行成功,如果任何一个执行失败,都会被取消
- 如果最后一步付款失败,所有预订也将被取消
Saga适用的场景如下:
- 嵌套调用。如在网上购物时,会依次经过下单、支付服务和第三方支付这几个子事务,其中下单依赖于支付服务的返回状态,而支付服务也包含了多种可选的支付方式,并依赖于具体支付方式返回的结果。通过 Saga 可以清晰地看到一个完整事务中各个服务之间的关系,在异常时也能快速定位出现问题的子事务
- 高并发。如在秒杀场景下,在成功扣除库存和完成支付后方可认为秒杀成功,若成功扣除库存但支付失败,则自动进行补偿(即恢复库存)。鉴于 Saga 只有提交和补偿两种状态,在成功场景下只需对每个子事务进行一次调用即可,因此可以在高并发下保持高性能。
- 调用时间长。如在线上购买电影票,选好座位后一般会有 15 分钟的支付时间。Saga 仅在子事务的提交阶段对资源进行短暂的锁定,且通过超时机制确保事务超时后能自动补偿,即在规定时间内没有支付成功就自动释放锁定的座位,极大地简化了业务出现异常时的处理逻辑。
Saga 的解决方案比两阶段提交更易扩展。在事务可补偿的情况下,Saga 相比 TCC 对业务逻辑几乎没有改动,性能更高。集中式的 Saga 设计解耦了服务与数据一致性逻辑及其持久化设施,并更容易排查事务中的问题。
Saga 目前已经有一些成功使用的案例,比如微软的大型在线射击游戏 Halo。Saga 的实现通常需要 SEC(Saga Execution Coordinator)来协调子事务的执行和回滚,还需要 Saga Log (类似 Mysql 数据库的 bin log)记录子事务执行的记录。SEC 将操作写入 Saga Log,应用每个 Saga 的子事务(请求),并且在需要的时候回滚事务。Saga Log 可以是分布式的,使用类似 Kafka 的消息队列工具实现。子事务最多执行一次,但是补偿事务至少执行一次。在所有子事务完成时或者子事务开始回滚时,如果 SEC 本身失败,并不会对最终结果有影响。在其他场景下,SEC 失败则认为事务失败,并且可以根据 Saga Log 的记录来回滚操作。
解决方案对比分析:
上一篇 微服务架构与实践笔记(二)微服务关键技术之服务划分与实现