Springboot秒杀案例
使用技术栈: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秒杀案例相关推荐
- SpringBoot开发案例从0到1构建分布式秒杀系统
前言 最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路.俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场景 ...
- Redis 秒杀案例
Redis 秒杀案例 文章目录 Redis 秒杀案例 实现 ab工具模拟并发 超卖和超时问题解决 配置JedisPool连接池来解决超时问题 利用乐观锁淘汰用户,解决超卖问题 库存遗留问题解决 什么是 ...
- Java实例开发教程:SpringBoot开发案例
最近在做邮件发送的服务,正常来说SpringBoot整合mail还是很方便的,然而来了新的需求:A请求使用邮箱C发送,B请求使用邮箱D发送,也就是说我们需要配置两套发送服务. 单实例 首先我们来看下单 ...
- mysql悲观锁关键字_MySQL悲观锁 select for update实现秒杀案例(jfinal框架)
MySQL悲观锁 select for update实现秒杀案例(jfinal框架) 发布时间:2018-08-17作者:laosun阅读(4287) 为了方便测试,博主使用最新的jfinal框架,里 ...
- SpringBoot开发案例之异常处理并邮件通知
SpringBoot开发案例之异常处理并邮件通知 参考文章: (1)SpringBoot开发案例之异常处理并邮件通知 (2)https://www.cnblogs.com/lywJ/p/1107696 ...
- 尚硅谷Redis6基础教程-秒杀案例中库存遗留问题
尚硅谷redis6基础教程中视频24-27的秒杀案例,使用Redis乐观锁解决了超卖问题,但是也产生了库存遗留问题.引入Lua脚本,解决了超卖和库存遗留.Lua脚本为什么解决了库存遗留问题???
- 11、Redis_事务_秒杀案例
11. Redis_事务_秒杀案例 11.1. 解决计数器和人员记录的事务操作 基本功能实现: public class SecKillController {//秒杀过程public static ...
- NetCore3.1连接Redis做秒杀案例
测试环境:netcore3.1 redis-6.2.4 一:安装Redis 尽管在不是系统性介绍Radis的地方介绍安装radis并不是一件明智的事情,但本着能跑起来就算成功的原则,这里简单介绍一 ...
- Redis学习之秒杀案例
记录每次学习的过程,总结学习的内容,希望能给到自己和别人帮助. Redis学习之秒杀案例 基础设计思路 1.uid和prodid的非空判断 2.连接redis 3.拼接key,包括库存的和成功秒杀的用 ...
- 一个SpringBoot小案例
一个SpringBoot小案例 这个案例是B站雷丰阳老师的SpringBoot课程里的实验(一个员工的crud操作),通过学习本案例可以熟悉springboot的开发流程,只采用了springboot ...
最新文章
- Object.create()和new Object()
- 学习Python3:20171031
- python 获取第一个key_Python中常见的9大坑,看看你有没有遇到
- select min from 连接
- 无延时/无延迟视频直播实例效果案例
- python qq空间登录_Python案例之QQ空间自动登录程序实现-阿里云开发者社区
- java中图片排版_基于Java的图像排版系统的设计.pdf
- ps2键盘测试软件,PS2键盘51测试程序1
- 【Visual C++】游戏开发笔记四十一 浅墨DirectX教程之九 为三维世界添彩:纹理映射技术(一)
- 出租分数 20作者 陈越单位 浙江大学
- 百度网址安全中心提醒:该页面可能已被非法篡改!如何去解决?
- 密码学之BGN同态加密算法
- 2018年机器视觉产业技术现状、发展趋势分析及发展前景预测
- HTML5期末大作业:电影在线网站设计——漫威电影(2页) 免费大学生网页设计制作作业作品下载dreamweaver制作静态html网页设计作业作
- vue3 前端pc生成微信支付二维码
- 向量化编程思路小结(矩阵计算)
- 三菱fx5u modbus tcp fb块用法_2020江苏三菱PLCFX3GA14MR回收回收电话西门子软启动器...
- PS 学习笔记 03-移动工具图层概念
- 麒麟子Cocos Creator实用技巧八:回合战棋类RPG战斗效果
- 小学生终究干不过富婆?《王者荣耀》返场皮肤厨娘夺冠,猴子落榜