使用技术栈:Springboot+mybatis-plus+redis

本文介绍了秒杀案例入门级的案例,欢迎各位大佬观看!

1.配置环境

1.导入依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--引入令牌桶算法依赖--><!-- https://mvnrepository.com/artifact/com.google.guava/guava --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>27.1-jre</version></dependency><!--引入mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus</artifactId><version>3.4.1</version></dependency><!--引入mysql依赖--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>

2.配置配置文件

# tomcat端口号
server:port: XXXX
spring:# 配置mysql连接datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://XXXXXXusername: XXXXpassword: XXXX# 配置redisredis:host: XXXXport: XXXXpassword:
# 开启mybatis-plus日志
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志

3.配置Druid配置类

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @Author XXX* @Date 2022/1/3 16:48* @Version 1.0*/
@Configuration
public class DruidConfig {@ConfigurationProperties("spring.datasource")@Beanpublic DruidDataSource druidDataSource(){return new DruidDataSource();}
}

4.编写mapper文件进行测试

@Repository
public interface StockMapper extends BaseMapper<Stock> {Stock selectAll();
}

mapper.xml

<select id="selectAll" resultType="cn.hmc.springbootms.pojo.Stock">select * from stock;
</select>

测试类:

@SpringBootTest
class Springbootms2ApplicationTests {@Autowiredprivate StockMapper stockMapper;@Autowiredprivate StringRedisTemplate redisTemplate;//测试使用mapper配置文件连接数据库@Testvoid contextLoads() {Stock stock = stockMapper.selectAll();System.out.println(stock);}//测试连接redis@Testvoid testReadid(){redisTemplate.opsForValue().set("a","123");System.out.println(redisTemplate.opsForValue().get("a"));}
}

2.防止超卖

1.使用悲观锁

在controller增加同步代码块

/**** 秒杀,使用悲观锁解决* @param userId* @param stockId* @return*/
@GetMapping("/kill")
public  String kill(Integer userId,Integer stockId) {Integer kill = 0;synchronized (this){kill = stockService.kill(userId, stockId);}return "恭喜用户["+userId+"]秒杀成功!订单号为["+kill+"]!";
}

2.使用乐观锁

需要根据version字段进行更新

<update id="updateStock">update stock setsale = #{sale} , version = #{version} + 1whereid = #{id} and version = #{version};
</update>

service

@Override
public Integer killCAS(Integer userId, Integer stockId) {Stock check = check(userId, stockId);//将出售量+1check.setSale(check.getSale()+1);//在sql层次将version+1,带着version作为条件进行修改int update = stockMapper.updateStock(check);if (update==0){throw new StockException("抢票失败,请重试!");}//创建商品订单Integer orderId = saveOrder(check.getId(), check.getName());return orderId;
}

conreoller

/*** 秒杀,使用乐观锁解决并发问题* @param userId* @param stockId* @return*/
@GetMapping("/killCAS")
public String killCAS(Integer userId,Integer stockId){Integer kill = stockService.killCAS(userId, stockId);return retrue(userId,kill);
}

总结:

  • 悲观锁与乐观锁都能解决并发问题;

    • 悲观锁的优点:

      • 可以保证商品不会少卖。

    • 悲观锁的缺点:

      • 每次请求都上了一把锁,效率比较低。

    • 乐观锁的优点:

      • 出问题才不允许操作,效率比悲观锁高。

    • 乐观锁的缺点:

      • 请求量少的情况下会出现商品剩余问题。

3.使用redis

利用redis的事务,以及原子性保证秒杀不会出错

3.接口限流

使用令牌桶,限制用户一次性访问的请求。

还可以指定超过请求时间返回连接超时。

1.导入依赖

<!--导入令牌桶依赖-->
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1.1-jre</version>
</dependency>

2.测试

//使用令牌桶创建实例                     桶大小private RateLimiter rateLimiter = RateLimiter.create(40);@GetMapping("/killtoken")public String killtoken(Integer userId,Integer stockId){//1.没有获取到token请求就一直等待,直到获取到token令牌
//        double acquire = rateLimiter.acquire();
//        System.out.println("开始秒杀,等待时间:"+acquire);//2.设置等待时间,如果等待时间内没有获取令牌就丢弃if (!rateLimiter.tryAcquire(3, TimeUnit.SECONDS)){System.out.println("请求超时!请重新连接!");return "请求超时!请重新连接!";}//模拟处理业务System.out.println("处理业务中...");return retrue(userId,stockId);}

4.隐藏秒杀接口

使用redis解决

  • 限时抢购

  • 接口隐藏

  • 单用户限制

1.限时抢购

在redis中加入商品id,并指定过期时间。

在java业务代码中判断该商品id是否存在,存在就进行秒杀, 不存在就提示错误信息!

1.在redis 中加入,并设置过期时间

set KILL_[商品id]_TIME EX [过期时间,单位秒]

