《面向生产环境的SOA系统设计》PPT解读

很长一段时间一直在考虑支付系统的可靠性、一致性问题,生产上随着交易量的增大或是其他程序原因发生过很多问题,例如:

  1. 服务器JVM crash导致客户流水状态不一致、批量自动任务未执行完成
  2. 数据库处理阻塞导致记录/更新流水失败,导致数据状态不一致
  3. 应用服务器阻塞导致交易处理缓慢,客户迟迟查不到结果或是查到的结果不正确
  4. 前端组件阻塞导致客户交易发到应用系统时已超时,客户查无流水但最终交易成功
  5. 由于后端核心系统问题流控或其他异常导致客户交易异常失败

这些问题都不太好解决,但又是作为支付系统必须面对的,将这些问题留给客户处理实在是不可取。最近在网上发现一个PPT《准备好发射了吗?–面向生产环境的SOA系统设计》,看了很有启发,和大家分享一下。这个PPT的作者是程立,支付宝的首席架构师,这里我就不多做介绍了,大家可以在网上搜一下。

下面逐个对其中介绍的内容进行介绍。有写的不对的地方欢迎指正。

1. 典型的SOA应用

典型的SOA应用可分为几层:

  1. 接入层:界面展现服务、客户接入服务(集成服务),主要负责渠道接入功能;
  2. 产品服务层:包括各个产品应用,比如收付款、代收付、理财等产品应用,主要完成产品的业务处理,一般作为主项目对整个产品负责。
  3. 公共服务:提供基础的公共服务如客户信息、机构员工信息、收费处理、安全检查等等,这些一般不会直接作为产品,但通过这些划分可以大大简化产品开发,提高一致的企业级要求。
  4. 基础服务:这些是多个公共服务中的公共功能,和上面的公共服务的区别比较模糊。
  5. 集成服务:这里指的是调用外部系统的网关,对于支付宝来说一般是负责接入各家银行系统,从设计上集成服务一般仅负责接口适配、路由转发(在目前我们系统中是这样的)。

2. 服务的内部

服务作为基础业务单元,本身具有高内聚的特性。它需要保证对外提供的服务具有如下指标:质量约束(包括功能、非功能指标)、服务位置、功能描述(包括功能描述,还包括其他特性描述,如是否满足幂等性、可查性、TCC等等)、交互模式(包括同步联机交易、异步消息等)、通信协议、消息格式。服务对外表现为独立的业务单元,不仅要考虑自身的功能还需要在设计时充分考虑调用方的需求、业务场景以及业务扩展,另外尽量简化调用方的使用。

3. SOA技术基础设置

这些基础设施是为各个产品服务、基础服务来使用的,从后面的PPT可以看出是根据各个服务对功能、性能以及监控的要求而独立出来的。大家从各个服务角度来看待这些基础设施会好理解很多。(再想想,我们系统现在就是缺少这些基础设施,|-_- )

4. 一个典型的电子支付应用

这里拿一个典型的电子支付做例子,电子支付本身很复杂,涉及到很多的业务规则,也涉及到很多其他的服务。这里也可以看出,所有的汇聚点是在“订单处理”,也就是说订单处理需要负责整个全流程的控制、处理,它也就是我上面说的产品服务。

5. 响应时间分析

这里可以看出整个处理时间依赖与各个服务的处理时间,这也可以看出SOA并不能减少交易时延,甚至还会增加时延。这里说的合理评估务必要通过技术手段来统计各个服务的响应时间,这也就是公共基础设施“服务监控”的作用。

6. 响应时间优化

除了交易自身处理逻辑本身的优化外,另两个常用的(有效的)优化措施包括:(1)改为异步调用;(2)通过future并发调用来减少时延,当然并发调用本身也会带来额外的时延(文中为10ms)。(3)通过缓存服务结果来提高服务本身的响应时间。这个会后面文里提到的。

7. 关于性能的基础设施支持

上面优化方案都需要基础设置的支持,这些基础设置包括企业服务总线、服务监控、服务代理等等。有了这些服务支持才能使性能优化过程大大简化,更易于实现,也更科学。

