本文主要是利用springboot,实现一个单机版秒杀demo,通过单机版实现,可以对基本并发秒杀的知识有一定的了解。

首先先提供秒杀业务实现类:


/*** spring 注解加在实现类*/
@Service
@Transactional
public class OrderServiceImpl implements OrderService {@Autowiredprivate StockMapper stockMapper;@Autowiredprivate StockOrderMapper orderMapper;@Autowiredprivate StringRedisTemplate redisTemplate;//方法用synchronized修饰,单机应用增加悲观锁//注意,与@Transactional一起使用是,不会生效,如要要使用的话在调用该方法的地方使用synchronized代码块//原因:Transactional事务是在锁之前开始的,事务范围广,当一个线程锁释放了,但是事务还没提交,当下个线程过来是,一起提交上一次事务//一般不建议使用,线程会单个使用,降低效率,并且不要在业务代码增加synchronized@Overridepublic  int kill(Integer id) {//加入redis缓存限时抢购,即使获得令牌如果不在活动时间内也是无法抢购//校验redis中秒杀商品是否结束if (!redisTemplate.hasKey("kill"+id)) {throw new RuntimeException("当前商品的抢购活动已经结束啦!");}//校验库存StockDO stockDO = checkStock(id);//扣减库存updateStock(stockDO);//创建订单return createOrder(stockDO);/*      //根据id校验库存StockDO stockDO = stockMapper.checkStock(id);if(stockDO.getSale().equals(stockDO.getCount())){throw new RuntimeException("库存不足。。。");}else {//扣减库存stockDO.setSale(stockDO.getSale() + 1);stockMapper.update(stockDO);//创建订单StockOrderDO stockOrderDO = new StockOrderDO();stockOrderDO.setSid(id).setName(stockDO.getName()).setCreateTime(new Date());orderMapper.insert(stockOrderDO);return stockOrderDO.getId();}*/}private StockDO checkStock(Integer id){StockDO stockDO = stockMapper.checkStock(id);if(stockDO.getSale().equals(stockDO.getCount())) {throw new RuntimeException("库存不足。。。");}return  stockDO;}private void updateStock(StockDO stockDO){//使用乐观锁//在sql完成销量+1 和版本号的+1,并且根据商品id和版本号同时查询更新商品//使用版本号,数据库不支持并发写int updateRows = stockMapper.update(stockDO);if(updateRows == 0){throw new RuntimeException("抢购失败,请重试!!!");}}private Integer createOrder(StockDO stockDO){StockOrderDO stockOrderDO = new StockOrderDO();stockOrderDO.setSid(stockDO.getId()).setName(stockDO.getName()).setCreateTime(new Date());orderMapper.insert(stockOrderDO);return stockOrderDO.getId();}
}

1.使用synchronized关键字悲观锁,防止超卖,使用悲观锁的话,对资源浪费比较大,每一次只允许一个线程访问,降低效率,其他的只能等待,显示是不合理的。不过如果真的要是用synchronized,不要在业务代码使用,必须在调用业务代码的地方使用同同步代码块,原因如下:

业务代码使用了@Transactional注解,一起使用是不会生效,因为Transactional事务是在锁之前开始的,事务范围广,当一个线程锁释放了,但是事务还没提交,当下个线程过来是,一起提交上一次事务。
   @GetMapping("/kill")public  String kill(Integer id){System.out.println("秒杀商品id = "+id);try {//根据秒杀商品id,去调秒杀业务//调用处增加synchronized悲观锁//先加锁,然后在开启事物,可以保证安全性。synchronized(this){int orderId = orderService.kill(id);return "秒杀成功,订单id:" + orderId;}}catch (Exception e){e.printStackTrace();return e.getMessage();}}

2.数据库层面version版本号,乐观锁防止超卖,利用数据库不支持并发写,每一次只允许一个线程操作。数据增加version字段,每次修改都更加version去修改,并且数据增加销售量。

  <update id="update">update stockset sale = sale + 1,version = version +1where id = #{id} and version =#{version}</update>

使用测试发现利用乐观锁进行控制商品秒杀,同样的并发量,明显效率比较高,时间也是缩短一半左右。

3.Google guava RateLimiter令牌桶算法接口限流,在利用乐观锁的实现超卖的前提下进行限流,因为是接口限流所以是在前端调用的时候进行限制。

这边插入一下限流算法的概念:

限流:是对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或者宕机接口限流:在面临高并发的抢购请求时,我们如果不对接口进行限流,可能会对后台系统造成极大的压力,大量的请求抢购成功时需要调用下单接口,过多的请求达到数据库时会对系统的稳定性造成影响常用限流算法:令牌桶算法、漏斗算法(用的少),Google开源项目Guava中的RateLimiter使用的就是令牌桶控制的算法。在实际开发高并发系统时有三把利器:缓存、降级、限流缓存:缓存的目的是提升系统访问量速度和增大系统处理容量降级:当服务压力剧增的情况下,根据当前业务情况及流量对一些服务和页面的降级(springcloud的hystrix),以此释放服务器资源以保证核心任务的正常运行限流:目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制的速率则可以拒绝服务、排队或者等待、降级处理漏斗算法(简单粗暴):漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率令牌桶算法:令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.设置超时时间,超过时间自己抛弃.

代码实现:

  //创建令牌桶实例private RateLimiter rateLimter = RateLimiter.create(10);/*** 秒杀方法* @param id* @return*/@GetMapping("/killToken")public  String killToken(Integer id){//System.out.println("秒杀商品id = "+id);//秒杀前,执行限流操作,获得到了令牌才执行抢购//@return {@code true} if the permit was acquired, {@code false} otherwise//如果超过等待时间if (!rateLimter.tryAcquire(1,TimeUnit.SECONDS)){log.error("当前请求被限流,直接抛弃,抢购失败!!,当前活动过于火爆,请重试");return "抢购失败!!,当前活动过于火爆,请重试";}try {//根据秒杀商品id,去调秒杀业务//调用处增加synchronized悲观锁//先加锁,然后在开启事物,可以保证安全性。// synchronized(this){int orderId = orderService.kill(id);log.info("秒杀商品id = "+id);return "秒杀成功,订单id:" + orderId;//  }}catch (Exception e){e.printStackTrace();return e.getMessage();}}

4.Redis缓存抢购时间,主要是为了缓解数据压力,利用缓存在调用数据库前,判断是否秒杀活动结束了,并且秒杀的话存在的时间也不是很长,如果存在才进行数据库操作,所以即使获得的秒杀资格但是活动结束的话也是抢购失败,主要是在业务层进行控制。即数据存一个秒杀key,设置秒杀时间比如:set kill1 EX 180 表示秒杀key存在180秒

  //加入redis缓存限时抢购,即使获得令牌如果不在活动时间内也是无法抢购//校验redis中秒杀商品是否结束if (!redisTemplate.hasKey("kill"+id)) {throw new RuntimeException("当前商品的抢购活动已经结束啦!");}

5.接口压力测试使用Jmeter

6.代码连接:Gitee 仓库地址,module工程师miaosha,https://gitee.com/ou_qiming/springcloud-demo.git

Springboot秒杀系统(乐观锁+RateLimiter令牌+Redis缓存)相关推荐

  1. springboot 秒杀系统(二)redis

    上一步我们做的秒杀虽然在操作上没问题, 但性能上能有很大的提升空间. 我们可以先把秒杀数据加载到内存中,考虑到以后服务集群化, 所以加载的数据不存放在JVM中,而存在放redis 首先,我们都知道,r ...

  2. SpringBoot秒杀系统

    秒杀系统库存 今天带来一套秒杀库存扣减 涉及到单体架构和集群架构 希望能给你们带来帮助 我也不想学 但是bgs不教 首先讲一下大致的扣减库存的思路 @RequestMapping("dedu ...

  3. SpringBoot + 秒杀系统

    本书源码 https://github.com/huangwenyi10/spring-boot-book-v2 目录 创建 秒杀系统 雏形 小结 详情 高并发优化 流量削峰 创建 解决 Cannot ...

  4. springboot 秒杀系统(一)

    秒杀系统应该是很检验一个人的能力的项目.包括从前端 到运营商到nginx到后端等等,很多地方可以优化. 前端的页面控制,运营商的CDN加速,nginx的动静分离等 下面我来一步一步实现后端的秒杀功能的 ...

  5. SpringBoot使用@Cacheable实现最简单的Redis缓存

    前言 之前我们使用过RedisTemplate来实现redis缓存,然后使用工具类来实现操作redis的存储.这样的方式好处是很自由,但是还不是最简单的处理方式.对于一些简单的应用来说,其实redis ...

  6. springboot 启动加载数据库数据到redis缓存

    启动项目后, 加载数据库公共配置数据到redis中 import org.springframework.data.redis.core.RedisTemplate; import org.sprin ...

  7. 【springboot】mybatis-generator+tkmybatis通用mapper+swagge+redis缓存整合使用

    介绍

  8. 高并发】高并发秒杀系统架构解密,不是所有的秒杀都是秒杀

    前言 很多小伙伴反馈说,高并发专题学了那么久,但是,在真正做项目时,仍然不知道如何下手处理高并发业务场景!甚至很多小伙伴仍然停留在只是简单的提供接口(CRUD)阶段,不知道学习的并发知识如何运用到实际 ...

  9. 实践出真知:全网最强秒杀系统架构解密!!

    很多小伙伴反馈说,高并发专题学了那么久,但是,在真正做项目时,仍然不知道如何下手处理高并发业务场景!甚至很多小伙伴仍然停留在只是简单的提供接口(CRUD)阶段,不知道学习的并发知识如何运用到实际项目中 ...

最新文章

  1. 微信小程序红包开发 小程序发红包 开发过程中遇到的坑 微信小程序红包接口的...
  2. 计算机函数match,秒杀vlookup函数,Index+Match函数组合
  3. android ViewPager之PagerAdapter中View的重用
  4. 云联惠身份认证需要多长时间_欧盟REACH认证需要多长时间【周期、费用、有效期】...
  5. SAP HR工资核算基础(转)
  6. 2022年全国计算机二级Access数据库程序设计模拟试题及答案
  7. python求列表的方差值
  8. 怎么制作合法有效的电子签名?
  9. 2019 告辞了您嘞 ~
  10. 【CS231n】斯坦福大学李飞飞视觉识别课程笔记(四):图像分类笔记(上)
  11. mysql服务重启和关闭
  12. 什么高大填空四个字动人_照样子填空填四字成语什么什么什么地想
  13. 自动化构建:gitlab gitlab-run ,maven的缓存 和 gitea drone drone-run
  14. Dancing Link --- 模板题 HUST 1017 - Exact cover
  15. Activiti学习(一)之工作流的介绍和使用
  16. 普乐蛙5d车载影院5d飞行影院5d轨道影院体验馆
  17. 一花独放不是春,华为与用友共同构建百花齐放生态
  18. 利用word和excel实现中英文互换
  19. MySQL中escape(避开,逃避)关键字的用法
  20. 今年冬天有点冷(2)

热门文章

  1. 图文 线性回归与梯度下降
  2. 重磅!超详细图解Self-Attention!
  3. 【计算机毕业设计】网上球鞋竞拍系统
  4. 推荐算法架构4:重排
  5. gomonkey总结
  6. Elasticsearch启动遇到nofile、nproc、jvm等报错
  7. 解决xmanager连接Centos7无法输入中文
  8. 如何让测试少加班?阿里Mock平台使用方法揭秘!
  9. 超详细 Vagrant 入门指南,建议收藏
  10. Java强制类型转换异常