2.java代码

 @Autowiredprivate StringRedisTemplate redisTemplate;// 判断商品是否过期private void pastDue(Integer stockId){String key = "KILL_"+stockId+"_TIME";String s = redisTemplate.opsForValue().get(key);if (s==null){throw new StockException("秒杀时间已过,请关注下一次秒杀时间!");}}@Overridepublic Stock check(Integer userId, Integer stockId) {//1、判断用户id是否存在User user = userMapper.selectOne(new QueryWrapper<User>().eq("id", userId));if (user==null){throw new StockException("请输入正确的用户ID!!");}//2、判断商品是否存在Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("id", stockId));if (stock==null){throw new StockException("请输入正确的商品ID!!");}pastDue(stockId);//3、判断库存是否存在if (stock.getCount().equals(stock.getSale())){throw new StockException("库存不足!!");}return stock;}

2.接口隐藏

根据用户id与商品id生成一个MD5的加密字符串,存入redis中,用户在进行秒杀的时候判断一下密匙是否正确。

1.编写生成密匙的方法

@Override
public String getMd5(Integer userId, Integer stockId) {//验证数据的合法性·check(userId,stockId);//生成hashKeyString hashKey = "KEY_"+userId+"_"+stockId;//生成md5String md5 = DigestUtils.md5DigestAsHex((userId + stockId + " ").getBytes());//写入redis中redisTemplate.opsForValue().set(hashKey,md5,10, TimeUnit.SECONDS);return md5;
}

2.使用controller调用

/*** 获取密匙* @param userId* @param stockId* @return*/
@GetMapping("/getMd5")
public String getMd5(Integer userId,Integer stockId){return stockService.getMd5(userId,stockId);
}

3.验证密匙是否正确

//判断密匙是否正确
private void isMD(Integer userId,Integer stockId,String md5){//从redis中拿,判断是否有密匙String hashkey = "KEY_"+userId+"_"+stockId;String md5s = redisTemplate.opsForValue().get(hashkey);if (md5s==null||!md5.equals(md5s)){throw new StockException("密匙错误!");}
}

4.使用密匙+用户id+商品id测试

service

//判断密匙是否正确
private void isMD(Integer userId,Integer stockId,String md5){//从redis中拿,判断是否有密匙String hashkey = "KEY_"+userId+"_"+stockId;String md5s = redisTemplate.opsForValue().get(hashkey);if (md5s==null||!md5.equals(md5s)){throw new StockException("密匙错误!");}
}@Override
public Stock check(Integer userId, Integer stockId, String md5) {//1、判断用户id是否存在User user = userMapper.selectOne(new QueryWrapper<User>().eq("id", userId));if (user==null){throw new StockException("请输入正确的用户ID!!");}//2、判断商品是否存在Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("id", stockId));if (stock==null){throw new StockException("请输入正确的商品ID!!");}//判断秒杀是否开始pastDue(stockId);//判断密匙是否正确isMD(userId,stockId,md5);//3、判断库存是否存在if (stock.getCount().equals(stock.getSale())){throw new StockException("库存不足!!");}return stock;
}@Override
public Integer killCASTokenMD5(Integer userId, Integer stockId, String md5) {Stock check = check(userId, stockId,md5);//将出售量+1check.setSale(check.getSale()+1);//在sql层次将version+1,带着version作为条件进行修改int update = stockMapper.updateStock(check);if (update==0){throw new StockException("抢票失败,请重试!");}//创建商品订单Integer orderId = saveOrder(check.getId(), check.getName());return orderId;
}

controller

/*** 秒杀,使用乐观锁,令牌桶限流,md5隐藏接口* @param userId* @param stockId* @param md5* @return*/
@GetMapping("/killCASTokenMD5")
public String killCASTokenMD5(Integer userId,Integer stockId,String md5){Integer integer = stockService.killCASTokenMD5(userId, stockId, md5);return retrue(userId,integer);
}

3.单用户限制频率

在用户抢购的时候根据用户id+商品id生成一个键值对存入redis中,设置过期时间。

如果在过期时间内请求次数超过多少次就提示繁忙。

//判断用户同一时间访问次数是否超时
private void bindingHours(Integer userId,Integer stockId){//1.生成键值对String hashKey = "KILL_"+userId+"_"+stockId+"_COUNT";//2.判断redis是否存在该键,String key = redisTemplate.opsForValue().get(hashKey);// 如果不存在则新增键,值为0,设置过期时间;if (key==null){redisTemplate.opsForValue().set(hashKey,"0",10,TimeUnit.SECONDS);return;}//如果存在则判断次数是否超过10次Integer count = Integer.parseInt(key);if (count>=10){throw new StockException("请求超时,请重新再试!");}count++;redisTemplate.opsForValue().set(hashKey,String.valueOf(count));
}

在判断方法中加入

@Override
public Stock check(Integer userId, Integer stockId, String md5) {//1、判断用户id是否存在User user = userMapper.selectOne(new QueryWrapper<User>().eq("id", userId));if (user==null){throw new StockException("请输入正确的用户ID!!");}//2、判断商品是否存在Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("id", stockId));if (stock==null){throw new StockException("请输入正确的商品ID!!");}//判断秒杀是否开始pastDue(stockId);//判断密匙是否正确isMD(userId,stockId,md5);//判断用户在指定时间内是否超出点击次数bindingHours(userId,stockId);//3、判断库存是否存在if (stock.getCount().equals(stock.getSale())){throw new StockException("库存不足!!");}return stock;
}