8. 吞吐量分析

每个新业务上线会对底层服务带来大量的吞吐量。如果超过了底层服务的吞吐量就需要对新业务能支持的吞吐量进行调整。 注:这页PPT可以通过放映模式观看,会更易懂。

9. 关键服务的吞吐量优化

每个服务都需要给出自己的吞吐量公式,指出其优化的方向。对于无状态的服务只要增加服务器即可,但对于有状态的服务(例如记账、结算)就没那么简单了,可能需要拆库、拆表设计改造,另外也要保证服务事务的一致性。从后面程立对支付宝的重构的讲解中可以看出他对高可用、水平扩展很重视,也就是说这些关键服务必须要保证其水平扩展,这个决定是需要强大的技术支持、也需要魄力的。

10. 非关键服务的吞吐量优化

非关键服务可以通过optional在超量时进行短路降级,以保证整体系统的可用性。这也是需要基础设施支持的。

11. 资源使用分析

文中指出通过sql执行次数来进进行分析,这个数据也是依赖于服务监控的。可以看出性能要通过监控数据优化,通过监控能迅速找到性能瓶颈,减少分析成本。监控的常见手段包括日志分析,交易链路分析,服务器性能指标分析等。

12. 资源使用优化

文中指出了如何通过服务代理缓存服务结果,以减少对资源的直接访问。其实通过缓存手段对于服务本身进行改造也是一个手段,这样也更加可控。缓存的key值选取需要根据应用而已,且务必保证结果正确性。

13. 关于容量的基础设施支持

文中介绍了几个基础设施,如果没有这些基础设施或是基础设施不易用、功能不完善,会导致各个产品功能优化开发很难开展,问题排查也很痛苦。

14. 单个服务的故障条件

单个服务的故障条件有很多种,对于资源(主要是数据库,也可能是文件系统)来说,包括资源不可用、资源响应超时;对于外部服务来说,存在通信中断、服务不可用、服务响应超时、服务违背功能契约(外部服务自身问题);对于服务使用者来说会有并发请求、重复请求、超量请求、请求积压;对于服务自身来说会有BUG、处理中断、处理超时等。正如PPT中所说,唯一确定的是不确定。如何在这些不确定中保证系统的正确性是一个值得探讨的问题。

15. 应对方式

虽然这些问题多种多样,但总有应对之道。ppt中指出了应对的思路,但也指出需要从下面几个方面进行控制:避免发生、降低概率、控制影响、快速恢复。

故障条件 应对方式 类型
超量请求 配额控制 避免发生
重复请求 幂等控制 控制影响
并发请求 并发控制 降低概率或控制影响
请求积压 请求丢弃 控制影响
服务/资源响应超时 时间控制 控制影响
可恢复通信故障 合理重试 控制影响、快速恢复
处理中断 事务/分布事务 控制影响、快速恢复
BUG 自检 降低概率

16. 局部配额控制

通过令牌控制超量请求,在服务发起之前申请令牌,服务完成后归还令牌。当然为了防止令牌未归还的情况,令牌需要设置超时时间。此令牌服务也是一种基础设置。

17. 幂等服务、幂等控制

这里说的幂等服务包括两类,一类是服务本身支持幂等,另一类是通过唯一ID来进行判重,以便保证不会重发。这种幂等控制一般采用数据库的唯一索引来做,这样可以做到严格的唯一性控制且性能很好。例如在我们系统中设计了判重辅助表,通过插入操作判断是否为重复交易。PPT中指出通过“操作日志服务”来进行唯一性判断,具体的实现方式文中没说,应该是类似的数据库的实现。

18. 基于资源的并发

文中详细介绍了悲观和乐观的方案,两者的区别是事务的长短,会影响系统的伸缩性。两者的另一个不同是悲观方案可以保证在并发不大时对客户不报错,乐观锁如果要做到这一点需要做更多的工作例如应用级的重试。在系统设计时需要充分考虑利弊,尽量做到在系统层面支持并发。另外这两种方案都不适用于热点资源,针对热点资源的并发是很复杂的问题,例如春运火车票,很大程度需要根据业务情况进行设计解决。文中还提到了通过分布式锁的方式,其本质是将悲观或是乐观方案中的锁采用单独的服务来提供,以达到控制并发的目的。

