分布式服务架构原理设计与实践:2.分布式微服务架构设计原理思
2018-06-12 15:07:36 0 举报
AI智能生成
讲解在微服务架构下如何解决数据的一致性。
作者其他创作
大纲/内容
2.4 超时处理模式
2.4.1 微服务的交互模式
1.同步调用模式
服务1的线程阻塞等待服务2返回处理结果,如果服务2一直不返回处理结果,则服务1一直等待到超时为止。
同步调用模式适用于大规模、高并发的短小操作,而不适合用于后端负载较高的场景,例如:几乎所有JDBC的实现完全使用BIO同步阻塞模式。
2.接口异步调用模式
服务1请求服务2受理某项任务,服务2受理后即刻返回给服务1其受理结果,如果受理成功,则服务1继续做其他任务,而服务2异步处理这项任务,直到服务2处理完这项任务后,才反向地通知服务1任务已经完成,服务1再做后续处理。
接口异步调用模式适用于非核心链路上负载较高的处理环节,这个环节经常耗时较长,并且对时效性要求不高。
3.消息队列异步处理模式
消息队列异步处理模式利用消息队列作为通信机制,在这种交互模式中,通常服务1只需将某种事件传递给服务2,而不需要等待服务2返回结果。在这样的场景下,服务1与服务2可以充分解耦,并且在大规模、高并发的微服务系统中,消息队列对流量具有消峰的功能。
消息队列异步处理模式与接口异步嗲用模式类似,多应用于非核心链路上负载较高的处理环节中,并且服务的上游不关心下游的处理结果,下游也不需要向上游返回处理结果。
2.4.2 同步与异步的抉择
1)尽量使用异步来替换同步操作。
从业务功能的角度触发的,也就是从用户或者使用方的交互模式出发的。
如果业务逻辑允许,用户对产品的交互形态没有异议,则我们可以将一些耗时较长的、用户对响应没有特别要求的操作异步化,以此来减少核心链路的层级,释放系统的压力。
2)能用同步解决的问题,不要引入异步。
从技术和架构的角度触发的,这条原则应用的前提是同步能够解决问题,这隐含了一个含义:如果性能不是问题,或者所处理的操作是短小的轻量级处理逻辑,那么同步调用方式是最理想不过的,因为这样不需要引入异步化的复杂处理流程。
2.4.3 交互模式下超时问题的解决方案
1.同步调用模式下的解决方案(只针对参数可匹配唯一数据插入的接口)
同步返回的两种状态
成功和失败(两种状态同步接口)
唯一数据
api网关调用服务1超时时,api网关熔断机制返回给前端超时信息。
1)所有服务自身数据保存和调用其它服务必需依次调用,这样就能保证自身保存数据失败,调用其它服务肯定是失败的。
2)服务检查自身数据和被调用服务的数据一致性,规则是:1)数据自身不存在则被调用的服务肯定不存在。2)自身存在,被调用的服务不一定存在。
3)根据规则,如果自身存在,被调用的服务不一定存在,因此需要定时任务跑批进行修复。
非唯一列表数据
流程同上,但是要注意的是,非唯一列表数据,当服务1的数据不存在时,网关也不会再次调用服务1,所以一定要严格准守调用规则。
同上
同上
成功、失败和处理中(三种状态同步接口)
同上,就是多了个状态
同上,就是多了个状态
同上,就是多了个状态
注意:同步调用只适用于核心的短小操作,而非核心的长时间操作,请使用异步调用
2.异步调用模式下的解决方案(只针对参数可匹配唯一数据插入的接口)
1)异步调用接口超时
类似同步调用流程,不同的是,如果产生异步调用,则会通过回调返回处理成功或失败。
2)异步嗲用内部超时
类似同步调用流程,不同的是,如果产生异步调用,则会通过回调返回处理成功或失败。
3)异步调用回调超时
类似同步调用流程,不同的是,如果产生同步调用受理模式,则会通过回调返回处理成功或失败。
3.消息队列异步处理模式的解决方案
1)消息队列的生产者超时
这种场景请参考2.3.4节
2)消息队列的消费者超时
消息队列自行处理
提示:此模式一般用于松耦合的非核心服务,服务也无需将结果返回给使用方
2.4.4 超时补偿的原则
1)服务1调用服务2,如果服务2响应服务1并且告诉服务1消息已接受,那么服务1的任务就结束了;如果服务2处理失败,那么服务2应该负责重试或者补偿。在这种情况下,服务2通常接受消息后先持久再告诉服务1接收成功,随后服务2才开始处理持久的消息,避免服务进程被杀掉而导致消息丢失。
2)服务1调用服务2,如果服务2没有给出明确的接受响应,例如网络超时,那么服务1应该持续进行重试,直到服务2明确表示已经接受到消息。在这种情况下容易出现重复的消息,因此服务2中通常要保证滤重和幂等性。
所谓明确响应就是拿到报文,从报文中找到状态位,状态位是成功的才算是明确的响应。
2.5 迁移开关的设计
在接受到服务请求后,在请求创建的关联实体上标记开关,对于以后的处理流程,包括同步和异步的处理流程,都通过订单上的开关来判断是用老系统逻辑还是新系统逻辑
2.1 什么是一致性
是一个抽象的、具有多重含义的计算机术语
传统IT时代,指强一致性,体现在“你中有我、我中有你、浑然一体”
互联网时代,含义远远超出它的原有含义
互联网时代特点
信息量巨大
需要非常强大的计算能力
要求对用户的响应速度快
要求吞吐量指标向外扩展(即水平伸缩)
谈论最多话题是拆分
1)水平拆分
单一节点无法满足性能需求,需要扩展为多个节点,多个节点具有一致的功能,组成一个服务池,一个节点服务一部分的请求量,所有节点共同处理大规模高并发的请求量
2)垂直拆分
按照功能进行炒粉,秉着“专业的人干专业的事”的原则,把一个复杂的功能拆分为多个单一、简单的功能,不同的单一功能组合在一起,和未拆分前完成的功能是一样的。由于每个功能职责单一、简单,使得维护和变更都变得更简单、容易、安全、所以更易于产品版本的迭代,还能够快速地进行敏捷发布和上线。
互联网时代,一致性分布式服务化系统之间的弱一致性,包括应用系统的一致性和数据的一致性
2.2 一致性问题案例
1)下订单和扣库存
说明
下订单和扣库存如何保持一致。如果先下订单,扣库存失败,那么将会导致超卖;如果下订单不成功,扣库存成功,那么会导致少卖。
解决方案
1)数量较小情况下,可以利用关系型数据库的强一致性解决,把订单表和库存表放在同一个关系型数据库中,利用关系型数据库进行下单和扣库存两个紧密相关的操作,达到订单和库存实时一致的结果。(注意:如果量增到后,需要拆业务和拆库就不行了)
2)使用查询模式、补偿模式、异步确保模式(如果是异步)、定期核对模式(为了进一步确保最终一致性)
2)同步调用超时
说明
系统A同步调用系统B超时,系统A可以明确得到超时反馈,但无法确定系统B是否已经完成了预设的功能,于是系统A不知道应该继续做什么,如何反馈给使用方。
曾经客户要求在接口超时的情况下重新通知他们,这在技术上难以实现,因为服务器本身可能并不知道自己超时,可能会继续正常地返回数据,只是开户端并没有接受到结果,因此这不是一种合理的解决方案。
解决方案
1)使用查询模式、补偿模式
3)异步回调超时
说明
此案例类似案例2,不过这是一个受理模式的场景,使用了异步回调返回处理结果,系统A同步调用系统B发起指令,系统B采用受理模式,受理后则返回成功信息,然后系统B处理后异步通知系统A处理结果。在这个过程中,如果系统A由于某种原因迟迟没有收到回调结果,那么这两个系统间的状态就不一致,互相认知的状态不同会导致系统间发生错误,在严重情况下会影响核心链路上的交易状态准确性,甚至会导致资金损失。
解决方案
1)使用查询模式、补偿模式、异步确保模式
4)掉单
说明
在分布式系统中,两个系统协作处理一个流程,分别为对方的上下游,如果一个系统中存在一个请求(通常指订单,其实是保存的数据),另外一个系统不存在,则会导致掉单
解决方案
1)使用查询模式、补偿模式、异步确保模式(如果是异步)、定期核对模式(为了进一步确保最终一致性)
5)系统间状态不一致
说明
此案例类似案例4,不同的是两个系统间都存在请求,但是请求的状态不一致
解决方案
1)使用查询模式、补偿模式、异步确保模式(如果是异步)、定期核对模式(为了进一步确保最终一致性)
6)缓存和数据库不一致
说明
交易系统基本上离不开关系型数据库,依赖关系型数据库提供的ACID特性,但是在大规模、高并发的互联网系统里,一些特殊的场景对读操作的性能要求极高,服务于交易的数据库难以抗住大规模的读流量,通常需要在数据库前增加一层缓存,那么缓存和数据库之间的数据如何保持一致性?是要保持强一致性还是弱一致性呢?
解决方案
1)缓存一致性模式
7)本地缓存节点间不一致
说明
一个服务池上的多个节点为了满足较高的性能需求,需要使用本地缓存,这样每个节点都会有一份缓存数据的复制,如果这些数据是静态的、不变的,就永远不会有问题,但是如果这些数据是半静态的或者经常被更新的,则被更新时各个节点的更新是有先后顺序的,在更新的瞬间,在某个时间窗口内各个节点的数据是不一致的
解决方案
1)缓存一致性模式
8)缓存数据结构不一致
说明
这个案例时有发生,某系统需要在缓存中暂存某种类型的数据,该数据由多个数据元素组成,其中,某个数据元素需要从数据库或者服务中获取,如果一部分数据元素获取失败,则由于程序处理不正确,任然将不完全的数据存入缓存中,在缓存使用者使用时很有可能因为数据的不完全而抛出异常,然后可能因为没有合理处理异常而导致程序出错。
解决方案
1)缓存一致性模式
2.3 解决一致性问题的模式和思路
2.3.1 酸碱平衡理论
1.ACID 酸
A:Atomicity,原子性
C:Consistency,一致性
I:Isolation,隔离性
D:Durability ,持久性
解释:具有ACID特性的数据库支持强一致性,强一致性代表数据库本身不会出现不一致,3个典型关系型数据库Oracle、MySQL、DB2都能保证强一致性。
场景:如果为交易的相关系统做技术选型,则交易的存储应该只考虑关系型数据库,对于核心系统,如果需要较好的性能,则可以考虑使用更强悍的硬件,这种向上扩展(升级硬件)虽然成本较高,却是最简单。有效的。另外,NoSQL完全不适合交易场景,主要用来做数据分析、ETL、报表、数据挖掘、推荐、日志处理,调用链跟踪等非核心交易场景。
2.CAP 帽子原理
针对分布式系统的CAP原理
C:Consistency,一致性。在分布式系统中所有数据备份,在同一时刻具有同样的值,所有节点在同一时刻读取的数据都是最新的数据副本
A:Availability,可用性,好的响应性能。完全的可用性指的是在任何故障模型下,服务都会在有限的时间内处理完成并进行响应。
P:Partition tolerance,分区容忍性。尽管网络上有部分消息丢失,但系统任然可继续工作。
CAP原理证明,任何分布式系统只可同时满足以上两点 ,无法三者兼顾,而BASE(碱)思想解决了CAP提出的分布式系统的一致性和可用性不可兼得的问题。
3.BASE 碱
解释
BASE 是“碱”的意思, ACID 是“酸”的意思,基于这两个名词提出了“酸碱平衡”的理,论,简单来说就是在不同的场景下,可以分别利用 ACID和BASE 来解决分布式服务化系统的一致性问题。
BASE 思想与 ACID 原理截然不同 ,它满足 CAP 原理,通过牺牲强一致性获得可用性,一般应用于服务化系统的应用层或者大数据处理系统中,通过达到最终一致性来尽量满足业务的绝大多数需求。
BASE模型
BA:Basically Available ,基本可用。(目标)
S: Soft State ,软状态,状态可以在一段时间内不同步。(思想)
E:Eventually Consistent ,最终一致性,在一定的时间窗口内, 最终数据达成一致即可。(目标)
解释:系统需要记录操作过程中每个步骤的状态,一旦系统出现故障,系统便能够自动发现没有完成的任务,然后根据任务所处的状态继续执行任务,最终彻底完成任务。
4.对酸碱平衡的总结
解决一致性问题的三条实践经验
可以向上扩张强悍的硬件,比如Oracle、DB2和MySQL,能够保证强一致性。
如果向上扩展成本高,则用廉价硬件运行的开源关系型数据库(例如MySQL)进行水平伸缩和分片,将相关数据分到数据库的同一个分片上。
业务规则限制,无法将相关数据分到同一个分片上
2.3.2 分布式一致性协议
名词解释
应用程序
事务管理器(统管全局的管理者,即协调者)
资源管理器(事务的参与者)
通信资源管理器(事务的参与者)
1.两阶段提交协议(JEE的XA协议):把分布式事务分为两个阶段
1)准备阶段
协调者向参与者发起指令,参与者评估自己的状态,如果参与者评估指令可以完成,则会写 redo 或者undo日志,然后锁定资源,执行操作,但是并不提交。
2)提交阶段
如果每个参与者明确返回准备成功,也就是预留资源和执行操作成功,则协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源;如果任何一个参与者明确返回准备失败,也就是预留资源或者执行操作失败,则协调者向参与者发起终止指令,参与者取消已经变更的事务,执行undo日志,释放锁定的资源。
示意图
通过示意图,可以看到两阶段提交协议在准备阶段锁定资源,这是一个重量级操作,能保证强一致性,但是实现起来复杂、成本较高、不够灵活,更重要的是它有如下致命问题。
阻塞
从描述看,对于任何一次指令都必须收到明确的响应,才会继续进行下一步,否则处于堵塞状态,占用的资源被一直锁定,不会被释放。
单点故障
如果协调者宕机,参与者没有协调者指挥,则会一直阻塞,尽管可以通过选举新的协调者替代原有协调者,但是如果协调者在发送一个提交指令后宕机,而提交指令仅仅被一个参与者接收,并且参与者接收后也宕机,则新上任的协调者无法处理这种情况。
脑裂
协调者发送提交指令,有的参与者接收到并执行了事务,有的参与者没有接收到事务就没有执行事务,多个参与者之间是不一致的。
总结:上面的所有问题虽然很少发生,但都需要人工干预处理,没有自动化的解决方案,因此两阶段提交协议在正常情况下能保证系统的强一致性,但是在出现异常的情况下,当前处理的操作处理错误状态,需要管理人员人工干预解决,因此可用性不够好,这也符合CAP协议的一致性和可用性不能兼得的原理。
提示:过于复杂且任然存在问题,因此鲜有人用
2.三阶段提交协议:两阶段的改进版本,通过超时机制解决了阻塞问题,并分了三个阶段
1)询问阶段
协调者询问参与者是否可以完成指令,协调者只需要回答是或不是,而不需要做真正的操作,这个阶段超时会导致终止。
2)准备阶段
如果在询问阶段所有参与者都返回可以执行操作,则协调者向参与者发送预执行请求,然后参与者写 redo和undo 日志,执行操作但是不提交操作:如果在询问阶段任意参与者返回不能执行操作的结果,则协调者向参与者发送中止请求,这里的逻辑与两阶段提交协议的准备阶段是相似的。
3)提交阶段
如果每个参与者在准备阶段返回准备成功,也就是说预留资源和执行操作成功,则协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源:如果任何参与者返回准备失败,也就是说预留资源或者执行操作失败,则协调者向参与者发起中止指令,参与者取消已经变更的事务,执行 undo 日志,释放锁定的资源,这里的逻辑与两阶段提交协议的提交阶段一致。
示意图
三阶段与两阶段协议主要有两个不同点
增加了询问阶段,此阶段可确保尽可能早地发现无法执行操作而需要终止的行为,但它并不能发现所有这种行为,只是减少这种情况的发生。
在准备阶段后,协调者和参与者执行的任务中都增加了超时,一旦超时,则协调者和参与者都会继续提交事务,默认成功,这也是根据概率统计超时后默认为成功的正确性最大。
总结:三阶段与两阶段提交协议相比较,具有如上优点,但是一旦发生超时,系统任然会发生不一致,只不过这种情况很少见,好处是至少不会阻塞和永远锁定资源。
提示:过于复杂且任然存在问题,因此鲜有人用
3.TCC协议
将任务拆分成三个步骤
Try
正常流程会先执行Try。
Confirm
Try执行没有问题,再执行Confirm
Cancel
如果Try或Confirm执行中出现了问题,则执行操作的逆操作Cancel来取消之前的操作
示意图
总结:任然属于两阶段提交协议一种,任为解决极端情况下会出现不一致和脑裂问题。然而,可以通过自动化补偿手段修复,将需要人工处理的不一致情况降到最少。
2.3.3 保证最终一致性的模式
提示:由于两阶段和三阶段存在问题,而TCC协议又很臃肿一般不太会使用,实现最终一致性有些非常有效、简单的模式,下面就介绍这些模式及其应用场景
1.查询模式
任何服务都需要提供一个查询接口,对外部输出操作执行状态。服务操作的使用方可以通过查询接口得知服务操作执行状态
为了能够实现查询,每个服务操作都需要有唯一的流水号
单笔查询操作必须提供,也鼓励使用单笔查询
如果使用了批量查询,则需要有合理的分页机制,且限制分页大小,以及对批量查询的吞吐量有容量评估、熔断、隔离和限流等措施
为了能够实现查询,每个服务操作都需要有唯一的流水号标识,也可使用此次服务操作对应的资源 ID 来标识,例如:请求流水号、订单号等。(重要)
应用场景:2-5案例
2.补偿模式
解释说明
当整个操作都处于不正常的状态,则需要修正操作中有问题的子操作,这可能需要重新执行未完成的子操作,或者取消已经完成的子操作,通过修复整个分布式系统达到一致,为了让系统最终达到一致状态而做的努力都叫补偿。
补偿操作根据发起形式分为以下几种
自动恢复
程序根据发生不一致的环境,通过继续进行未完成的操作,或者回滚已经完成的操作,来自动达到一致状态。
通知运营
如果程序无法自动恢复,并且设计时考虑到了不一致的场景,则可以提供运营功能,通过运营手工进行补偿
技术运营
如果很不巧,系统无法自动恢复,又没有运营功能,那么必须通过技术手段解决,这是最糟糕的一种场景,也是我们在生产中尽量避免的场景
3.异步确保模式
解释说明
此模式是补偿模式的一个典型案例,经常应用到使用方对相应时间要求不太高的场景中,把这类操作从主流程序中摘除,通过异步的方式进行处理,处理后把结果通过通知系统方式通知给使用方。此方案的最大好处是能够对高并发流量进行消峰。
操作步骤
1.将要执行的异步操作封装后持久入库
2.通过定时捞取未完成的任务进行补偿操作来实现异步确保模式
3.只要定时系统足够健壮,则任何任务最终都会被成功执行。
示意图
4.定期校对模式
解释说明
此模式目的是为了进一步确保系统间的最终一致性,在事后异步地批量校对操作状态,如果发现不一致的操作,进行补偿。
实现定期校对的一个关键就是分布式系统中需要有一个自始至终唯一的ID
持久型:通过数据库生成
时间型:机器号+业务号+时间+单节点自增ID组成
示意图
这里还涉及到关于调用链的一些知识,调用链知识看第五章
示意图
5.可靠消息模式
解释说明
在分布式系统中,当我们使用消息驱动模式(异步方式执行,也就是前面提到的异步确保模型),对于消息队列,我们需要建立特殊的设施来保证可靠的消息发送及处理机制的幂等性。
1)消息的可靠发送
第一种实现(异步调用同库)
步骤1:业务模块持久消息到本地库标记待发送(注意,因为是异步调用且同库,因此不用关心返回)
步骤2:消息管理模块接收到通知后向消息队列订阅发送消息功能,消息队列将消息发送成功后持久消息到本地库标记为成功
步骤3:消息管理模块由于异常导致向消息列表订阅失败,因此使用定时任务定时从数据库捞取在一定时间内未发送的消息进行订阅(需要消息队列自行完成幂等性,保证不重复订阅)
步骤4:消息队列消费订阅的发送消息功能,发送成功,持久消息到本地库,标记成功
注意:如果消息队列消费订阅的功能中调用第三方服务,需要先通过查询模式查询是否发送,如果发送就不再发送,没有就继续发送,最后按照步骤4进行
示意图
第二种实现(异步调用独立库)
步骤1:业务模块持久消息到本地库标记待发送(注意,因为是异步调用,因此不用关心返回)
步骤2:定时器从本地库捞取一定时间内待确认的消息同步调用消息管理器查询消息是否发送成功,如果成功,就标记成功,如果没有就再次通知其发送,如果待发送,就不变,等待下次确认
步骤3:消息管理模块接收到通知后持久消息到本地库,标记待发送,并向消息队列订阅发送消息功能。
步骤4:业务模块定时从本地库中捞取待通知的消息通知消息管理模块,成功后持久消息待发送。
步骤5:消息队列消费订阅的发消息功能,如果成功,持久消息到本地库,标记为成功,并回调给业务模块(提示:如果业务模块自己查询确认,则不用回调)
注意:如果消息队列消费订阅的功能中调用第三方服务,需要先通过查询模式查询是否发送,如果发送就不再发送,没有就继续发送,最后按照步骤5进行
示意图(此图不是很准确)
2)消息处理器的幂等性
要保证可靠地发送消息,要保重消息发送出去,且不重复,处理重复最佳方案是幂等性,公式f(f(x))=f(x),常用方式如:
使用数据库表的唯一进行滤重,拒绝重复请求
使用分布式表对请求进行滤重
使用状态流转的方向性来滤重,通常使用数据库的行级锁来实现。
根据业务的特点,操作本身就是幂等性,如:删除、增加、获得一个资源。
这里还有种,等服务集群的时候,难保证在存入数据前的一刻,查询不到,导致再次插入,导致重复,因此可以添加唯一索引来解决。
6.缓存一致性模式
大规模,高并发常见核心需求亿万级的读需求,需要缓冲机制,为了保证一致性的最佳实践
如果性能要求不是非常高,则尽量使用分布式缓存,而不要使用本地缓存
写缓存时数据一定要完整,如果缓存数据的一部分有效,另一部分无效,则宁可在需要时回源数据库,也不要把部分数据存入缓存中
使用缓存牺牲了一致性,为了提高性能,数据库与缓存只需要保持弱一致性,而不需要强一致性,否则违背了使用缓存的初衷
读的顺序是先读缓存,后读数据库,写的顺序是先写数据库,后写缓存。
0 条评论
下一页