Springboot秒杀案例相关推荐

  1. SpringBoot开发案例从0到1构建分布式秒杀系统

    前言 最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路.俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场景 ...

  2. Redis 秒杀案例

    Redis 秒杀案例 文章目录 Redis 秒杀案例 实现 ab工具模拟并发 超卖和超时问题解决 配置JedisPool连接池来解决超时问题 利用乐观锁淘汰用户,解决超卖问题 库存遗留问题解决 什么是 ...

  3. Java实例开发教程:SpringBoot开发案例

    最近在做邮件发送的服务,正常来说SpringBoot整合mail还是很方便的,然而来了新的需求:A请求使用邮箱C发送,B请求使用邮箱D发送,也就是说我们需要配置两套发送服务. 单实例 首先我们来看下单 ...

  4. mysql悲观锁关键字_MySQL悲观锁 select for update实现秒杀案例(jfinal框架)

    MySQL悲观锁 select for update实现秒杀案例(jfinal框架) 发布时间:2018-08-17作者:laosun阅读(4287) 为了方便测试,博主使用最新的jfinal框架,里 ...

  5. SpringBoot开发案例之异常处理并邮件通知

    SpringBoot开发案例之异常处理并邮件通知 参考文章: (1)SpringBoot开发案例之异常处理并邮件通知 (2)https://www.cnblogs.com/lywJ/p/1107696 ...

  6. 尚硅谷Redis6基础教程-秒杀案例中库存遗留问题

    尚硅谷redis6基础教程中视频24-27的秒杀案例,使用Redis乐观锁解决了超卖问题,但是也产生了库存遗留问题.引入Lua脚本,解决了超卖和库存遗留.Lua脚本为什么解决了库存遗留问题???

  7. 11、Redis_事务_秒杀案例

    11. Redis_事务_秒杀案例 11.1. 解决计数器和人员记录的事务操作 基本功能实现: public class SecKillController {//秒杀过程public static ...

  8. NetCore3.1连接Redis做秒杀案例

    测试环境:netcore3.1   redis-6.2.4 一:安装Redis 尽管在不是系统性介绍Radis的地方介绍安装radis并不是一件明智的事情,但本着能跑起来就算成功的原则,这里简单介绍一 ...

  9. Redis学习之秒杀案例

    记录每次学习的过程,总结学习的内容,希望能给到自己和别人帮助. Redis学习之秒杀案例 基础设计思路 1.uid和prodid的非空判断 2.连接redis 3.拼接key,包括库存的和成功秒杀的用 ...

  10. 一个SpringBoot小案例

    一个SpringBoot小案例 这个案例是B站雷丰阳老师的SpringBoot课程里的实验(一个员工的crud操作),通过学习本案例可以熟悉springboot的开发流程,只采用了springboot ...

最新文章

  1. Object.create()和new Object()
  2. 学习Python3:20171031
  3. python 获取第一个key_Python中常见的9大坑,看看你有没有遇到
  4. select min from 连接
  5. 无延时/无延迟视频直播实例效果案例
  6. python qq空间登录_Python案例之QQ空间自动登录程序实现-阿里云开发者社区
  7. java中图片排版_基于Java的图像排版系统的设计.pdf
  8. ps2键盘测试软件,PS2键盘51测试程序1
  9. 【Visual C++】游戏开发笔记四十一 浅墨DirectX教程之九 为三维世界添彩:纹理映射技术(一)
  10. 出租分数 20作者 陈越单位 浙江大学
  11. 百度网址安全中心提醒:该页面可能已被非法篡改!如何去解决?
  12. 密码学之BGN同态加密算法
  13. 2018年机器视觉产业技术现状、发展趋势分析及发展前景预测
  14. HTML5期末大作业:电影在线网站设计——漫威电影(2页) 免费大学生网页设计制作作业作品下载dreamweaver制作静态html网页设计作业作
  15. vue3 前端pc生成微信支付二维码
  16. 向量化编程思路小结(矩阵计算)
  17. 三菱fx5u modbus tcp fb块用法_2020江苏三菱PLCFX3GA14MR回收回收电话西门子软启动器...
  18. PS 学习笔记 03-移动工具图层概念
  19. 麒麟子Cocos Creator实用技巧八:回合战棋类RPG战斗效果
  20. 小学生终究干不过富婆?《王者荣耀》返场皮肤厨娘夺冠,猴子落榜

热门文章

  1. 【Axure高保真原型】人物卡片多条件搜索案例
  2. pandas数据分析小案例:以美国大选数据为例
  3. 2021-08-28(算法记忆口诀)
  4. UE4和C++ 开发-UE4怎么删除C++类
  5. C# 合并实体对象
  6. 亮哥不说话就能让我知道bug在哪。。
  7. I2C通信协议详解和通信流程分析
  8. 20180627各手机的电池生产厂商
  9. Linux | Linux使用互斥锁及条件变量替代信号量
  10. MySQL存储过程调试工具