19. 请求丢弃、时间控制

交易请求中需包括请求发出时间+客户端超时设置(或是约定好的固定超时时间),业务活动能否继续取决于整体的时延,在系统的关键位置(例如外呼、预计和更新流水信息等)中需要提供对交易是否超时的判断,如超时将直接报错。这样可以避免我最开始列的交易阻塞的问题。这里我们采用了时间,而时间在分布式系统中是不一致的,需要考虑时间的补偿(一般采用直接扣减差异的方式)。

注:前面提到的请求发起时间的设置一般是在前置机或是接入系统中进行设置,如果采用客户的时间会带来太大偏差,而使用整个系统内部的时间误差是可控的。另外在考虑超时时间时还需要考虑http的超时时间,例如客户端超时时间是30s,但接入系统的超时时间不能是30s,应该减去接入系统http的超时时间例如15s,则整个交易的超时时间保守设置应该是15s。此请求时间和超时时间应保证做到全交易线传递。

20. 领域自检

领域自检即对数据库资源内容进行简单的核验,保证其数据的正确性,自检的时机为领域对象读取后、更新前。采用这种方式可以一定程度上避免程序的bug。自检的内容包括不变式如取值范围等的校验、状态变迁,即状态变化可选范围的控制。

21. 分布式事务(TCC模式)

通过事务可以避免程序处理中断(如jvm crash等)导致的数据库状态不一致。例如两次资金记账操作,为了保证一致性,可以采用此模式。此TCC模式说起来简单,但实现起来可不简单,其本质相当于使用应用服务来作为数据库的资源管理器的角色,例如负责实现隔离性、原子性,通过统一的调度来实现最终一致性。

22. 分布事务(补偿模式)

这种事务的难点是如何实现补偿,以及在无法实现补偿时如何处理(或是通过业务处理)。这种事务不提供隔离性支持。

23. 总结

可以看出企业级的SOA设计不是单独一个项目组这么简单,很多东西需要是企业级,需要大量的基础设施的支持。目前比较火热的微服务、DevOps不仅仅是一个概念,它们体现了一种架构思想,也体现了一种企业文化,甚至也体现了一种企业组织架构。这个PPT已经发布很久,但现在来看还是很有借鉴意义,相信大家在阅读时结合自己的经历会更有体会。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》

应用系统无事务时如何保证交易一致性

在进行应用系统开发时一笔交易往往需要多次操作数据库,为了保证交易的一致性、正确性,简单的方式是通过数据库事务来控制,多次数据库操作要么均成功要么均失败。但有时我们不能将多次数据库操作封装在一个事务中,主要有两方面原因:1.多次数据库操作中间需要调用外部服务,而这个外部服务比较耗时,此时如果统一封装在一个事务中会长时间占据数据库连接,可能导致长时间锁定某些资源,从而严重影响系统并发度;2.随着异构系统越来越流行,一个系统可能同时有多个数据库,或是同时有关系数据库、Nosql数据库、分布式缓存、消息队列等,如何保证这些系统之间的一致性,可以使用“两阶段提交”或PAXOS等分布式协议,这样的话会显著增加系统的延时,降低系统的吞吐量,另外当有异常时处理也很复杂,会有超时、死锁等问题。

