关于高并发支付、秒杀的一些设计思路

于高并发支付、秒杀的一些设计思路

 

一、问题描述

高并发支付和秒杀的场景有很多,Amazon黑色星期五、天猫双11促销、京东618等情况都是如此。假如Amazon某件低价商品只有100个,库存也就是这100份,不会再变了。在00:00秒杀开抢的那一刻,会涌入非常多的流量进程查询(Read)和下单支付(Write)操作。这就是一个非常典型的秒杀支付场景。

 

由于支付业务开发中,和钱相关的业务是非常重要的,不能出现任何差错,所以需要依赖关系型数据库例如mysql的事务和锁的机制来保证数据的一致性。因此,对于秒杀支付的场景中,瞬时流量激增,并且都是读相同的数据,update相同的数据。造成mysql读写冲突,锁严重,各种的锁等待,锁fail等情况。这就是我们在支付秒杀业务中遇到的实际案例。

下文总结一下优化秒杀支付业务的设计思路与方向。

 

二、优化设计方向

1. 开源节流

2. 引入分布式缓存

3. 引入MQ请求队列与异步操作

4. 引入悲观锁状态标记,解决分布式下的数据一致性

5. 业务设计的合理性

    关于开源节流,目的是尽可能多的拦截的请求,减少mysql层的锁冲突。因为一旦mysql锁冲突多了,mysql响应变慢,但是并发量依然很高,几乎所有请求全部timeout,那么真正支付成功的基本没有,那么这个秒杀系统是失败的案例。因此基于以上的想法,我们就需要拦截尽可能多的请求。

    那么如何做到开源节流,需要以下几个方向:

(1) client端,禁止用户出现double click和double payment的情况。限制client在N秒内只能发出一次秒杀请求。

(2) 在后端服务的Controller层,根据请求用户的Account对象的uuid进行计数和排重。保证一个Account对象在N秒内只能发出一次秒杀请求。(这个步骤防止有hacker穿透client层直接http抓包for循环)

(3) 引入页面缓存,同一个Account对象的uuid,限制访问频度,做页面缓存,N秒内的秒杀请求,均返回同一页面。同一个查询,做页面缓存,N秒内到达的请求,均返回同一页面。

 

关于引入分布式缓存,这里通常使用的是主流的redis。在传统的电商系统或是subscription billing&payment系统中,秒杀的情况都是读多写少的情况。场景都是先查询,如果依然有库存,然后发出写请求进行支付操作;如果没有库存或是结果等待查询时间过长,再次刷新查询请求,所以在整个秒杀过程中,大部分情况下写请求占0.001%,读请求占99.999%。因此,在读多写少的情况下,非常适合使用redis。redis单实例最多可以hold住qps 10w的读请求。因此,在秒杀的读请求99.999%全部查redis。如此限流,只有非常少的写请求能够到达Data Center,和非常少的读请求会miss掉穿透到Data Center去,然而,最多的99.999%的读请求被拦住了。


关于引入MQ,作为请求队列。为了增加支付系统的吞吐量,将秒杀支付的过程变成异步过程。可以使用active mq作为请求队列,对于client提交过来的支付请求(Write),做请求队列,只会透过和库存等量的写请求到Data Center层。例如:库存有3000,这里active mq的BET只会透过3000个message去处理。activemq中剩下的message在处理的时候,直接返回“soldout”。如果业务需求中对生成invoice的下单操作和对invoice的payment支付操作可以分离的话,也可以通过active mq将两个操作做成异步。


关于引入悲观锁状态标记,解决分布式下的数据一致性。由于支付过程中,需要RPC访问Payment Gateway Service,无法使用LocalTransaction来保证数据一致性。因此,在最后写请求进行支付的时候,需要在mysql的payment表中设置一个状态为标记payment status,在访问Payment Gateway的过程中将标志位成processing,通过悲观锁将这条记录锁住,直到Payment Gateway的settle成功后,将payment status置成success。在此期间其他的并发的支付请求无法对相关的invoice账单进行支付,保证分布式数据的一致性。


关于业务设计的合理性,对于没有必须要知道库存数量的case,只需要显示是否有库存,所以缓存可以直接存true和false即可。对于12306的火车票的秒杀情况,分时分段售票也是可以均摊流量的方式等等。

 

三、总结


关于高并发支付、秒杀的一些设计思路



Client:做开源节流

Web server(Controller层):Account uuid做limit,做页面缓存

Back End Service:请求队列控制流量,做数据缓存

Data Center:垂直切分global shard,水平切分分流数据,悲观锁保证分布式数据一致性

Business Model:考虑均摊流量

    假如实际情况是1000w的请求秒杀一个3000库存的商品,Client端和Web Server端可以挡住30%的无效请求。Redis hold住所有的查询请求,Back End Service层把写请求发送到AMQ中,BET在消费AMQ的时候只有前3000条消息是可以支付成功的,后面的消息处理会sold out。

    以上只是一个设计的思路,具体的细节还是需要根据具体的业务场景而定,离开业务的讨论技术细节是不可行的。