账务系统热点帐号问题

账务系统的热点帐号是一个很常见的问题,特别是随着互联网支付的发展,热点帐号的问题更加突显。很多支付机构由于业务限制往往在一个银行只有一个对公帐号,这一个帐号往往就是热点帐号。

对于不同帐号的并发处理相对比较简单,比如按客户或帐号拆表、拆库,这之间比较复杂的是分布式事务问题。目前处理分布式事务一般的解决方案包括:

  1. 可靠消息+补偿模式;
  2. TCC分布式事务模式;
  3. 两阶段提交;
  4. 拆分为多个局部事务。

这些方案往往通过自动任务来处理异常的场景从而保证最终一致性。

如果说去IOE最困难的问题应该就数热点帐号问题了(或者说分布式事务问题),因为单个帐号的扣减余额等肯定要通过事务来保护,防止出现余额和流水不符的情况。但事务必然会导致并发度下降,即使对于功能很强大的主机核心系统,并发度也会显得捉襟见肘。为了防止自己处理能力超限,主机往往会进行流控以保护自己,但这样的用户体验会很差。如果这件事在开放系统中做,由于每个服务器性能有限,并发度会更低。下面介绍下自己对热点帐号问题的理解,因我本人并不负责核心系统,有不当之处欢迎指正。大体解决方案分为如下几种:

1. 汇总记账方式

汇总记账分为两种,收单和付款。其中收单采用异步入账的方式比较好,可以将钱先放在内部过渡户中(过渡户可以是一个或多个),然后异步将钱整批汇总后入收方帐号。对于付款会复杂些,主要涉及到透支的问题,如果业务上允许透支也可以采用先银行垫付一段时间,然后整批一笔入付方帐号;如果业务不允许透支就比较复杂了,一种可行的方案是将每笔账务先预先记录流水然后返回“处理中”,后台采用批量的形式以批量为单位更新付方帐号的余额。但这样带来的一个问题是响应不够及时。

2. 异步缓冲记账

参考资料1中提到的削峰填谷方式,思路比较简单,对于不超过并发数的交易同步响应,对于超过并发的交易放在缓存队列慢慢处理,可以返回处理中或成功(返回成功有透支风险)。这种方式本质并没有提高系统的并发量,只能算是一种简单体验改进。

3. 同步批量缓冲记账

在参考资料2中提到了一种可行的解决方式,但并没有介绍如何实现,结合参考资料3,可行的解决方案如下。当接到客户的转账请求后,如果为热点帐号,则在执行完所有的校验后,将交易放在一个队列(或多个队列)中。针对每个队列会有单一的线程进行处理,它一次或获取多条待处理记录然后统一记录数据库流水,对于热点帐号的余额扣减只扣减一次。对于这种方法如何确定队列的大小以及同步等待的时间是一个需要分析的问题。这种方法可以成倍减少数据库的压力,类似与数据库事务管理器的提交机制,但它也带来一个问题就是增加了单笔交易的延时。为了使这种方法收益最大化,在框架设计层面需要把同一个热点帐号的交易转到特定的服务器来处理。

4. 建立多个影子帐号

上述“同步批量缓冲入账”方式的限制是单台应用服务器的处理上限,单台服务器的处理上限。为了解决此问题,一种可行的解决方案是将一个热点帐号拆分为多个影子帐号,每个影子帐号有自己的余额和流水信息。所有影子帐号的余额之和是整个帐号的余额。拆分后的处理逻辑和单个帐号的处理方式相同,可以采用不同的处理方式,如异步缓冲记账或是同步批量缓冲记账等。这个方案需要特殊考虑的点包括:

  1. 帐号余额增加时如何分配给其他的影子帐号:为避免分布式事务,建议采用异步方式处理。当然由于这个交易量很少,采用分布式事务也是可行的。
  2. 当单个影子帐号余额不足但整体帐号余额充足时的处理方式:可以采用“直接报错“”或是“返回处理中”改为异步处理。对于客户的特殊提取交易不应直接报错,因为报错则无法支持客户转出所有余额。对于异步处理的方案在返回处理中时可将此笔账务交易转移到其他影子帐号处理,如果所有影子帐号均余额不足但整体余额充足时,此时需要进行影子帐号间调账,将此笔交易放入特殊队列,将此影子帐号置为停用,在处理时将此影子帐号的钱转给其他影子帐号然后继续处理。直到所有的帐号均不支持则进行报错。可以看出当开始停用影子帐号时系统整体的并发度会显著降低,所以采用影子账号应让客户知晓,并提前做好客户的工作,例如帐号至少要保留特定余额、指定特殊的销户流程、与客户约定好各种异常的处理策略等等。
  3. 多个影子帐号之间需要保证负载均衡问题:另外需支持动态增加和减少影子帐号的个数。