其实对于应用系统来说,如果不是要求“实时一致性”,只是要求“最终一致性”,是可以通过系统设计做到不使用两阶段协议也可以保证一致性的(参考Idempotency Patterns)。下面先表述几个概念:

  • 一致性:其实就是同一系统或多个系统对于某个数据状态的一致意见,这也包括从外部调用者来看状态是一致的。在数据库系统中只要记录了提交日志,事务就相当于成功了。这里日志就相当于合同契约,所以日志必须持久化,否则就没有意义了。在分布式系统协议PAXOS中其实多个系统最终也是得到了一份契约,所以PAXOS也可以作为类似两阶段协议这样使用。
  • 最终一致性:系统在运行中或发生故障时处于某个“临时状态”,经过一段时间后会重新进入到一个“一致状态”。注意这里的“临时状态”并不是错误状态,只是说它是一个中间状态,这个中间状态也可以是正确的,只是不是最终状态而已。如果能做到这个中间状态对于客户是透明的就更完美了(数据库系统就做到了)。这里不提什么“弱一致性”,弱一致性其实就是不保证一致性,这只是业务对于系统设计问题的容忍,如果你是做账务系统,就不要考虑弱一致性了。

其实很多系统仅要求“最终一致性”即可,如果系统可以做到正常情况下完全一致,异常的情况下一段时间(如5分钟内)做到交易结果一致,客户应该都没什么问题。时间也是我们的朋友,虽然这个朋友在多个机器上不完全一致,但是误差都很小,在系统设计时可以加以利用。

下面主要介绍在不使用事务的情况下如何保证系统正确性,对于如何使用“两阶段提交”(如JAVA XA规范的分布式事务)可以参考其他文章(推荐这篇)。我们这里主要依靠下面几个工具:

  1. 状态:当前交易所处的状态,状态要尽量少、明确,要以事务边界为粒度。状态应避免循环,尽量采用顺序前进的方式。对于需要重复或是循环时可以设置子状态,以保证业务逻辑清晰。
  2. 自动任务:对于中间状态的记录进行处理,使其恢复正常状态。此任务应该考虑时间,防止过早调起,将正常交易作为中间交易进行处理。
  3. 时间:要记录数据的最后更新时间,通过时间确定记录是真的异常还是正常处理中。不同的系统的时间间隔应该不同,具体根据业务需要和技术实现来定。
  4. 可测试性/幂等性(重发):可以参考我的另一个文章《系统重发设计》。

系统内部设计

下面先介绍单个系统内的处理,假设一个系统涉及如下处理逻辑:

  1. 系统收到交易请求,进行交易输入合法性检查,如检查失败直接报错。
  2. 业务合法性检查,如合法性检查失败则记录交易失败信息。
  3. 调用外系统交易进行信息登记。(可能会有异常)
  4. 系统内部进行限额扣减。
  5. 调用外部系统进行转账处理。
  6. 记录转账结果
  7. 如转账失败则回冲限额(这里为简单起见,假设前面的信息登记动作由其他自动任务保证一致性,否则此处需增加外呼冲正)
  8. 返回交易结果。

针对上面的场景应该如何设计系统呢,最简单的设计是把所有这些全部放在一个事务内,由事务保证一致性,这样的话事务很长,如果遇到外呼超时会严重影响系统性能。如果采用状态来控制应该采用下面步骤:

  1. 定义状态,根据前面的状态规范,尽量少、明确,以外部系统为边界。可以看出可以分为如下状态:1-待登记;2-处理中(登记完成,限额扣减完成,待转账);3-交易成功;4-交易失败。
  2. 确定合适的记录数据库时机,延迟保存原则,只在状态需要变更时保存流水,且预计流水(参考数据库的先记日志原则)。系统内尽量通过事务保证一致性。
  3. 定义自动任务,对各种中间状态(此处为1-待登记;2-处理中)进行处理,通过重发或是查询外系统的结果来确定后续做法。需要和对方系统约定处理方式。

下面展示下设计后的系统处理模式:

  1. 系统收到交易请求,进行交易输入合法性检查,如检查失败直接报错。(无DB操作)
  2. 业务合法性检查,如合法性检查失败则记录交易失败信息。(若失败记录4-交易失败)
  3. 调用外系统交易进行信息登记。(可能会有异常)(外呼前记录1-待登记) (外呼后若失败,则记录4-交易失败)
  4. 系统内部进行限额扣减。(扣减限额与更新流水放在一个事务中,状态改为2-处理中)
  5. 调用外部系统进行转账处理。 (外呼后若失败,则记录4-交易失败)
  6. 记录转账结果(若5成功则更新状态为3-交易成功)
  7. 如转账失败则回冲限额 (若5失败,则记录4-交易失败,同时回冲限额,放在一个事务中)
  8. 返回交易结果。

