通用券抢券的优化过程.

来源:互联网 发布:淘宝管制刀具定义 编辑:程序博客网 时间:2024/05/15 16:58

目前运销平台的券分为通用券和非通用券.

通用券就是说,每张券可以重复使用. 非通用券就是说只能使用一次的券.

举个非通用券的例子:

给了100张券,没张券使用1次. 一个订单可以有1~9个乘机人,那么分配给订单时,就给订单从10张中抽取(1~9)张券就好了.这个很好理解.

举个通用券的例子:

给了9种通用券,每种券可以使用100次,那么一个带有5个乘机人的订单来了,那么应该从9种券里取5种券返回给订单.同时,把这5种券的库存-1.

本次优化的主要是通用券.

目前取券的逻辑是: 

接到订单请求后,去数据库里查询可用的通用券,查询sql如下:

SELECT * FROM activity_common_voucher a1  WHERE reuse_count>=1 AND DATEDIFF(publish_date, NOW())=0 AND act_id = #actId# ORDER BY reuse_count DESC LIMIT #size#

上面的reuse_count就是通用券的库存.

activity_common_voucher 表的索引如下:

primary:id

act_publish_idx: act_id,publish_date,voucher_code


上面sql的执行计划如下:



type=ALL 是全表扫描. 比较糟糕.

上面的语句如果不排序呢?像下面这样的语句:

SELECT * FROM activity_common_voucher a1  WHERE reuse_count>=1 AND DATEDIFF(publish_date, NOW())=0 AND act_id = #actId# LIMIT #size#

它的执行计划如下:


type=ref 走索引了,情况有些好转,索引act_publish_idx生效了.

能不能去掉order by reuse_count desc这句话呢,不能。因为会引起其他新的问题。

比如: 现在有9张非通用券,每张库存100次,每次取券按上面的sql取券,极端情况下,来了100个订单,每个订单1个乘机人,那么就会出现第一种券的库存为0了,后面8种库存还是满的.

下次有个订单需要9个乘机人,必然这个订单提交不成功了.

也就是说在分配通用券时,需要考虑券均衡的问题. 上面的order by reuse_count desc 字段就是解决这个问题的.

能不能为reuse_count字段建索引呢.让order by reuse_count desc也走索引. 这样是能减轻查询的负担.

但每次订单提交时,会扣减库存,让这个索引维护成本会增大,可能会影响插入效率.

能不能考虑查询出这些结果,在内存中进行排序呢? 

我做了一层内存中的cache, 并且在内存中维护了一份每个通用券的库存.

cache的大致思路是这样的:   

//  <活动id,List<pair<券id,券库存>>

map<long,List<pair<long,AtomicInteger>>> indexCount;

 // map<活动id,pair<上次load时间,list<Pair<券id,券DO>>>>
 Map<Long,Pair<Long,List<Pair<Long,ActivityVoucherDO>>>> voucherMap;

cache里就这2个数据结构.

当然,map是CopyOnWriteArrayList,List是CopyOnWriteArrayList。都是线程安全的.

上面的结构设计是想达到这样几个目的:  

1, 我可以缓存券DO

2, 我可以在单个节点上维护库存.

3, 我不想自己处理扣减库存带来的并发问题.用了线程安全的AtomicInteger

4, 我可以定时同步数据库的库存到内存(因为在多节点上,每个内存中的库存和数据库中真实的库存是有差异的).


这样一来,抢券流程就变成了, 下单接口请求->查看内存库存->返回券号->根据券号扣减库存->返回券号给订单. 少了一次数据库操作.

看上去效率提高不少.来一轮压力测试吧。并发20个用户.


优化前结果:



tps:  63.9  rt: 297ms

优化后结果:



tps: 38.9  rt:  522ms

are you kidding me ?? 优化后竟然比优化前吞吐量小,并且rt还长!!!!


冷静下来仔细分析发现, 每次同步数据库库存前,内存中的通用券库存是不会再排序的,虽然同步数据库库存的时间很短(目前是5秒),但这5秒内,所有的并发线程都分配了相同的券,在mysql Innodb行锁的情况下,所有的线程都去竞争的扣减同一优惠券的库存。而未进行优化的每次每个线程都分配到不同的券.自然竞争小了.

也就是说, 内存中保留的券和库存扣减节省的开销+相同券扣减库存的开销  >  load一次db+不同券扣减库存的开销.

如果避免内存中分配的时候都分配同一种券呢,让它均衡一点呢. 加个内存排序任务,目前是每300毫秒运行一次.

再来一轮压力测试.


这会儿 rt已经提高到:  65.9了,  rt已经降到142ms了.

再看服务器load,才到1,加大并发量呢. 50个并发来一发:

优化前:


TPS:  45.8  RT: 1076MS

优化后:


TPS:  69   RT: 630ms

显然,无优化的在并发量飙高的情况下性能下降得厉害。

优化后的比较RT飙高,但TPS还比较稳定.


总结:  

1

























0 0
原创粉丝点击