本文共 2273 字,大约阅读时间需要 7 分钟。
目前运销平台的券分为通用券和非通用券.
通用券就是说,每张券可以重复使用. 非通用券就是说只能使用一次的券.
举个非通用券的例子:
给了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