对于上面的状态如果想保存中间结果,例如登记成功的结果、外呼失败的信息,可以通过子状态来做,或是增加状态。但这样会增加自动任务处理的复杂度,需要做更多的判断,好处是可以一定程度减少外呼查询或其他动作。如果系统内部的多数据库操作不想使用事务封装或是无法封装(可能涉及多个模块),也可以通过状态区分开各个阶段。

系统间设计规范

多个系统内一致性控制需要采用分段控制。即每个系统都要保证自身的一致性、正确性。调用方系统保证消息被正确传递,并保证最终更新交易结果;被调用方保证自身的一致性,实现自身状态可测试性或是幂等性。系统之间的消息,采用唯一标识区分,被调用系统提供查询消息结果接口,或支持幂等性(可重发)。此唯一标识由调用方生成,调用方系统在调用外呼系统前记录此唯一标识,被调用方通过此唯一标识实现交易幂等性。如果被调用方支持多个调用方,可以通过调用方编号+唯一标识来作为唯一标识,或是指定统一的标识生成规则,由各方遵守。

如果存在多个系统串联的情况,可以通过这种方式来实现正确性传递。注意这种传递只能是从前->后,而不能由后面的步骤保证正确性,因为后面方法可能会没收到消息。

本文所述方法可以从系统设计层面保证交易的正确性,可简化记为:状态 + 自动任务 + 时间 + 可测性/幂等性 = 最终一致性。当然了这种方法也有弊端,1.应用设计需要考虑各种状态转移,系统在记录状态同时,需要记录时间、唯一标识等额外信息,增加系统复杂度。2.自动任务需要详细设计,通过时间保证和联机交易不同时执行,另外需通过乐观锁保证状态仅被自动任务更新(建议update时进行原状态对比)。3.后一步结果严格依赖前面的执行结果,只能采用串行模式。4.需保证中间状态对于客户是无害的,包括查询结果和实际业务效果(这一点有时很难做到)。

本文为我个人的一点经验总结,希望能对大家设计系统有帮助,大家有更好方式也欢迎指出。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》

系统重发设计

重发又称为幂等性(Idempotency,即f(x) = f(f(x))),是指重复发送后会得到和和首次发送相同的结果,且不会导致不良的结果。重发是一个很重要的系统特性,数据库系统的事务设计、系统异常的处理都依赖对重发的支持。重发说起来简单,但做起来可不简单,有些交易本身天然支持重发,例如将所有记录状态改为正常,重复做一次并没有什么问题。但有些交易支持重发则很困难,例如向A转账10元,如果重做一次就可能会重复入账。

对于不是天然支持重发的交易,如果要支持可重发,需要做一些特殊处理。简单的方法是通过唯一标识来判重,具体步骤如下:

  1. 发送方生成消息的唯一标识,在发送消息中附上此标识;
  2. 接收系统收到消息在处理时,在完成消息处理的同时进行唯一标识的判重。例如记录流水,在流水中包括此标识,并对此标识通过唯一索引来判重;
  3. 对于消息重复的情况,给客户端反馈特定的错误码、错误信息,接收方以此得知消息已重复。交易结果建议反馈“处理中”,以免客户认为交易失败。

如果流水信息中不便于添加此标识,可以建立一个辅助表,用于存储此标识,通过唯一索引判重。记录辅助表和更新其他信息尽量放在一个事务中以免双方记录不一致。由于判重一般只要保证一段时间即可,故此辅助表的数据不必保留太久。

此处记录辅助表和记录流水放在一个事务中是为了保证一致性,如果不采用事务保证,建议先记录判重辅助表,这时可能会出现记录了辅助表,但未记录流水的情况(可能记录流水时出错,或是记录流水前出错),这时如果再有相同消息进来仍然会报错。这种程度的误报你需要分析业务上是否有问题,这种情况下你的系统响应是否正确。不建议在记录流水后再记录判重表,这时数据库中的流水记录无法回滚,这时客户来查消息结果会得到两条流水,很难区分哪个是正确的结果。

