秒杀项目面试点

mysql版本

  • mysql版本采用的是 8.0.16,它与5.6版本相比,默认的存储引擎改为InnoDB引擎,而且它的增删查改性能相比原先的版本也有较大的提升。

数据库表

(密码需要跟用户的主表信息分开存取)

  • 用户信息表:id、名字、性别、年龄、手机号等用户基本信息。
  • 用户密码表:id、密码(以密文的方式存储在数据库里,不能以明文的方式)、use_id 作为外键关联到前面的用户信息表上。

单机版redis

  • 因为单机版redis与cluster集群相比,仅仅存在水平扩展的容量问题,而由于这个项目是自己学习所用,数据并不多,所以不存在容量问题,于是采用的单机版redis了。

redis缓存商品详情信息

  1. 通过id得到商品详情信息。
  2. 如果redis中不存在这个商品详情信息,就将它存入redis中进行缓存(失效时间设置的是十分钟):RedisTemplate.opsForValue().set(“item_”+id , itemInfo); redisTemplate.expire(“item_”+id , 10 , TimeUnit.MINUTES);
    (要注意itemInfo类需要实现 Serializable 接口)

分布式扩展的Jmeter压测情况

  • 分布式扩展:采用1000个线程(循环20次)进行压测,扩展前平均耗时500毫秒左右,tps在1500左右,cpu占用率80%左右。扩展后,mysql服务器的cpu占用率降到了10%左右,解决了单机性能瓶颈的问题。(cpu占用率采用Linux的top命令查看)
  • redis缓存商品详情信息:缓存优化后,平均耗时缩短到200ms左右,tps达到了2000。减轻了数据库服务器的压力,减少了对数据库磁盘的访问。

Redis缓存库存提高交易性能

  • 缓存优化前,采用1000个线程(循环20次)进行压测,平均耗时有2000毫秒,也就是快2秒多了,tps在400左右,cpu占用10%左右,相比查询而言,交易过程做的一些数据库IO操作比较耗时,400的tps却对应2秒的耗时,显然是不妥当的。
  • 缓存优化后,平均耗时降到了600毫秒左右,tps达到了1200多,优化方案产生了明显的效果。
  • 交易性能瓶颈的原因:程序中当用户进行下单操作时,首先会有一个对于下单商品是否存在的校验,它会对数据库发送sql来获取活动商品的信息,还有一个对于用户是否合法的校验,还有一个对商品活动的校验,也就是校验活动中是否存在该商品以及校验活动是否正在进行中,这些校验完成后还要进行一个落单的减库存操作,这个操作在秒杀场景中将会是一个热点操作,具体的sql语句是 where item_id = #{item_id},这里有一个对数据库的行锁,落单减库存成功后就进行订单入库,生成订单流水号,并将流水号插入到数据库中,还要向数据库中插入商品的销量,总共加起来对数据库产生了六次IO操作。 交易性能瓶颈的总结:首先它对于交易的验证完全依赖于数据库,也就是完全通过sql形式发送给数据库来做读操作进行验证;还有一个库存行锁等待的问题,所有减库存的操作都是串行进行的。
  • item_id上有一个唯一索引,加唯一索引的方式:alter table stock_table add unique index item_id_index(item_id)
优化交易性能:
  1. 将用户信息和活动商品的信息存到redis缓存中,在redis缓存中来对它们进行校验,至于活动的属性,可以将活动的开始时间和结束时间存到redis缓存中,但活动的数据是可以修改的,比如运营发现活动时间配置错误或者这个活动需要提前开始,然后运营修改了数据库中的活动属性,但redis缓存中的活动属性并没有正常过期,这就会导致用户仍然可以以活动商品的秒杀价格来进行交易,因此需要有一个紧急下线的功能,在后台提供一个给运营使用的将活动紧急下线的接口,在这个接口里我们可以手动的通过代码的方式清除掉redis缓存。
  2. redisTemplate.opsForValue().set(“item_” + id , itemInfo);
    redisTemplate.expire(“item_” + id , 10 , TimeUnit.MINUTES);(十分钟)

项目交易的执行流程

  1. 开始交易,获取活动商品信息和用户信息。
  2. 校验商品是否存在,校验用户是否合法,校验活动中是否存在该商品,校验活动是否正在进行。
  3. 扣减库存,库存不足则结束交易。
  4. 扣减成功则进行订单入库,生成订单流水号。
  5. 最后数据库中插入商品的销量,交易完成。(itemService.increaseSales(itemId , amount))

库存扣减行锁的优化

  • 优化前,是通过mysql的sql语句进行库存的扣减:set stock = stock - #{amount} where item_id = #{item_id} and stock >= #{amount}。这里在表中给 item_id 加了一个唯一索引,数据库会在 item_id = #{item_id} 这个地方加一个数据库的行锁,将 item_id 对应的商品串行化的减库存,这里会成为数据库性能的瓶颈。
  • 库存行锁的优化方案是,将扣减库存的操作做到redis中,也就是将库存同步到缓存中,下单交易的时候就减缓存中的库存。redis中key是活动商品id,value是对应的库存,减库存操作的实现方式是:redisTemplate.opsForValue().increment(活动商品id,amount*-1)。得到的库存扣减后的结果如果大于等于0,则更新库存成功,return true;如果小于0,就表示库存扣成负的了,就卖不了,所以 return false,表示库存扣减失败。在活动开始的时候,自动上架活动的商品,活动没有开始时,商品是下架状态。
  • 接着采用异步消息队列的方式来扣减数据库中的库存,这样的话既能保证C端用户通过redis完成一次高效的购买体验,又能通过异步扣减的操作来保证redis和数据库中库存数据的最终一致性。(如果失败,就把redis中扣减的库存加回来)
  • (其中,在数据库的减库存方法上加了一个Transactional注解,来保证减库存和创建订单的操作要么同时成功,要么同时失败)
事务消息
  • 首先生产者在消息队列上开启一个异步发送事务型消息的操作。这样会构建了一个map结构,key是商品id,value是库存扣减的数量,然后通过这个map构建出对应的消息,并投递到消息队列中,此时这个消息是一种半消息的状态,也就是对消费端是不可见的;接着真正要做的是创建订单和redis扣减库存的操作,如果执行成功,刚才的半消息就可以被消费者消费了,执行失败就进行回滚。
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值