一.简单的service逻辑:
1.查询kill库存
2.根据库存判断是否秒杀成功
3.成功则生成订单
@Override
@Transactional
public int kill(Integer id, Integer gnumber, int uid) {
//先查询kill库存
Kill kill = killDao.queryKillById(id);
//库存不足直接返回0
if(kill.getSave() <= 0){
return 0;
}
//库存足够
//减库存
killDao.updateSave(id,gnumber);
//插入订单
Orders orders = new Orders();
orders.setOrdertime(new Date());
orders.setOrderid(UUID.randomUUID().toString());
orders.setUid(uid);
orderDao.insertOrder(orders);
return 1;
}
秒杀一个高并发和高速读写的场景,我们并不能简单的操作数据库
1.解决数据一致性,因为高并发多线程访问产生的超买情况
2.解决数据库的高速读写问题,减小数据的负载
我们直接在方法上添加synchronized,使用同步代码块,或者互斥锁,也就是简单的给业务层的共享资源上锁
我们在查询秒杀库存的时候添加for update,也就是我们的排它锁,一旦我们查询了该条数据,其他事务不可以添加任何锁,也就是我们在对库存做操作的时候只要事务没有提交一定可以保证其他事务无法访问该数据
每次查询库存的时候携带版本号,然后每次扣减库存的时候判断版本号是否一致,一旦不一致就不允许提交
update t_kill
set save = save - #{gnumber}
where id = #{id} and version = #{version}
也是乐观锁的思想,只不过我们不需要维护version字段,只要我们在扣减库存的时候对save字段进行判断即可,因为insert,delete,update是默认自带排他锁的,我们可以保证在执行的时候该数据其他事务无法访问
update t_kill
set save = save - #{gnumber}
where id = #{id} and save >= gnumber
将数据库的数据先存放在redis中,然后我们直接对redis进行脚本操作,将需要添加的订单信息也存放到redis中然后判断save<=0在将缓存中的数据添加回数据库中
--key=kill+id
local id = KEYS[1]
--需要购买的商品数量
local number = tonumber(ARGV[1])
--获得库存
local save = tonumber(redis.call('get','kill'..id))
if save == null or save <= 0 then
--购买失败 库存不足或者没有该商品
return 2
end
--此时库存的扣减
save = save - number
--设置库存回到缓存
redis.call('set','kill'..id),save)
--缓存订单数据
redis.call('rpush','order'..id,ARGV[2])
-- 秒杀成功 返回结果
if save == 0 then
-- 0 : 表示最后一次,秒杀结束
--此时需要将缓存中的数据写回数据库
return 0
else
return 1
end
@Autowired
private RedisTemplate redisTemplate;
private RedisConnection connection;
//lua脚本的位置
private String luaPath;
private String luaKey;
@PostConstruct
public void init(){
connection = redisTemplate.getConnectionFactory().getConnection();
luaPath = this.getClass().getResource("/static/lua/kill.lua").getPath();
try {
luaKey = connection.scriptLoad(FileUtils.readFileToByteArray(new File(luaPath)));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
@Transactional
public int kill(Integer id, Integer gnumber, int uid) {
Orders orders = new Orders();
orders.setOrdertime(new Date());
orders.setOrderid(UUID.randomUUID().toString());
orders.setUid(uid);
//执行缓存的lua脚本
Long result = connection.evalSha(luaKey, ReturnType.INTEGER, 1,
(id + "").getBytes(),
(gnumber + "").getBytes(),
new Gson().toJson(orders).getBytes());
if(result == 0){
//秒杀结束
//为了避免最后一个用户等待一个将数据写回数据库的时间,我们使用异步的service方法进行写回
saveReturnData(id);
}
return result.intValue();//0,1成功 2失败
}
@Override
@Async
@Transactional
public void saveReturnData(Integer id) {
//通过id获得数据
Long size = redisTemplate.opsForList().size("order" + id);
List ordersStringList = redisTemplate.opsForList().range("order" + id, 0, size);
List ordersList = new ArrayList<>();
for (int i = 0; i < ordersStringList.size(); i++) {
Orders orders = new Gson().fromJson(ordersStringList.get(i), Orders.class);
ordersList.add(orders);
}
//批量插入订单
orderDao.insertOrderList(ordersList);
//将库存设为0
killDao.updateSave(id,0);
//删除redis中的数据
redisTemplate.delete("kill" + id);
redisTemplate.delete("order" + id);
}