对于系统交易判重不建议采用查询方式,即在记录流水前查询一下流水是否存在,因为会有并发问题,之前生产上也遇到过,当两笔交易几乎同时发送时就会出现判重规则失效,记录重复流水的问题。需要指出,这并不是小概率事件,而是设计缺陷。

系统正确性的保证依赖与系统两方面的能力支持:1.可测试性,即系统支持调用方查询交易结果。2.可重发,且不会因为重复执行造成不良影响,即幂等性。系统只要支持其中一种就行,如果一种都不支持,永远无法保证操作正确性。其实很多实操做就是如此,例如你需要向水中扔1个石头,如果你正巧扔的时候精神溜号了,等你回过神发现水面很平,你不知道是否扔进去了,你现在再扔1个可能多扔了,如果不扔可能前面1个没扔进去。对于这种实操作,必须在执行前深重考虑,确保精神不要溜号,哈哈。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》

账务交易一致性设计概述

对于账务交易最重要的就是交易一致性,即给客户反馈正确的一致的结果。交易一致性并不是一个简单的话题,说来话长了。下面我结合项目的一些经验,谈一下我认为重要的地方。

交易本身大体分为三类,联机交易、批量交易、批量联机交易(即联机实时返回多笔交易的结果)。下面分别对于不同的场景进行说明:

1 联机交易

1.1 接口设计

从接口设计角度,对于联机交易一般包括两个接口,账务发起接口、交易结果查询接口。其中联机交易接口用于发起实时交易,并实时返回结果。交易查询交易则用于对账使用,用于确认账务成功与否。

账务发起接口:

此接口输入至少应包括如下字段:客户标识、客户交易流水号(用于客户对账使用)、其他账务相关的要素包括付方账号、收方账号、金额等(具体根据业务需要来定)。

输出至少应包括如下字段:交易结果代码、错误码、错误信息、系统交易流水号(可选)、交易日期(可选)。交易结果代码包括成功、失败、不确定三种状态,其中当交易超时等异常时反馈不确定状态,由客户调用“交易结果查询”接口进行对账。另外两个可选字段系统交易流水号用于表示本系统内部的交易流水号,方便排查问题;交易日期表示交易发生的记账日期,以便客户知道这笔账属于哪日的交易,如失败“交易日期”可以为空。接口务必保证交易结果代码的正确性。

交易结果查询接口:

此接口仅用于账务交易反馈不确定或超时时对账使用,对于需要列表查询的需求建议另外使用单独接口来做(例如支持分页,支持模糊查询等),这主要是出于性能、可维护性如流控等方便的考虑。对于交易结果查询接口输入字段应包括:客户交易流水号、系统交易流水号(两者二选一输入)、客户标识,输出字段除了包括账务发起接口的交易结果输出字段外,还应该包括查询结果代码、查询错误码、查询错误信息,用于反馈这笔查询交易是否查询成功,是否应该后续继续查询。对于同时包括报文头、报文体的交易,查询结果代码等可以放在报文头中,而交易本身结果可以放在报文体中。

1.2 其他说明

上面提到的客户交易流水号由客户生成并上传,生成规则应保证同一个客户内部全局唯一,以便用于对账。注意不要采用一天内唯一的概念,由于时间并不是一个双方都认可的值,特别是涉及到日切时。可以采用一周内、一个月内唯一的概念,这需要在接口中提前约定。此客户交易流水号涉及到判重的问题,系统应保证交易唯一性,建议通过数据库唯一索引来做(具体实施方式将在后续文章介绍)。

系统交易流水号由我方系统生成,应保持全局唯一性,此流水号建议包含日期、时间信息,以便排查问题。

交易结果代码:由于此代码十分重要,程序务必保证其正确性。应用应将此字段放在应用能够完全控制的地方,例如对于报文包括报文头、报文体两部分,而报文头由框架负责填充,此时建议将此字段连同错误码、错误信息一起放在报文体中,对于报文头中的错误和客户约定为”处理中”状态,这是为了避免由于框架原因而导致原本正确的交易返回失败。此规则适用于账务发起接口及交易结果查询接口。

