一个秒杀的业务如下:
public Result execMiaoSha(Long goodsId,User user){
//判断user是否秒杀过goodsId的商品
if(redisDao.get(Prefix.goodsAndUser+goodsId+user.getId)!=null){
throw GlobalException("不可以重复秒杀");
}
//获取商品信息
Goods goods = redisDao.get(Prefix.goods+goodsId);//获取json串,通过fastJson转化为一个Goods对象
if(goods == null){
goods = goodsDao.getGoodsById(goodsId);
if(goods == null){
throw GlobalException("商品不存在");
}
}
//TO执行:1.更新goods数据 2.插入order数据
goods.setGoodsStock(goods.stock-1);
Order order = getOderByUserAndGoods(goods,user);
goodsDao.updateGoods(goods);
orderDao.insert(order);
//信息缓存在redis
redisDao.set(Prefix.goods+goodsId,bean2Json(goods));
redisDao.set(Prefix.goodsAndUser+goodsId+user.getId,order);
return Result.success("秒杀成功");
}
本文针对这四行核心代码做出优化:
//TO执行:1.更新goods数据 2.插入order数据
goods.setGoodsStock(goods.stock-1);
Order order = getOderByUserAndGoods(goods,user);
goodsDao.updateGoods(goods);
orderDao.insert(order);
原始方案:将这四行代码放入一个方法中,并且声明成为一个事务。
@Transactional
public void execDB(){
try {
//TO执行:1.更新goods数据 2.插入order数据
goods.setGoodsStock(goods.stock - 1);
Order order = getOderByUserAndGoods(goods, user);
goodsDao.updateGoods(goods);
orderDao.insert(order);
}catch (Exception e){
throw GlobalException("秒杀数据失败");
}
}
优化一:更换语序
首先是在更新操作的时候给行加锁,插入并不会加锁,如果更新操作在前,那么就需要执行完更新和插入以后事务提交或回滚才释放锁。而如果插入在前,更新在后,那么只有在更新时才会加行锁,之后在更新完以后事务提交或回滚释放锁。
@Transactional
public void execDB(){
try {
//TO执行:1.更新goods数据 2.插入order数据
goods.setGoodsStock(goods.stock - 1);
Order order = getOderByUserAndGoods(goods, user);
orderDao.insert(order);
goodsDao.updateGoods(goods);
}catch (Exception e){
throw GlobalException("秒杀数据失败");
}
}
优化二:存储过程
优点:用存储过程这两个操作,一次网络io,执行两天命令。
缺点:数据库提供业务运算,数据库开销变大
优化三:
可以换一种思路:miaosha是一个拼接的sql,相当于一个请求执行两天sql语句,一定层度上将逻辑相关的代码交给spring,mysql只处理sql(具体做法可以百度:mybatis一次请求执行多条语句)
@Transactional
public void execDB(){
try {
//TO执行:1.更新goods数据 2.插入order数据
goods.setGoodsStock(goods.stock - 1);
Order order = getOderByUserAndGoods(goods, user);
orderGoodsDao.miaosha(order,goods);
}catch (Exception e){
throw GlobalException("秒杀数据失败");
}
}
优化四(kafka解耦):
用一个消息中间件解耦,下单以后发送一个,然后写一个kafkaListen监听数据接收,进行秒杀,调用orderGoodsDao.miaosha(order,goods)。
@Transactional
public void execDB(){
try {
//TO执行:1.更新goods数据 2.插入order数据
goods.setGoodsStock(goods.stock - 1);
Order order = getOderByUserAndGoods(goods, user);
kafkaServer.send(Json.fromBean2Json(order)+":"+Json.fromBean2Json(goods.getId()));
}catch (Exception e){
throw GlobalException("秒杀数据失败");
}
}