秒杀的实现和解决超卖问题(mysql和redis实现)
秒杀的实现和解决超卖问题
关于超卖问题
由于mysql是多线程,而且在磁盘上进行工作,如果两个用户同时下单,二商品库存只有一个,会出现同时下单成功,这时就需要用到MySQL的乐观锁和悲观锁机制,不过秒杀机制还有一个重要的地方就高并发,高并发推荐使用redis这种在内存工作的数据库,不过本片先介绍关于超卖的问题
创建商品表和订单表
创建与表相关实体类
编写mapper接口及实现
public interface GoodsMapper {
/**
* 减掉商品库存——悲观锁
* @return
*/
@Update("UPDATE `databaseset`.`seckill_goods` SET `name` = 'iphone X', `count` = #{goods.count}, `sale` = #{goods.sale}, `version` = 0 WHERE `id` = 1 ;") //for update
int updateGoodsCount(@Param("goods") Goods goods);
/**
* 减掉商品库存——乐观锁
* @return
*/
@Update("UPDATE `databaseset`.`seckill_goods` SET `name` = 'iphone X', `count` = #{goods.count}, `sale` = #{goods.sale}, `version` = #{goods.version}+1 WHERE `id` = #{goods.id} and version = #{updateVersion};")
int updateGoodsCountOptimisticLock(@Param("goods")Goods goods, @Param("updateVersion")int version);
/**
* 查询商品
* @return
*/
@Select("select `id`, `name`, `count`, `sale`, `version` from seckill_goods where id = 1 for update;")
Goods getGoods();
}
public interface OrderMapper {
/**
* 生成订单
* @param name
* @param createTime
* @return
*/
@Insert("INSERT INTO `databaseset`.`seckill_order`(`custname`, `create_time`) VALUES (#{name}, #{createTime});")
int insertOrder(@Param("name") String name, @Param("createTime") String createTime);
}
Service层接口及实现
根据mapper层提供的一些对数据库的操作方法,更进一步实现秒杀功能
悲观锁实现
悲观锁更多的是在数据库操作,当表进行修改时,会把表锁住。直到修改成功
public void seckillPessimi**sm()** throws Exception {
//悲观锁begin
SqlSession sqlSession = sqlSessionFactory.openSession(false);
sqlSession.getConnection().setAutoCommit(false);
//查询库存,如果库存大于0,则继续秒杀逻辑
Goods goods = goodsService.getGoods();
if (null != goods && goods.getCount() <= 0) {
System.out.println(Thread.currentThread().getName() + "悲观锁方式商品卖光了!!!当前时间:" + System.currentTimeMillis());
return;
}
//库存-1,销量+1
Goods goodsForUpdate = new Goods();
goodsForUpdate.setCount(goods.getCount()-1);
goodsForUpdate.setSale(goods.getSale()+1);
goodsForUpdate.setId(1);
int i = goodsService.updateGoodsCount(goodsForUpdate);
//当库存更新成功后创建订单
if(1>0){
//创建订单
String time = System.currentTimeMillis()+"";
String custname = "zhangsan"+time.substring(8,time.length());
String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
insertOrder(custname,createTime);
}
sqlSession.getConnection().commit();
}
乐观锁的实现
乐观锁更多是在表中多加一个字段,一般叫version,当表进行修改时,会提取version,提交修改时,会再次提取version值与之前version做标记,如果不一样则修改失败
public void seckillOptimistic() {
//查询库存,如果库存大于0,则继续秒杀逻辑
Goods goods = goodsService.getGoods();
if (null != goods && goods.getCount() <= 0) {
System.out.println(Thread.currentThread().getName() + "乐观锁方式商品卖光了!!!当前时间:" + System.currentTimeMillis());
return;
}
int currentVersion = goods.getVersion();
Goods goodsForUpdate = new Goods();
goodsForUpdate.setVersion(currentVersion);
goodsForUpdate.setCount(goods.getCount()-1);
goodsForUpdate.setSale(goods.getSale()+1);
goodsForUpdate.setId(1);
int i = goodsService.updateGoodsCountOptimisticLock(goodsForUpdate,currentVersion);
//当库存更新成功后创建订单
if(1>0){
String time = System.currentTimeMillis()+"";
String custname = "zhangsan"+time.substring(8,time.length());
String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
insertOrder(custname,createTime);
}
}
乐观锁失败后的重试
public int seckillWithOptimistic() {
//查询库存,如果库存大于0,则继续秒杀逻辑
Goods goods = goodsService.getGoods();
if (null != goods && goods.getCount() <= 0) {
System.out.println(Thread.currentThread().getName() + "乐观锁方式商品卖光了!!!当前时间:" + System.currentTimeMillis());
return -1;
}
int currentVersion = goods.getVersion();
Goods goodsForUpdate = new Goods();
goodsForUpdate.setVersion(currentVersion);
goodsForUpdate.setCount(goods.getCount()-1);
goodsForUpdate.setSale(goods.getSale()+1);
goodsForUpdate.setId(1);
int i = goodsService.updateGoodsCountOptimisticLock(goodsForUpdate,currentVersion);
//当库存更新成功后创建订单
if(1>0){
String time = System.currentTimeMillis()+"";
String custname = "zhangsan"+time.substring(8,time.length());
String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
insertOrder(custname,createTime);
return 1;
}else{ //乐观锁如何重试呢?
return 0;
}
}
使用redis进行秒杀
public void seckillwithRedis() {
String key = "seckill"; //定义一个key,key的值就是商品的数量
long count = stringRedisTemplate.opsForValue().increment(key,-1l);
if(count >=0 ){
//创建订单
String time = System.currentTimeMillis()+"";
String name = "zhangsan"+time.substring(8,time.length());
String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
insertOrder(name,createTime);
}else{
System.out.println("卖光了"+System.currentTimeMillis());
}
}