如需提供交易结果列表查询功能,应发布单独接口。此接口应支持分页查询、或是查询下一页功能(可通过在输入中增加上次查询最大系统流水号字段实现),支持按账号、按金额、按日期等进行筛选。注意此处日期包括:客户提交日期、系统营业日期、记账日期,这些日期概念是不同的,由于此接口仅用于查询不用于对账,如果业务允许可以仅支持一种,建议使用客户提交日期。

1.3 交易超时设置

交易在每个外呼中均应该设置超时时间,超时时间应该满足“漏斗原则”,防止在后端未处理完成时前端已经失败返回。漏斗原则是指从客户前端->中间转发服务器->应用服务器->账务核心服务器,各个节点的超时时间应该以此减少。对于特殊情况不能满足漏斗原则的情况,应该由应用服务器在程序逻辑上保证交易一致性,例如返回处理中并提供异步对账功能,或是提供冲正功能。若采用冲正方式,违反漏斗原则会造成很多交易结果不确定,交易应该在设计阶段考虑各种情况下超时如何保证交易一致性(冲正比较复杂,在此不做详细介绍)。

交易超时还包括一点:避免异常情况下查询交易早于账务发起交易到达。这一方面可以通过控制查询交易和账务发起交易的间隔来保证,例如约定超时时的查询间隔30s,但即使这样在特殊场景也会有问题。为了防止此问题可以在交易报文中设置交易发起时间、交易剩余处理时间,这两个字段在进入我系统后由前端系统生成并赋值(不能由客户填充),如客户通过外围系统接入此字段由外围系统填充。交易剩余时间一般设置为30s,在消息传递的各个环节应该对剩余时间进行重写(包括中间系统、应用系统等),如剩余时间为0,则不应该进一步传递消息,应直接返回失败。对于应用系统(即需要保证交易正确性的系统)在记录流水前也应该判断此剩余时间,如为0则应直接返回失败而不记录流水。

应用设计者应了解每只账务交易的一致性保证机制,从设计上保证一致性。对于账务交易尽量不要支持重发,重发很容易导致交易重复提交,且不容易识别查询返回结果,除非服务方和调用方系统进行周密的设计不建议采用。

2 批量交易

2.1 接口设计

批量交易是指客户通过上传批量文件的方式发起的账务交易,这种交易一般为异步处理。一般包括批量文件上传交易、批量结果查询交易。

批量文件上传交易

批量文件上传交易主要完成客户文件上传功能,系统如接收文件成功应返回客户文件接收成功。接口输入报文应包括客户标识、客户批次流水号(用于查询批量整批结果)、批量文件路径及名称、批量总笔数、批量总金额。输出字段和联机单笔交易类似,至少应包括如下字段:批量交易结果代码、错误码、错误信息、系统批次号(可选)、交易日期(可选)。

接口的其他注意事项包括交易结果代码、唯一性约束和联机接口一致。除此之外还有以下几点需要注意:

  1. 客户应保证批量文件早于联机报文到达,以便根据报文解析文件时文件已经发送完毕;
  2. 对于批量文件名称应保证唯一性,可以对于每个客户使用单独的目录。这一点需要和客户提前约定;
  3. 文件应使用特定分隔符分隔,需要保证分隔符和正常业务字段不重复,例如 @ # 等;
  4. 文件内每笔交易应包含客户交易流水号或批内子序号,用来查询结果时标识每笔明细;
  5. 系统对于批量的联机处理,至少应包括记录批量流水记录;对于后续处理如文件解析、校验,记录批量明细信息、业务逻辑校验,业务处理可以根据业务、性能需要考虑是否包括在联机处理中。
  6. 系统联机处理时应对每批文件的笔数进行控制,具体可以根据业务需要来,可以为200笔或2000笔。

批量结果查询交易

