本篇博文是“Java秒杀系统实战系列文章”的第十三篇,从本篇文章开始咱们将进入“秒杀代码优化”环节,本文将首先从数据库级别Sql的优化入手,结合调整秒杀相关的部分核心代码,实现初步的优化!mysql
上篇文章咱们暴露出了“秒杀接口”在面对高并发请求的场景下所出现的“超卖”、“重复秒杀”等问题,并对相应的问题进行了分析,而后就没有而后了……(事了拂衣去!)git
问题既然落在咱们的手里,那么身为一名程序猿,那是没有理由回避的。经过分析该“秒杀接口”的核心代码,能够发如今数据库层面,其涉及的Sql咱们仍是能够动一动手脚的!其调整后的“秒杀核心业务逻辑”的完整源代码以下所示:算法
//商品秒杀核心业务逻辑的处理-mysql的优化
@Override
public Boolean killItemV2(Integer killId, Integer userId) throws Exception {
Boolean result=false;
//TODO:判断当前用户是否已经抢购过当前商品
if (itemKillSuccessMapper.countByKillUserId(killId,userId) <= 0){
//A 查询待秒杀商品详情
ItemKill itemKill=itemKillMapper.selectByIdV2(killId);
//TODO:判断是否能够被秒杀canKill=1?
if (itemKill!=null && 1==itemKill.getCanKill() && itemKill.getTotal()>0){
//B 扣减库存-减一
int res=itemKillMapper.updateKillItemV2(killId);
//TODO:扣减是否成功?是-生成秒杀成功的订单,同时通知用户秒杀成功的消息
if (res>0){
commonRecordKillSuccessInfo(itemKill,userId);
result=true;
}
}
}else{
throw new Exception("您已经抢购过该商品了!");
}
return result;
}复制代码
首先是对于 注释A 那里的调整,即在获取“秒杀商品详情”时,咱们限定了“可秒杀商品的数量total须要大于0”,其对应的代码为:itemKillMapper.updateKillItemV2(killId);完整的动态Sql以下所示:
sql
<!--获取秒杀详情V2-->
<select id="selectByIdV2" resultType="com.debug.kill.model.entity.ItemKill">
SELECT
a.*,
b.name AS itemName,
(CASE WHEN (now() BETWEEN a.start_time AND a.end_time)
THEN 1
ELSE 0
END) AS canKill
FROM item_kill AS a LEFT JOIN item AS b ON b.id = a.item_id
WHERE a.is_active = 1 AND a.id =#{id} AND a.total>0
</select>复制代码
而后是 注释B 对应的优化调整,即在扣减库存时,咱们除了能够保证正常减1的操做以外,还须要保证扣减完以后的数量大于0,即只有在保证扣减完以后的数量大于0之下,该Sql操做后受影响的行数为1,对应的代码为:itemKillMapper.updateKillItemV2(killId); 其对应的动态Sql以下所示:
数据库
<!--抢购商品,剩余数量减一-->
<update id="updateKillItemV2">
UPDATE item_kill
SET total = total - 1
WHERE id = #{killId} AND total>0
</update>复制代码
至此,咱们在秒杀核心业务逻辑的优化层面~数据库级别Sql的优化 已经搞完了!除此以外,咱们还在代码层面进行优化,以下所示:
bash
private void commonRecordKillSuccessInfo(ItemKill kill, Integer userId) throws Exception{
//TODO:记录抢购成功后生成的秒杀订单记录
ItemKillSuccess entity=new ItemKillSuccess();
String orderNo=String.valueOf(snowFlake.nextId());
//entity.setCode(RandomUtil.generateOrderCode()); //传统时间戳+N位随机数
entity.setCode(orderNo); //雪花算法
entity.setItemId(kill.getItemId());
entity.setKillId(kill.getId());
entity.setUserId(userId.toString());
entity.setStatus(SysConstant.OrderStatus.SuccessNotPayed.getCode().byteValue());
entity.setCreateTime(DateTime.now().toDate());
//TODO:学以至用,触类旁通 -> 仿照单例模式的双重检验锁写法
if (itemKillSuccessMapper.countByKillUserId(kill.getId(),userId) <= 0){
int res=itemKillSuccessMapper.insertSelective(entity);
if (res>0){
//TODO:进行异步邮件消息的通知=rabbitmq+mail
rabbitSenderService.sendKillSuccessEmailMsg(orderNo);
//TODO:入死信队列,用于 “失效” 超过指定的TTL时间时仍然未支付的订单
rabbitSenderService.sendKillSuccessOrderExpireMsg(orderNo);
}
}
}复制代码
即咱们在“用户秒杀成功生成订单记录”的代码加入了相似于“单例模式”中的“双重检验锁”,即在生成订单记录,再次判断一下“当前用户是否已经真的没有抢购过该商品”!微信
在后面的篇章中,咱们将开始搬上“中间件”这一利器,并结合本文所介绍Sql的优化和调整后的代码,完全解决高并发压力测试的场景下出现的“库存超卖”、“重复秒杀”等乱七八糟的问题!并发
一、目前,这一秒杀系统的总体构建与代码实战已经所有完成了,完整的源代码数据库地址能够来这里下载:gitee.com/steadyjack/… 记得Fork跟Star啊!!app
二、最后,不要忘记了关注一下Debug的技术微信公众号:dom