本文根据第23期美团点评技术沙龙演讲内容整理而成。
酒旅有很多条业务线,例如酒店、门票、火车票等等,每种业务都有结算诉求,而结算处于整个交易的最后一环不可缺少,因此我们将结算平台化,来满足业务的结算诉求。结算平台通过业务需求以及我们对业务的理解,沉淀了各种能力并构建了丰富的能力地图。我们将业务的发展归纳为几个阶段,例如业务孵化阶段,快速抢占市场扩大覆盖阶段,市场稳定后急需盈利阶段,国内业务稳定后的国际化阶段,业务发展的各个阶段都能在结算平台找到相应的能力支持。业务孵化阶段讲究的是低成本试错,我们将结算的核心流程,账单->付款->发票等模块平台化,新业务接入只需要5~10天。我们的预付款结算,分销结算,阶梯返佣结算,地推结算能力能快速的帮业务抢占市场实现盈利。我们的汇率管理,多币种,多时区结算能力可以助力业务开展海外市场。当前结算平台支持酒旅4个事业部,17条业务线,涵盖境内、境外等业务的线上结算,后边我主要介绍下对账平台的实践即账单的实践。
对账是平台化的第一环,它需要算清楚商家和美团点评的收益明细,后续的付款,发票等,都是基于对账进行开展的。同时它需要对接酒旅的各个订单中心,不同业务的订单具体实现和业务流程不同,不同的业务对账规则和时机也不相同,同时对账每天需要处理数百万条交易明细。
如何解决订单、对账层面的差异?每天处理数百万交易明细,如何高效准确的处理这些数据?17条业务线如何做隔离、定制、个性化扩容?其中面临了很多挑战,这也是为什么要讲对账的原因。
账单的生成逻辑主要分两部分:
优化的策略有两点:
提高数据处理能力思路是:单线程改多线程,串行改并行,单机计算改分布式计算。具体做法是:商家ID维度用取模的方式分片,通过配置将不同的分片配置到不同的server,同时每个server上有单独的线程池,可以在商家ID的维度并行获取数据,处理账单。这样虽然能快了起来,但是它又引入了一些新的问题。比如单点问题,如果server2宕机了,配置不具备自动rebalance的能力,就需要结合报警来人工处理,运维压力会变大。如果继续提升处理能力,就需要更多的数据分片,更多的server资源,这时需要去重新处理分片和配置的逻辑,虽然它是可扩展的,但是并不灵活。
资源利用率波动也比较大,在7月1日处理整个月的数据,资源利用率(蓝线),在月初的时候最高,平时的时候基本上接近于0。比较健康的资源利用率,应该像黄线一样,虽然会有业务的峰值,但是整体上来看是趋于平稳的。如果是蓝线这样的资源利用,就会导致订单系统、结算系统、DB,都需要按照业务的峰值来部署,会产生资源浪费,最后我们在速度和资源的使用上求一个平衡,通过这些优化做到了及时结账。
但还遗留了如下问题:
上边这些问题在对账平台化时都需要解决,尤其是逻辑耦合问题,如果不解决,后续对接的业务越多,步伐越沉重,最终会拖慢业务的发展。解决这个问题需要优化结算系统的业务架构,需要抽象一套订单模型,商家模型,计算规则模型,这些模型要足够通用,在兼容各个业务差异同时又要有一定的扩展和定制能力,模型的抽象是平台化的关键。
结算抽象了一组统一的订单状态来描述订单的整个生命周期:支付成功,使用前退、使用、取消使用、使用后退。它们所代表的含义如字面上一样,简单便于理解,每来一条业务线,将订单的原始状态和结算抽象的状态做映射,就能解决订单层面的差异。以酒店为例:预定周日晚上的酒店,周四行程变更无法入住,周五找客服退款,客服退款成功,这个对应使用前退,客服退款在一些特殊的场景也会对应到使用后退,最终解决了订单状态层面的差异。
状态过滤出来后要计算这笔交易商家和美团点评分别的收入和支出是多少。如上图所示酒店要计算收入和支出需要订单的基础信息、间夜信息等因子,这些因子可能散落在各个数据表中,收集和计算非常复杂,业务变更会导致计算因子和计算逻辑变更,不同业务计算因子不相同,所以它很难复用。
如上图所示
老架构
数据获取采用PULL模式,如果上游故障会导致无法获取数据,从而影响账单计算,重度外部依赖。不管是数据获取还是计算账单都需要穿透两层业务逻辑,业务逻辑严重耦合难以复用。
新架构
抽象了资金语言,商家信息,结算规则,标准化了接入规范,数据接入采用PUSH的模式,业务产生了交易,新签约了商家只要能将数据按时的PUSH过来就能按时结账,结算被动接收数据,轻度外部依赖。新架构设计了适配层,用来做业务数据和标准协议的适配,适配层逻辑结算和业务都可以做,但是考虑到业务侧的同学更了解业务,业务变更自己修改适配层不需要找结算排期更灵活,我们将适配层交给业务团队来做,每来一个新业务只需要一个小的适配块,就能快速接入。通过抽象的模型和标准协议,对账平台做到了数据聚合统一,计算规则统一,数据模型统一,从而达到了高度复用,结算和业务解耦。历史上酒店和旅游订单发生过多次重构,对结算基本无影响。人员也得到了高度复用,当前对账模块只有4名RD对接了17条业务线。
数据接入
订单产生交易,将交易转为资金语言,通过消息中间件(Mafka)实时的推送给结算,结算只做必要的校验,完成后数据落到mysql中,此时数据的状态是未处理,这一步设计了ack机制保证数据不丢。落库后会给账单引擎发一个消息,说有一条数据要处理了。数据的接入和账单引擎的计算做了分离,数据的接入非常快,基本延迟在毫秒级别。
账单引擎
账单引擎会在6月1日生成6月1日到6月30日的空账单,数据经过聚合规则,进入到相应的账单,同时触发账单计算规则,完成账单的实时计算。一些特殊的情况例如数据产生在6月1日,结算日期是6月2日,会将这个消息转发到Mafka的延迟队列,在6月2日重新消费这条消息,数据处理成功后,数据的状态变更为已处理。账单引擎的补偿和监控机制是通过对数据的状态控制来实现的,例如6月1日到30日,商家A一共产生了1万条交易明细,已处理的数据只有8千条,那剩下的2千条要么就是在途,要么就是消息丢了,或者系统BUG,会有一定的监控和补偿策略来保证数据的完整性,账单引擎处理具体的交易明细延迟在秒级。
提高账单的处理效率需要从两个维度出发:
提高高并发设计
我们通过消息中间件Mafka来提高并发度,在Topic的维度拆分多个Partition,不同的ConsumerGroup配置不同的消费线程数,Mafka基于滑动窗口机制实现了多线程消费同一个partition的数据,所以可以做到并发度=partition数量*消费线程数,在数据处理上做了幂等性保障,能确保数据的正确处理。从架构上来看除了DB之外全链路无单点,比如某个server挂了以后,通过Mafka的rebalance机制,将partition自动分配给别的server,消除了server层面的单点问题。
提高并行度设计
单个对账单包含了多条交易明细,账单的总金额是多条交易明细金额的累加,并发度上来以后,会产生线程安全性问题,怎么保证账单总金额的数据准确性?常规实现://事务保证原子性try{Locklock=distributedLockManager.getReentrantLock(statementId);//账单维度锁lock.lock();//获取锁,存在阻塞insert(detail);//写入明细compute(statementId);//计算账单}finally{lock.unlock();//释放锁}
如上边的伪代码所示,要有一个事务保证明细写入和账单金额计算的原子性,其次是获取分布式锁,写入明细,计算账单,因为锁的缘故单账单的并发度是N,并行度是1,并行度低的结果会导致消息出列变慢,单个账单的处理效率变低,有了事务也会有一定的性能开销。怎么提高并行度,怎么减少事务的粒度成为单账单维度高性能计算的关键问题。
提高读性能
场景一
以前边所提账单的计算为例,核心语句如下:selectsum(金额)from账单明细表where账单ID=X,当数据规模在亿级怎么保证效率?我们除了在账单ID维度增加索引以外,还通过分表,数据备份的方式尽可能保证单表里数据量不至于太大,来提高处理效率。
通过中间件atlas,账单明细按照账单ID取模的方式进行分表,保证同一个账单明细在一张表中,虽然会有一定的数据分布不均匀的问题,但是在很大程度上也能避免同一个表过于庞大,从而提高的SUM操作的性能。账单明细有一个特性是不可变更,我们的业务场景是写多,读少,Hbase也支持水平扩展,很适合做数据备份,所以我们通过databus将数据同步到Hbase中一份,还设计了一个程序定期的去对比MySQL和Hase的中的数据,账单结账后就不需要SUM操作了,经过一定的周期以后可以将MySQL中的明细做清除,释放MySQL的存储空间,只要一开始账单明细容量预留的足够大,通过这样的策略基本上能避免后续业务规模化后扩容产生的数据迁移问题,但是会有另外一个问题是需要定期处理数据库因为删除操作而产生的表空间空洞问题。
场景二
我们将交易明细做了冷热隔离,将已结算的数据备份到备库中,未处理的数据整体量级在10万条以内,主要是不到结算时机或者在途的一些数据,在配合一些索引,整体的处理效率是非常高的。
DB读写未来的规划
结算平台对接了17条业务线,不同业务体量不同,所需要的系统容量也不同,怎么定制化扩容?业务也会有一些特殊流程和逻辑怎么做逻辑定制?怎么做到A业务线故障不影响B业务线?
如上图,我们根据一定的路由规则,将数据灌到不同的topic中,酒店体量比较大可以针对酒店topic配置较多的partition,火车票体量没有酒店大可以配置相对少的partition,针对不同的消费组配置不同的消费线程数,从而做到给不同业务分配不同的系统容量。账单引擎由通用逻辑+定制逻辑实现,定制逻辑以消费组维度做隔离,每个消费组配置单独的开关,当业务产生异常需要暂停结算时,关掉开关,对其他业务无影响。server集群公用,通过Mafka的机制根据topic和partition自动划分资源,最终做到个性化扩容,多业务隔离相互不影响。
主要分三层:
整体的扩展是需要多层进行配合的,比如Mafka的并发度特别高,server数也特别多,但是只有一个DB,DB可能就是一个瓶颈,那就需要在DB的层面做一些扩展和优化。
最后呈现出的效果是,数据接入基本上延迟是在毫秒级别,对账单是实时对账单,产生交易后商家立刻就能在账单上看到收入明细,对商家来说体验非常好,对接了17条业务线,从不交叉影响。