导语:腾讯计费是孵化于支撑腾讯内部业务千亿级营收的互联网计费平台,在如此庞大的业务体量下,腾讯计费要支撑业务的快速增长,同时还要保证每笔交易不错账。采用最终一致性或离线补偿的方案往往会带来较多的处理风险或投诉。因此,我们提出了一种通用的基于应用层的长事务解决方案,将复杂的分布式一致性问题化繁为简。
英国计算机专家Hoare所言,软件设计构建有两种方法,一是使其尽可能简单,从而一目了然确定其中不存在缺陷;另一种方法则是使其极为复杂,以至于看不出什么明显的缺陷。然而,实现第一种方法要困难的多。
腾讯计费是孵化于支撑腾讯内部业务千亿级营收的互联网计费平台,目前汇集国内外主流支付渠道,提供账户管理、精准营销、安全风控、稽核分账、计费分析等多维度服务。平台承载了公司每天数亿收入大盘,为上百个国家(地区)、万级业务代码、 百万级结算商户提供服务,托管账户总量 300 多亿,是一个全方位的一站式计费平台。
在如此庞大的业务体量下,腾讯计费要支撑业务的快速增长,同时还要保证每笔交易不错账。然而在分布式场景下要满足ACID,保证所有操作均执行成功才提交结果,随着分布式规模的扩大要达成一致性的时间周期越长。例如,Bitcoin的Scalability问题,为了满足一致性一笔交易的平均确认时间需要10分钟。因此,为了适应复杂的业务场景出现了Base理论,使用最终一致性来代替强一致性。在计费场景下,通常的一些最佳实践是:
通过上述等机制,在异常情况下可做到读修复,写修复,异步修复等,以保证事前和事中的交易高一致。但考虑到问题的闭环,我们也建立了一套完善的针对实时订单,异步订单,离线订单的三级对账机制,以做到事后的保证。
然而,对于日均过亿的交易量,采用最终一致性或补偿性的方案往往会带来较多的处理风险及投诉,需要我们做到实时的一致性。同时,计费系统的逻辑多且复杂,目前涵盖了近百个特点迥异的支付渠道,异常处理强依赖开发者的经验,需要由平台统一处理异常问题来提高服务的容错性。随着业务系统的发展,系统会有不断的新功能迭代,加强逻辑的可管理性从而降低开发门槛,也是我们需要考虑的。
化繁为简是我们的目标,为了应对上述挑战,结合我们在计费领域的多年工程实践经验,决定着力打造一套通用的基于应用层的分布式高一致解决方案TDXA (Titan Distribute eXtended Architecture),将复杂的分布式一致性问题交给引擎平台处理使业务开发更加聚焦,主要实现以下目标。
TDXA通过在内部业务的试点,基于TDXA实现的计费服务,上线后通过对账统计发现异常的交易订单数量明显减少,整体服务质量提高。同时,由于TDXA屏蔽了复杂的异常处理,业务开发效率也随之提高,相同需求的开发工作周期较之前减少了近50%。
在工程上,作为完整的计费服务,一致性不仅需要考虑数据层的数据一致性,同时也要考虑应用层的逻辑一致性。比如,转账这个行为,数据层可以保证每次数据的更新一致性,而应用层如果只减不加,用户看到的账户数据也是不一致的。交易的一致性,简单讲就是收对钱,发对货。看似简单的描述,但在实现中却面临了很多约束,导致容易出现一致性问题。比如:
在OLTP场景下,用户的一次购买请求通常会涉及多个后端服务的操作,如果其中某一个服务调用出现了二义性错误(网络超时),此时应该返回失败还是成功,之前调用过的服务是否需要回滚?倘若请求量小的情况下,可以借助人工来处理部分调用异常,但是当请求量大了以后,人工介入的成本就非常高。
计费服务的整体质量要求至少达到四个9,同时不允许交易出现坏账,对外提供事务一致性保障。从一致性的本质来看,要么保证在一个业务逻辑中包含的服务都成功,要么都失败。在计费场景下,后端服务可能涉及接口,数据库,分布式缓存,消息队列等,我们希望做到针对不同后端资源的长事务一致性。
我们的做法是,通过引入分布式事务管理器(Transaction Manager),将全局事务分解为多个子事务,并交给不同的资源管理器(Resource Manager)处理。同时,针对不同的资源类型抽象为不同的事务处理模型,帮助业务实现自动的事务提交或回滚。面对复杂的长事务流程,业务可以自定义组合不同的事务模型,并根据业务自身特点指定不同的异常处理策略(Error Strategies),让业务开发像处理单机事务一样来保证整体分布式事务的一致性。通过这样的方法,可以有效地帮助业务开发减少异常处理,更专心地实现业务核心逻辑。下面分别对不同的事务模型说明如下。
除了以上常见的几种事务处理模型,我们也在不断完善新的事务模型。其中每种事务模型都定义了相应的回调接口由业务实现(如下表所示),同时在使用上也有一些约束准则要求。
最后,以用户转账为例看如何实现混合类型的事务流程。首先在第一阶段,调用订单服务创建一笔订单(try),然后提交转账sql1和sql2。第一阶段执行成功后并对用户发起一条消息通知(do)。若第一阶段执行成功,在第二阶段,框架会自动完成第一阶段的确认过程(confirm和commit);若第一阶段出现异常,框架也会自动完成回滚过程(cancel和rollback)。业务开发需要保证业务逻辑上的合理性,将可以回滚的操作先执行,而由框架保证整体事务的一致性。
具体交互过程如下图所示。
根据墨菲定律,事情如果有变坏的可能,不管这种可能性有多小,它总会发生。我们的系统服务也是,虽然大多数情况都是可以正常对外提供服务的,但是在少数情况下也会出现服务不可用,网络超时等问题。因此,当服务调用出现异常时,如何保证事务可以继续自动执行完成?一种可行的方案是,将事务状态保存和业务事务通过数据库本地事务打包成一个原子事务,当异常出现时,可以通过一个消息重放服务将事务继续执行完成。
此方案的特点是,存在一定的业务侵入性,并且依赖单机DB的处理性能。为了解决这样限制,提出了另一种基于消息中间件的方案,首先消息中间件本身需要保证数据的可靠性,同时,在客户端通过引入一个生产代理Proxy,来解决在无法及时写入的情况下,使用本地存储充当一个缓冲区。通过结合本地队列和远程分布式队列构成一个可用性更高,延迟更低的分布式消息队列方案。
|
因此,我们需要消息中间件满足以下要求:
下面是我们基于Pulsar实现的一套高可靠的消息中间件解决方案,整体架构如下图所示。
|
关于消息中间件的实践经验,我们团队在Apache Pulsar Meetup北京站也做过一次分享,可以参考另外这篇文章Apache Pulsar 在腾讯计费场景下的应用。
由于计费流程的复杂性,通常一笔完整的支付请求需要至少几十次的rpc调用。我们希望加强对流程的约束管理以减少可能的逻辑错误。我们的主要解决思路是,通过状态图定义服务调用流程,基于事件进行驱动来完成整个流程。其中,每个节点表示一个rpc操作,每个rpc操作分为三个独立过程(Pre/RPC/Post);每条边上可以定义多个算子,通过算子的返回值实现服务路由。
|
同时,通过引入有限状态机白名单的方式,保证业务状态的流转是合理的,可规避一些不必要的业务逻辑错误,减少异常错误的发生。例如,不会出现已经支付的订单被再次提交支付,即Double Pay的问题。
业务开发只需要关心业务节点注册的Pre和Post回调接口的具体实现,以及每个业务节点操作结果的返回算子,其余工作由框架完成整个流程的驱动,以及分布式事务的提交或回滚。同时,我们支持将当前的事务状态通过Json描述出来,并转换成流程图,让复杂的流程更加直观。
腾讯计费高一致TDXA,旨在帮助业务解决长事务的一致性问题,通过使用状态机来控制事务的执行路由,基于插件化注册机制,提供多种子事务处理模型,实现对不同类型资源的分布式事务处理,完成自动化的提交或回滚。并提供规范的二进制对接协议,以及Java等语言二次开发能力。整体架构如下图所示。
对每个模块的功能简要说明如下:
TDXA的内部工作流程,主要分为三个部分:
a) 初始化时,TDXA将从配置服务获取要加载的所有注册so或xml流程配置,并依次加载。 b) 加载后通过回调协议接口取得要注册的流程名及对应的注册函数。 c) 通过调用注册函数完成流程注册。
a) 通过解析业务请求中的事务流程名,创建需要执行的流程,并将请求数据添加到事务的数据池中。 b) 初始化事务状态为begin,并通过转移条件决定下一个要执行的状态。 c) 执行当前状态的预处理和对应服务的执行调用。 d) 根据执行结果判断服务路由,若没有找到满足的路由,则根据业务指定的错误策略完成事务恢复。 e) 重复执行c~d,直至达到状态end,或发生异常情况。 f) 最后根据事务的执行结果,设定事务返回信息。
a) 反序列化分布式消息队列通知的事务信息。 b) 对未完成的事务断点执行。
为了方便业务快速接入,我们实现了一套前端自助化流程配置和功能验证系统。希望能够帮助业务在接入时做到尽量能所见即所得。前端每生成一个节点, 就会生成一份对应的后台代码, 支持分Tab页代码编解功能。为方便重度开发人员,页面上也可以下载整体的含框架代码,开发者可以选择使用其他的IDE进行开发。
TDXA内部提供了一些必要的逻辑异常检测机制,帮助业务规避不必要的错误,包括但不限于:1,注册接口参数检测。2,DAG(Directed Acyclic Graph)合理性检测,需要满足,仅有一个连通分量,不能回环(但允许自环),每个节点都能达到终止节点保证闭环。3,运行时异常流程错误告警等。
TDXA对于业务异常的流程处理原则分为两类。对于业务类错误,引擎会把控制权交给开发者,由开发者根据返回值信息决定事务流程如何流转;对于系统类错误,引擎会返回E_PROCESSING错误码给前端,然后内部借助消息中间件进行异步重试处理,其中重试次数和时间间隔均可由业务决定。
在服务可靠性方面,TDXA中的事务管理器TM是与业务服务集成在一起的,并通过消息中间件实现事务的持久化。考虑可能出现的以下几种组件异常情况,以及如何实现故障迁移。
情况一:在TM进行事务持久化前,TM出现故障。此时,可以通过原始的事务请求进行重放。
情况二:在TM完成事务持久化后,TM出现故障。此时,未完成的事务可以由集群中其他的TM继续完成处理。
情况三:在TM进行事务持久化时,TDMQ出现故障。此时,通过本地的TDMQ Proxy异步重试。
情况四:在TM进行事务恢复时,TDMQ出现故障。此时,会选取新的可用的Broker进行消费,可能出现多次消费,需要业务保证幂等处理。
事务的核心是锁,而事务和性能是两个相悖的特性,因此在满足分布式事务的前提下,也需要考虑对系统性能的调优,通常的优化方法包括:1,尽可能减少锁的覆盖范围。2,MVCC多版本并发控制协议,读写不冲突。3,选择正确的锁类型。悲观锁适合并发争抢比较严重的场景,而乐观锁适合并发争抢不太严重的场景。TDXA在性能调优方面做了以下考虑。
优化1:通常引入事务协调者会增加网络调用次数,因此会减少系统的并发处理能力。为了尽量减少不必要的网络调用,我们将事务协调者与业务服务集成在一起实现,提供了插件和配置的方式注册业务事务。
优化2:允许不相关的事务可以并行执行,即pipeline操作。例如,b0~b3与c0~c3是两个独立的操作流程允许并行执行。
优化3:对于TCC事务模型,操作相同资源的不同事务是独立的,不需要等待执行。例如,事务T1在没有confirm或cancel时,事务T2可以继续执行try而不需要等待事务T1完成。
优化4:针对数据库外部XA的性能优化。在XA模型下,如果由于不稳定的网络通信导致事务没有提交,会影响所有其他事务都在等待。我们针对数据库的网络模型增加了线程池的处理模式提高了I/O的处理能力,较原生的半同步方式提高了近5倍的性能。具体可以参考计费平台部高一致性存储层架构变迁之路---分布式MySQL数据库(TDSQL)架构分析
腾讯计费作为承载每年千亿级业务营收的计费平台,保证支付交易高一致,每笔收支不错账是我们的核心能力。分布式服务相比单体服务,为我们提供了更好的扩展性和灵活性,但也带来了复杂的一致性问题。腾讯计费高一致TDXA在此背景下应运而生,旨在实现长事务的一致性,支持多种资源的混合分布式事务,在异常出现时可以实现零人工介入处理。让复杂的问题在这里变得简单,化繁为简是我们的目标。最后,考虑到极端情况,我们还会借助三级对账,通过实时订单,异步订单,离线订单的三级策略,来保障异常情况下的交易一致性。
腾讯计费通过多年的实践探索,我们构建了一套完整的金融级计费解决方案,TDXA作为其中的一个重要组成部分,其他的还包括TDSQL,TSM,TBC等。目前,TDXA已经在腾讯计费内部业务广泛应用,包括,大额的企业支付,腾讯云计费,腾讯计费开放版等,在实际场景中得到了验证,减少了不必要的人力成本,业务服务质量得到显著提高。
最后,我们在解决了一些问题的同时,也遇到一些新的问题。