近来,FF项目的运营活动越来越多,对于架构设计以及程序研发有了更高的要求,参考国内互联网公司对于营销活动app的设计思路,我们找到了最具有代表性的支付宝双11活动,阐述运营活动类高并发模块的设计思路,并阐述其测试方案。
对于高并发类app的设计,我们需要从两个方面考虑:一是app端的设计,另一个是后端服务的支持,其中对app端的设计比较简单,一般是数据预缓存和cdn分发技术,后端的设计是最重要的环节,本文着重讨论后端设计。
一、运营活动性能问题分析
(一)数据库单点问题
一般项目数据库使用的是单oracle数据库,没采用分库分表的方式,在平时的业务活动中,事务性的业务较少,数据库不会存在太多由事务造成的压力,但运营活动需求中,会存在资源原子控制,存在更多的事务性处理逻辑,用户瞬间并发量大,所以数据库存在单点瓶颈问题。
(二)服务器数量过少,无互备机房,入口单一
一般项目的生产环境只有一个机房的服务器,且入口处服务器数目较少,对于高负载的瞬间请求存在宕机的风险,另外由于地域差异,对不同地区的响应存在时间差异,部分地区延时较高。对于运营活动的静态页面资源目前没有cdn分发,导致主服务器负载过高。
(三)总线模式成为单点瓶颈
基于总线的架构模式,使得总线成为请求关联系统的一个单点,当并发用户数较多,总线吞吐量不能满足要求时,导致整个系统的崩溃。目前少数互联网公司在soa架构中的去中心化的架构模式也印证了类似集团总线模式对于高并发高吞吐服务的弊端。
(四)关联方并发资源不足
在运营活动中,往往发现本身的服务没有挂掉,关联方系统挂掉,导致服务无法正常进行,这主要是因为运营服务相关的出口没有做流量控制所致。
二、支付宝双11技术解决方案。
(一)前端:cdn+dns动态负载+预缓存
对于app静态资源,支付宝客户端主要采用预缓存的方式,其次联合cdn和dns动态负载技术减少静态资源获取的单点问题。
1.app预缓存技术
对于静态资源,支付宝客户端在客户连接wifi时会对支付宝客户端所用到的静态资源进行预缓存,将所有需要用到的数据缓存到本地,运营活动的html5页面的缓存保证了运营活动时用户和服务器的交户数据只存在接口的交互,而不存在静态资源的交换。
我们可以试验一下,打开支付宝客户端,清空所有缓存,之后连接wifi,登录客户端之后不做任何操作,通过抓包的方式可以发现客户端会拉取部分静态资源。或者每次在断网状态下客户端时,你也可以发现我们可以进行某些活动的操作,只是无法完成请求提交。
2.cdn和dns动态负载
cdn是很老的一种技术,被广泛应用在网页中静态资源的分发,减少主服务器的压力,支付宝客户端静态资源的分发也使用这种技术,尤其对于高并发要求的运营活动是必须使用cdn分发的。且这种静态资源通过dns分省解析技术,可以使用户找到最近的cdn服务器,增加用户体验。
(二)解决数据库单点问题:分库分表法+分布式缓存
对于业务处理来说,负载均衡机制把业务负载到多个服务器是明智的选择,但对于数据库来说把数据读写负载到多个服务器来做的方式叫做分布式数据库,例如Hbase,mongodb,但往往把数据库进行分布式处理增加数据维护成本的同时也降低了数据库稳定性,队于支付宝这种金融级别的高安全要求,类似hbase的弱关关系型数据库显然不是其最佳选择,在研究自己的数据库oceanbase库(双11已经运用)的同时,还是倾向于oracle数据库的成熟解决方案,为了让oracle也支持分布式,支付宝采用分库分表法,而对于高并发、非关系型的数据存储采用分布式缓存,将数据读写转移到内存中。这里重点探讨分库分表法。
分库分表法是利用表和库的id,根据订单主id去判别订单落库的位置,我们以订单2016041212212221200123为例,最前面的是时间戳和唯一约束数,我们看最后5位-3位数字001是分库分表标志位,订单生成后此标志位决定了其所在库,以后所有的操作均依据单号去读写对应的库,由于这种操作在数据库的存储过程中得到了封装,对于上层来说是透明的,上层的逻辑处理完全可以看作是一个库,上层无需考虑数据库分库分表的逻辑,而且由于分库分表之前完全考虑到了连表查询的情况,在分库分表设计时联合业务和切分法则,避免了跨库查询的情况。
以上的分库分表法采用的是数据库切分中的水平切分,某些情况下也会采用垂直切分的方法,即把关系紧密(比如同一模块)的表切分出来放在一个server上。垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非常低,相互影响很小,业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业务模块所使用的表分拆到不同的数据库中。根据不同的表来进行拆分,对应用程序的影响也更小,拆分规则也会比较简单清晰。
(三)分布式数据一致性:变同步为异步,保证最终一致性-消息中间件
支付宝作为一个金融机构,对于各项事务的一致性做的也是业界顶级的一家公司,它具有自己独有的数据一致性框架ATS(阿里分布式服务框架)保证事务的强一致性,而这种强一致性强调实时性,很大程度上导致性能低下,于是工程师们原创出弱一致性解决方案:核心原则为保证事务的最终一致性。他们发现:对于支付和通知付款成功这种类似逻辑,不需要要求多方数据的实时一致性,而是要保证最终一致性,于是通过消息队列的方式,将数据一致性问题作为消息通知发给相关联系统,通过重发来保证关联系统最终可以接收到,通过幂等性来保证重发导致的重复处理的问题。而且在整个机制中每个事务都会落到一个事务表,如果事务最终没有保证一致性,定时任务触发之后会保证将非一致性的事务做完,也最终保证了一致性。
(四)安心药:资损统计,后期处理。
对于一个晚上几千亿的资金流,我们即时充分相信工程师们的能力,同时由于双11当天各种优惠券,各种天猫积分等参与支付,最后的对账环节比较复杂,于是也需要通过平台去监控资金损失(也包括由于小数位的四舍五入,国际版支付宝的汇率换算造成的资损),和支付宝风控服务一样实时监控整个交易链路中的资金流,实时统计各种原因造成的资金损失。
(五)并发资源锁控制:一锁二查三更新
对于运营活动,尤其是双11活动,由于瞬间的高并发,对于公共资源的读写会出现并发问题,导致数据资源状态不正确的情况发生,比如下图中对于数据库中某金额字段的操作,由于判断语句和update语句不为一个原语,高并发情况下存在请求乱序的状态到账数据错误:
int money=select();
If(money>=sale)
{update(money-sale);
}
以上的代码中,如果两个请求同时到达int money=select();
后,那么其取得的金额数据全都是30,最后就会造成30-29-25=5的情况(减29的请求先操作完成)。
而避免此类事情发生的方法是采用数据库事务锁的方式,原则为:一锁、二查、三更新,以上程序更改为:
lock db;——加数据库锁
If(money=select()>=sale)——查询和判断
{update(money-sale);
}
unlock db;——解除锁
由于此时查询时是在数据库加锁时进行的,因为此时可以保证数据库的独占性,就不会出现并发问题。一般情况下数据库锁(即悲观锁)的效率较低,而乐观锁效率较高,为了更好的讲解,我们在距离时采用悲观锁的模式,真正实施时其实使用的为乐观锁(基于数据版本做数据处理),因为oracle悲观锁和乐观锁的水太深,此处不做详细说明,具体区别和实现敬请百度。
(六)总线模式性能突破:去中心化
目前传统soa架构会存在集团总线的概念,集团总线提供的公共服务极大的方便系统架构,而在高并发的情况下,集团总线却成为系统瓶颈,因此目前支付宝的架构在逐渐去中心话,将集团总线模式转化为点对点对调的模式,这种实行模式多为dubbo与dubbo的再优化模式。点对点对调模式是去中心化的模式,具体实行方法是将每个系统作为单一的服务器进行部署,即每个系统只部署一个服务器单例,然后将此组服务器记为A组,然后再增加相同的一组服务器B、C、D等等,而服务器的通讯只能进行组内通讯。这种去中心话的思想,完全省略了总线模式,而系统资源不足时只需要通过不断的增加更多组服务器即可。
(七)解决瞬间并发:削峰填谷技术
对于运营类活动,尤其双11这种,用户的付款行为在某一时间区间内是不均衡的,在3秒内就可能造成支付量最高峰和最低峰百万级的差别,因此对这种支付请求在短时间内分布不均衡的情况,支付宝采用“削峰填谷”去均衡请求,使得批量请求在各业务链的执行是均衡的,直接避免并发过高到账系统崩溃的情况。
(八)解决关联方并发承受力不足:任务缓存池
在双十一时,最大量的业务是支付,我们发现,有时候支付宝服务可以维持稳定时,银 行的支付记账接口却因为调用量过大拒绝服务,这时候就会采用支付任务缓存次的方法,根据此次银行方接口的稳定性,将支付任务进行排队,逐步完成银行扣款调用,直接减少了银行的压力。而对用户除去不断转圈的菊花以外,还会在等待支付结果较长时对用户提升请稍后查询,而避免用户重复支付,此订单在一定时长内是不可再支付状态。
(九)解决服务资源不足-服务降级机制
在双十一到来的前几个小时,我们会发现支付宝app在各种余额显示上会变为非实时自动的显示,对于芝麻信用、提现等服务则采用延时到账,暂缓服务等处理方式,还有我们看不到的后台风控降级,资损统计系统降级,日常监控降级,其实这都是为了减少app在使用时和后端交互的次数,减少后端压力,也是为了减少部分服务对公共服务中间件的资源占用,这种服务降级机制是减缓服务器压力的一种重要方式。
三、运营活动测试方案。
对于运营活动的测试不同于常规测试,除去业务逻辑正确以外主要关注两个方面的内容:并发正确与性能测试。其中并发测试是由于运营活动在同一时间的业务并发量较大,到账对同一字段的瞬时读写,容易产生并发问题,这种测试可以使用字段并发测试来解决。而对于运营活动所能承受的并发访问数目则要采用性能测试来解决。
(一)字段并发测试
对于运营活动的并发测试,更加关注于对数据库某一公共并发字段的update操作的并发性验证,不建议采用构造大批量瞬时请求的测试方法,而是建议采用debug的方式,只需要两个请求数目即可完成测试,一般的update操作前需要判断是否满足update操作的要求,只需采用debug的方式让两个操作同时处于判断与update请求之间的位置,即可完成字段并发测试。
(二)性能测试
测试方法不同于一般的性能测试,更关注于系统对瞬时高并发的支持情况。即需要构造瞬间高并发的测试场景。同时由于对于这种高并发级别的性能测试,一般测试环节不能满足环境要求(与生产环境毕竟存在差异),所以双11的性能压测是在生产环节做的。
1.不同于一般性能测试的测试模型
双11的压测模型更关注于瞬间并发,以及最重要的环节:购物车与支付,具体测试的水很深,不在此叙述。
2.生产环节压测重要一环:数据库影子模式
为了保证性能测试的数据准确性,支付宝采用生产环节压测的模式,而且一般是在白天用户正常使用系统的时候做,对于数据库脏数据的处理则是在和被测业务的同一个库里做影子表模式,而影子表的生成在框架最底层dao层以及做了抽象,对此不再赘述,需要了解请看我的另外一篇文章《支付宝双11压测模型及压测思路》