传统支付系统面临的挑战
随着近年来移动支付的兴起 ,如条码支付、声波支付、NFC近场支付等,随之还产生了聚合支付把多种支付方式聚合在一起,方便人们的使用,移动支付已经渗透到我们生活的每一个角落,不带钱包出门已经没有任何阻碍。
这就给传统的支付系统提出了新的挑战,用户量激增,系统容量和性能跟不上了。传统的架构往往以IOE技术为主,采用scale up方式以更强的硬件提升系统性能和容量,扩容成本将是巨大的。
支付机构是持牌机构,都有受到监管,对系统稳定性有强要求,传统架构下往往都会用冷备的方式来进行容灾,意味着又要投入一倍的成本。由于数据库主备复制的延时,必须等到数据同步完成才可以切换,容灾切换时间长,进行分布式改造已经刻不容缓。
分布式架构有着海量、成本、稳定、速度的优势,其核心思想是“解决一切单点”,单点容易出故障、性能有瓶颈,那么就拆分成多个点。垂直拆分能更清晰化模块划分,区分治理,水平切分能解决大数据量性能瓶颈问题,分布式改造主要是将这两者结合起来对传统架构进行改造。
分布式改造之垂直拆分
垂直拆分就是将原来一个整体的系统按业务模块拆分成多个系统。系统内部数据是自包含的,不会与别的系统共用数据库,系统与系统之间的交互通过暴露和调用服务来实现。那么如何按照业务来拆分呢?
为了方便理解,首先我们来看一下一笔支付过程是如何进行的:
图:简化的支付路径图(注:该图来自蚂蚁金服内部)
- 商户发起收单请求,经过API网关,调到产品层的“在线收单”产品
- 调用收银台选择支付方式,也可能直接进入支付环节,创建交易流水
- 进行支付处理,通过金融交换从银行扣客户帐,记录帐务流水,入商户帐,记录账务流水
- 对交易按照费率进行收费,记录收费的帐务流水。此时会异步触发营销和风控策略
- 日终会异步进行会计记帐(也有同步记会计帐的)、业会核对、清结算和对帐处理
那么从这个过程我们就可以大概推演出支付系统的一般应用架构:
图:支付系统的应用架构
那么如何进行垂直拆分呢?刚刚我们推演的应用架构派上用场了。
什么是应用架构:应用架构定义一个大型软件系统由哪些应用子系统构成,以及应用之间如何分工和合作。好的应用架构抽象合理、协作有序、易于扩展、能够复用。有了这个应用架构,我们可以非常清晰的根据应用架构划分的子系统来拆分。
从架构上来说,分为四层:
图:支付系统的分层
渠道层:商户和客户的交易请求的入口。一般会划分以下系统:商户网站、用户网站、无线接入、API网关。
产品层:通过基础服务层提供的服务组装成具体业务场景功能,对客户、商户运营等人员提供服务。一般会把服务商户的功能划分为商户域,服务C端用户的划分为用户域。可以按照这两个域拆分成两个子系统,也可以更进一步根据不同产品特性再拆分,比如商户域中的收单产品、虚拟产品、垂直行业产品。
公共服务层:将各个产品都需要使用的些服务抽像成公共服务。一般会划分:收银台、交易支付、计费等系统。比如说产品层可以通过组装各种交易类型和收费规则形成不同的产品。
基础业务层:支付系统的核心,资金和客户信息的处理都在这里。一般会划分三大子系统:帐务核心、会计核心、会员核心。
其它支撑系统:
网关:负责与银行、银联等金融机构进行资金交换,与外部合作伙伴接入,如渠道拓展商、行业客户等。一般划分:银行接入网关和合作伙伴接入网关。
运营支撑:贯穿于四个层的是运营支撑域:一般会划分运营支撑、安全、风控、营销子系统。
垂直拆分本质上是服务化改造,除了上面的讲的按业务拆分,还需要一套分布式服务框架的支撑。
分布式改造之水平拆分
前面讲的垂直拆分只是把系统按业务模块划分到不同的子系统,数据库也分到了不同系统,但没有解决单表大数据量的问题,而水平切分就是要把一个表按照某种规则把数据划分到不同表或数据库里。简单的说就是做分库分表。
在做分库分表之前我们需对数据模型进行分类,分为“流水型数据”、“状态型数据”和“配置型数据”。
- 流水型数据:像流水一样不断增长的数据,各条数据间是独立的。如支付订单、交易流水、帐务流水(入帐/出帐)、会计流水等。
- 状态型数据:代表一个对象当前的状态的数据。如会员信息、客户信息、帐户信息、会计帐。
为什么有会员信息还有客户信息?会员往往是注册在支付平台的用户,一个人可以注册多个会员,但是一个自然人只可能有一个客户信息,一个会员通过实名认证后就关联上了客户信息。无论一个客户注册多少个会员,实名认证后都只有一个客户信息。
- 配置型数据:系统中用作为配置的数据。如产品、手续费率、分支机构信息、支付路由规则、会计科目等。
流水型数据会不断产生,且各条数据间是独立的,天然适合进行分库分表。
状态型数据读写比相当,每一次写操作必须基于前一个正确的状态,可以评估一下数据量的大小,数据量如果大或者要实现单元化架构,也需要进行分库分表,提高并发处理能力,同时方便隔离故障影响。如果使用mysql数据库,单表最好不要超过五百万条记录。
配置型数据,读多写少,强依赖读,弱依赖写,不要求严格的读一致性,且配置型数据一般数据量不会很大,不需要进行分库分表设计。但是业务处理中往往又需要用到,传统架构的老系统可能使用了一些关联表操作,关联到了配置数据,分库后其它数据与配置不在一个库,不能进行关联表操作,由于配置型数据不要求严格的读一致性的特点,可以将配置型数据加载到分布式缓存里,如redis、memcached,由业务代码来做“join”。
那么分库分表按照什么规则来拆分呢?通常我们不会按实体id进行hash取模的方式来拆分。因为我们希望同一个用户的数据能够在同一个数据库中,尽量避免产生分布式事务。那么业界普遍的做法是通过用户维度来进行拆分。由于不同的实体id的值不同,且不能保证每个实体和请求中都包含用户id,所以简单的用实体id或用户id进行hash取模将不能保证同一个用户的数据都落在同一个分片。
我们的做法是在用户创建的时候给该用户随机或一定规则(如地区)生成一个两位的分片号00~99(两位意味着可以分成百库百表,通常够用了),那么在生成与该用户相关的所有实体的id的时候都约定把这个分片号拼接到这个id中。那么在分布式数据访问框架中进行路由选择时就可以取id中的分片号进行路由,而不依赖于用户id。且在排查问题的时候也非常方便定位数据的存储位置。
下面是一个参考的id生成规则示例:
所以数据水平拆分除了需要一个强大的分库分表数据访问中间件,还需要一个分布式序列生成器。当然这个生成器也可以是集成在分库分表数据访问中间件中的一个功能。
那么如果一笔交易涉及多个用户按谁的id来拆分呢?比如一笔转账或支付,涉及转出方/转入方或支付方/收款商户。这种情况我们一般按资金转出方来拆分。
分布式改造后带来了哪些问题,如何应对
1. 分布式事务产生
由于按用户维度进行了分库分表,可能存在跨数据库的事务,比如说转账交易中转出方和转入方的账户不在同一个数据库中,这就产生了分布式事务。通常我们不会用XA协议来解决,因为XA协议锁资源性能太差,通常是通过TCC柔性事务来解决。
2. 跨表查询如何解决?
由于分库分表后,不能进行跨库的连表查询,原来的一些很常见的查询操作变得很麻烦。对于不是以用户为维度的汇总查询也非常麻烦。比如说支付交易流水是按发起方用户(支付方)进行拆分的,用户需要查询自己的账单很容易。但是商户要查询账单就比较麻烦了,要去所有的库里遍历、汇总、分页。也非常耗系统资源。所以一般会做一些数据冗余,比如说蚂蚁内部是专门做了一个账单系统,通过消息队列异步将用户的交易流水同步过来,T+1跑批再按商户维度进行拆分,并生成商户账单。查询帐单都从帐单系统中查询。
还可以通过异构索引来查询和做OLAP分析,异构索引就是将数据同步到ElasticSearch,利用ES的强大索引能力来做查询和分析,为了使业务更容易使用,可以利用数据访问代理层来屏蔽底层是路由到数据库还是路由到ES。
3. 如何进行数据同步?
企业都有做大数据分析的需求,需要将数据同步大数据平台,如hadoop,分库分表之后,数据同步会比较复杂,毕竟之前是单表同步到 hadoop比较简单,但是100张表同步到hadoop里会复杂一些。
我们需要有一套数据模型管理平台,数据模型、分库分表规则等由这个平台来管理,当需要使用数据的时候通过(应用/逻辑表)维度订阅数据即可,不用单独订阅物理表。不仅是数据同步,凡是有业务需要用到各种数据,都可以通过这个平台来订阅,帮助企业数据业务快速发展。
4.分库分表后批处理任务怎么处理?
批处理任务,比如有日终对账、清算、生成账单等,原来在一个数据库中的时候,由一个应用Server去数据库中捞取流水就可以了。但是分库分表后流水都落在很多库里,一个Server去每个库里遍历显然不是一个很好的办法,且不能充分利用机器资源,提高批处理效率,甚至由于处理的数据量太大在日终低峰期内根本无法完成任务。
前面提到各条流水数据之间没有关联的,完全可以并发的进行处理,每个Server捞取一个分片的数据进行处理。那么就需要有一个很好的调度系统来协调,可以采用三层调度的方式。
图:三层调度示意图
- 第一层split:把任务按照分片规则拆分成多个Load任务,并发送到集群中的Server去执行。
- 第二层load:每个load任务捞取一个分片的数据,逐条创建execute任务,并发送到集群中的Server去执行。注意:捞取数据要进行流量控制以免数据量太大把集群打满。
- 第三层execute:执行具体的一条数据的逻辑。
三层架构并不是说一定都需要三层,可以根据业务逻辑来定制只有两层也可以。
5. 如何进行数据扩容?
通常我们会采用“预分配”的方式来做,即一开始我们就按一个比较长期的容量来规划分片数,比如百库百表,但实际上一开始并没有这么大的量,所以实际只有两个数据库Server,在这两个Server上分别建50个schema,逻辑上扔然是100个分库,物理上只有2个数据库Server,当容量不够的时候,为了保证数据的均衡,我们通常会采用成倍扩容的方式,再加两台数据库Server,然后分别迁移25个schema到这两个数据库Server上,数据也搬过来,由于数据同步有延时,全量数据同步完成后,两边的schema都禁写,待增量数据同步完成后打开新的schema写,会产生短暂的部分用户交易失败,重试一下即可,在低峰期做迁移,产生小范围失败一般是可以接受的。由于逻辑分片数没有变化,扩容成本比较低。我们通常不会用改变分片规则的方式来扩容,因为改变分片规则需要进行数据重新分布,成本和风险巨大。
6. 如何进行容灾?
- 同城容灾:通常可以同城多机房部署应用,数据库只有一个机房处于Active状态,所有机房的应用都连这个机房的数据库,另一个机房的数据库为备库,进行主备复制,当备机房发生灾难时业务不会中断,但业务会跌一半,当主机房发生灾难时,数据库切换备库,会有短暂的业务中断。
- 异地冷备:应用也是异地多机房部署,由于异地网络延时不可忽略,异地备机房是处于standby状态,正常是没有流量的,冷备机房采用数据库主备同步的方式同步数据,这种方式灾备切换时间长,成本投入高。
- 异地多活:应用采用异地多机房单元化部署架构,每个机房的应用都是可以提供服务的,单元内是自包含部署全量应用,每个单元服务多个分片的用户,单元化架构可以参考《素描单元化》。由于异地网络延时是不可忽略的,数据层的容灾方案也是分“流水型”、“状态型”、“配置型”数据采用不同的容灾策略。具体可参考《分布式系统数据层设计模式》。
7. 如何更好的排查和分析问题?
分布式改造后整个系统架构已经是服务化了,原来通常可以通过查本地日志来定位问题。但现在一个交易由若干个系统协同完成,我们需要一套分布式链路跟踪系统或APM(应用性能管理)系统来协助我们看清整个系统的全貌,分析排查问题。那么如何进行分布式链路跟踪呢?可以通过opentracing标准对整个分布式架构中的中间件和应用进行埋点或自动植入探针实现。
总结
分布式架构有着海量、成本、稳定、速度的优势,但它也不是银弹,分布式改造是一个非常复杂的工程,需要熟悉业务,设计出整个系统的业务架构,按照业务架构来进行垂直拆分。需要熟悉数据模型,区分“流水型”、“状态型”、“配置型”数据,根据不同类型数据的特点将它他按用户维度进行拆分。
熟悉分布式中间件的运用,分布式中间件在整个分布式架构中起着至关重要的作用,将技术构架与业务结合起来。整个改造需要公司内部下定决心,All in的方式进行。蚂蚁金服通过十五年五代金融级架构的演进,多年双11的考验,形成了一套业界领先的金融级分布式架构。