JavaWeb开发实战(一)一致性方案设计

目录

1、引子

2、系统一致性方案

3、接口一致性方案

4、对账


以下都是个人经验总结,水平所限,难免有误,欢迎指正。

1、引子

一致性就是保持数据一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。

要保证数据一致性,就需要对可能由服务超时、高并发甚至bug引起的问题事先考虑,建立合适的一致性模型,确保和上下游保持一致,并在各个服务异常节点,做好数据记录以便进行核对和补偿。

在做一致性设计的时候,如果考虑系统的数据一致性,那么就要认为系统外部都是不可靠的;如果考虑接口的数据一致性,那么就要认为其他接口都是不可靠的,基于此才能更全面地处理各种异常。这里说的不可靠,指的是网络服务或持久化服务不可靠。

下面从整个系统的数据一致性和服务接口的数据一致性两个角度,谈一谈一致性方案。

2、系统一致性方案

从单个应用设计的角度看,一致性包括两个方面,一个是应用本身数据一致性,一个是和上下游应用的数据一致性。

应用本身数据的一致性,包括集群(如果有)中所有机器数据一致性和单机内数据一致性,由于系统中的业务数据一般放在数据库或缓存,都是与应用机器分离的,因此一般不存在集群数据一致性问题,且集群的管理一般业务系统的技术设计上不考虑。对于单机内数据一致性,分为单个服务数据一致性,以及业务数据一致性。单个服务的数据一致性,一般通过数据库事务保证,达到准实时一致;而业务数据的一致性,则需要通过对账脚本来核对,一般提供弱一致性保证。

和上下游应用的数据一致性,从应用提供服务的角度来看,每个应用保证向下一致,对上负责。也就是说,每个服务保证服务内部数据一致性以及和下游系统一致性,给上游的结果可能是业务受理、业务成功、业务失败、业务处理中或超时。那么如何保证和下游数据一致呢?有明确结果的情况没什么好说,主要是超时情况的处理,这就要分场景了:如果是实时性要求高的场景,那么超时状态做业务失败处理返回给上游,并向下游发起请求撤销(回滚);如果实时性要求不高,那么可以立刻发起重试,或状态置为处理中,异步重试,直到得到明确结果返回给上游。

图解如下:

3、接口一致性方案

从服务接口角度看,一个接口的实现如何保证一致性,首先要保证接口本身涉及数据的一致性,其次外部依赖系统与本应用的数据一致性,另外要保证并发情况下的数据一致性。实现方案最基本的就是:接口支持幂等、内部事务处理、对外进行重试和补偿。

根据业务上对接口实时性要求,可以粗略分为两种接口设计方案,一种处理实时性要求较低的接口,也就是弱实时接口,很多是可以异步处理的;一种处理实时性要求高的请求,也就是强实时接口,一般都是同步接口。下面分别介绍两种接口的实现方案。

这里对外部依赖系统和服务做个说明:

1)如果外部依赖的接口不支持幂等,那么不能直接重试,需要先做查询,再重试;

2)如果调用外部超时,那么根据具体场景,要么重试直到成功,要么做取消请求的补偿;

3.1 弱实时接口设计

弱实时接口针对的场景是调用方只关注受理结果,而不关注直接处理结果,甚至当认为消息中间件基本可靠的情况下,受理结果也可以不关注,只通过数据对账来确认服务执行成功就行了。

设计思路是用乐观锁(CAS+Version)支持幂等,在抛异常时,无论本地异常或外部异常,都抛异常中止执行,再然后异步重试。弱实时接口往往可以异步执行,因此完全可以放在消息队列,单线程执行,不需要考虑并发问题,所以用乐观锁较好。

以一个简单的调账的接口为例进行说明,接口服务处理流程图如下:

请求处理共4步:

1)落一条基础数据,数据状态为处理中,如果失败则直接抛异常。

2)调外部账,如果超时或失败则抛异常。

3)调内部账,如果超时或失败则抛异常。

4)执行本地事务,落本地业务数据,如果失败则抛异常。

做几点说明:

1)接口是幂等的,且以上4步每一步都是幂等的,如果外部接口不支持幂等,那么就查询重试。

2)由于接口幂等,请求和重试是调用同一个接口。

3)根据异常码决定是直接重试还是先处理再重试,很多场景是不需要区分超时和业务失败,可以直接重试的。

4)先调外部账再调内部账是因为三方包相对于二方包风险更高,如果外部账失败了,就没必要调内部账了。

3.2 强实时接口设计

强实时接口针对的场景是调用方需要直接拿到明确结果,无论是成功、失败,还是外部超时,这种接口往往也有RT要求。

设计思路仍然可以考虑用乐观锁(如果存在大量写操作,服务失败概率会很高,那么可以考虑用悲观锁),只是如果服务内部或依赖服务失败或超时时,保存失败信息,直接返回业务失败;根据保存的失败信息,对失败信息进行补偿。

仍以调账服务为例,流程如下:

可见强实时接口和弱实时接口的服务处理流程是一样的,区别在于:

1)如果服务失败或依赖服务失败,则直接返回业务失败,而不是异步重试。(此处有时也可以直接重试,可以解决很多由于超时导致的异常,但如果服务都超时了,那么对强实时性要求接口来说,也就算是失败了)

2)对失败的记录,要做失败补偿,这要求下游服务提供补偿接口。如调外部账成功,而调内部账失败,则需要做失败补偿,补调外部账逆向操作。

3)高并发场景下的接口设计,另外开篇叙述,方案上会复杂很多。

3.3 小结

当然强弱实时性都是相对的,实际方案需要根据场景灵活处理,比如对于强实时场景,虽然依赖的某些服务可能暂时失败,但提供最终成功的保证,也可以只调用不处理,对于调用记录另外再异步处理。

4、对账

对于数据一致性,即使接口设计很好,还是可能会出现业务数据不一致的情况,如不同的业务模块之间的关联数据对不平,不同应用之间的关联数据对不平,和外部机构由于延时处理等原因导致数据对不平等。

保证数据一致性的兜底方案就是对账,这个对账就包括了应用内部关联模块的数据核对、关联应用的数据核对、关联合作方的数据核对等,如资金流的账务核对就包括了账账核对(账务系统和会计、清算系统)、账证核对(业务系统和账务系统)、账实核对(账务系统和银行核对)、业务财务核对等。

 

猜你喜欢

转载自blog.csdn.net/ss1300460973/article/details/85700997
今日推荐