另外提一下,内存数据库并不是一个好的解决方案,首先如何保证内存数据库和关系数据库的数据一致性是一个困难的问题;另一方面内存数据库本身也是一个单点,会存在单点问题;再一方面关系数据库本身对于热点数据也会做内存级别缓存,所以内存数据库意义并不是很大。

上面介绍了很多方案,可以看出并没有完美的方案,热点帐号方案更多的考虑是一种权衡,我们需要根据不同的情况来指定不同的策略。后续我会试着对于方案三、四进行实现,看是否有其他问题。后面如发现更好的解决方案也会更新此文章,也欢迎大家告知其他解决方案。

参考资料:

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

iBatis锁机制导致的交易阻塞

前段时间生产上出现了数据库锁太多的告警,其中等待的锁短时间内达到了6000个,好在十分钟后锁很快解除,否则可能会有数据库宕机的风险。此问题的具体表现如下:

  1. 其中一台应用服务器在凌晨2点钟出现大量交易变慢,导致weblogic线程超时阻塞,进而导致服务重启1
  2. 其他服务器在2点钟某一个账务交易出现大量长交易,但其他账务交易、查询交易均正常;
  3. 数据库某个限额统计表在2点多出现大量的行锁,触发数据库告警。
  4. 数据库在2点10分恢复正常,行锁也跟着消失

根据数据库锁的信息,结合那段时间的交易流水,可以确定是由于某个大客户的同一帐号的转账导致的。进一步梳理了代码,没有发现死锁的问题;另外这个客户日间交易量更大,理论上不应该出现这种问题。

进一步分析出问题的应用服务器怀疑是由于iBatis的锁导致的(原来生产上也出现过由于IBatis导致的锁定问题),但在什么情况下会出现长时间的行锁呢?目前iBatis有三个相关参数:maxRequests、maxSessions、maxTransactions,目前生产上分别设置的是100,60, 30。但根据iBatis的文档看maxRequests的值建议是maxTransactions的是10倍。为了验证是否是由于此参数的问题,我们在非功能环境进行了测试。

1. maxSessions=90,数据库连接数60,只做查询:

为了看出问题我们去掉了索引使查询交易耗时边长。此测试发现当查询并发小于等于60时没问题,但大于60时会出现连接不够的错误。

2. maxSessions=90,数据库连接数100,只做查询:

发现查询并发数增大(超过数据库连接)并不会导致交易失败,但查询时长会边长。这说明ibatis自身有锁机制来进行等待。

3. maxSessions=90,数据库连接数100,同时做付款与查询:

转账交易10个并发,在转账交易稳定后开始发送查询交易,查询交易85个并发。发现在启动查询交易一段时间后转账交易开始出现超时和失败,但查询交易无失败和超时,但交易时间有增加。

转账交易10个并发,在转账交易稳定后开始发送查询交易,查询交易75个并发。发现在启动查询交易一段时间后转账交易正常无失败和超时,查询交易亦无失败和超时。

这个测试我们为了看出对账务交易的影响对账务交易代码进行了修改,伪码如下:

Enquire agreementInfo and other info; (no transaction)
Start transaction #spring逻辑
	Get session  #ibatis处理逻辑
		Get Request #ibatis处理逻辑
			Do update account limit
		Return Request #ibatis处理逻辑
	Return session #ibatis处理逻辑
	Sleep 1s #增加的代码,为了便于看出效果
	Get session #ibatis处理逻辑;在这里可能出现拿不到session而阻塞,但这时其实线程是有数据库连接的,因为transaction未断开,不应该出现阻塞
	    Get Request #ibatis处理逻辑
			Insert account transaction information  #预计账务流水
		Return Request #ibatis处理逻辑
	Return session #ibatis处理逻辑
End Transaction #spring逻辑

我们可以看出由于iBatis在每次数据库操作开始都会请求session,而在每次数据库执行完成都会释放session。如果拿不到锁就会阻塞等待。这在仅仅是查询时没问题,但是在更新等事务中就会有问题,会导致数据库行锁,而且会影响到这条记录相关的所有交易。

后来我们仔细阅读了iBatis的代码,包括spring对于iBatis处理封装的逻辑,发现其实:maxRequests、maxSessions、maxTransactions这三个参数只有maxSession是有用的,因为maxRequest都是在session中进行获取,所以其取值不会比session大,而transaction是spring负责管理的,所以此参数也无效。这三个参数在iBatis的2.3.1版本中已经去掉了,可能也是因为这个问题。

基于上述分析我们决定使用数据库连接作为限制条件(通过使maxSession值大于数据库连接数),放弃使用session来进行控制。这个事也让我明白了在事务中尽量不要使用锁(特别是无等待时间的锁),不管是显示的还是隐式的(框架的)。关于jdbc的超时设置可以见参考资料2 3 4

参考资料:

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

《面向生产环境的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》