客户通过此交易可以查询批量处理的结果,包括两部分:一是批量整批的结果,一是每笔批量明细的结果。为了处理简单,可以当批量整批处理完成后再返回批量明细的结果,如果确实需要实时返回批量明细的结果,需要在设计时保证返回结果的正确性,包括明细的完整性、正确性(如批量太大不建议采用这种方式)。

输入接口应包括: 客户标识、客户批次流水号、系统批次号(可选);

输出接口应包括:1.整批信息:批量交易结果代码、错误码、错误信息、系统批量流水号(可选)、交易日期(可选);2.明细信息:客户交易流水号/批内子序号、明细交易结果代码、错误码、错误信息、系统批量子序号(可选)、交易日期(可选)。明细信息可以根据接口定义是否返回,如只有整批处理完再返回结果,可以定义为:当整批信息不为整批失败、处理中时会返回交易明细结果,当整批结果为交易成功时应返回明细结果。

这里需注意以下几点:

  1. 上述输出接口字段建议放在报文体中,通过报文头的系统交易结果、错误码、错误信息来表示查询交易本身的结果,如交易超时等;
  2. 如系统支持的批量文件较大时,需要通过文件来返回交易结果,返回报文中没有明细信息,但应该有明细结果文件路径及名称。文件名可以和客户输入文件名加后缀方式来对应。结果文件建议作为批处理的最后一步给客户提前生成好(建议在修改整批状态之前,以免客户查不到文件),并和客户约定保存多长时间。
  3. 为防止批量状态卡死,系统应该有专门的检测自动任务来检查异常的批量状态,并针对错误进行相应的处理(包括转人工处理)。
  4. 批量结果查询交易应和批量历史流水列表查询分开,以保证系统性能和可维护性。

2.2 其他说明

批量交易的其他注意事项,如判重、交易结果、超时,可参考联机单笔交易中的说明。在设计批量交易时应将整批作为一个整体考虑,结合系统非功能要求确定应支持的功能点,切记为了所谓业务需求,盲目实现不必要的功能。

3 批量联机交易

这类交易相当于系统提供的单笔交易的批量接口,但整批的结果要求同步返回(如不同步返回可以参考上面的批量交易)。这类交易比较特殊,为了保证交易一致性,需要将这多笔交易当做一笔来看待,作为整体记录流水,而不是简单的循环调多笔。批量联机交易一般不要直接发布给直连客户,可以作为界面或是内部调用的性能优化手段,如果没有性能问题应尽量采用循环单笔的方式。

输入接口应包含:客户标识、客户交易流水号[支持列表]、其他单笔交易的业务字段[支持列表]。输出接口应包含每笔交易的结果信息列表。对账接口应支持按照客户交易流水号列表来查询结果。

在交易设计时需要考虑如下几点:

  1. 对交易支持的最大笔数进行限制,这个限制的建议值为超时时间内能处理的交易笔数(包括启动多个并发、不启动并发两种情况),最大值为系统可以保证交易一致性的最大笔数。
  2. 交易处理方式可以考虑以下几种:a.将多笔交易作为一个整体提前记录流水信息,然后逐笔处理[建议];b.直接调用逐笔交易当做单笔处理,如已超时未处理记录直接返回失败,不做处理[可以满足一致性要求,但不建议];c.直接当做单笔处理,超时仅返回处理中,由判重功能来保证不会有账务问题。[不建议,客户无法知道应该等多久查结果、再次发送]
  3. 查询结果交易需要保证在交易处理的任何时点的结果都是正确的,不能仅查到部分交易的结果,故应该将多笔当做一个整体来预计流水。
  4. 多笔交易一般是有关联的,建议对中间数据进行交易级别缓存,以提高性能。
  5. 是否要采用并发处理建议能支持客户选择,由于并行处理对于某些场景是会影响交易正确性的。(PS.有些客户是在乎顺序的)
  6. 此交易会长时间占据联机服务器的线程,会大大降低系统的吞吐量,采用并发并不会使情况缓解。针对此交易设置单独限流策略。

上面对于三类交易的接口设计以及一些注意事项进行了介绍,后面文章再对账务交易的一些设计细节进行详细描述。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》