基于Springboot和redis的秒杀业务实现
github地址:https://github.com/g992987642/springboot-seckill
线上地址:http://118.190.54.134:8091/seckill/list
页面展示:
所有秒杀商品页:
秒杀详情页:
88.png
本篇主要讲了实现业务的分析思路、笔者学到的新知识点以及踩过的坑,系统中附有建库和导入数据的.sql文件,请自行更改application.yml下的配置属性。
1.这个系统是跟着慕课网老师的视频讲解做的,讲的很详细,值得一看。附上链接:Java高并发秒杀API
2.老师实现的系统是SSM的,我这边用的是Springboot,加了些其他自己的理解。
3.在跑系统前先来看看我总结的另一篇文章吧,秒杀遇到的问题及解决方案,分析了秒杀系统中的问题和实现方案,对一些数据库事务、消息队列、redis的知识点做了延伸,带着问题看代码效果更佳。
首先要说明的一点,因为笔者能力有限只实现了一部分秒杀遇到的问题及解决方案
介绍到的问题和解决方案,也会在以后能力有提升的时候继续完善这个系统,希望读者能在这之上拓展开来学习。先简单说说我的秒杀系统解决了什么问题吧:
(本篇没有概述如何解决这些问题的,老师的视频已经讲的非常详细,建议先看上面的文章,再边看视频边看代码,建议1.5倍速,另外本人按自己的理解做了修改和添加)
1.用MD5加密解决了链接暴露的问题
2.解决了用户重复点击按钮的问题
2.用Spring的声明式事务解决了超卖的问题
3.用数据库中联合主键索引的方式实现了单人限购一件
4.引入了redis来缓解数据库的查询压力
5.加入了布隆过滤器防止缓存穿透(2019.12.20添加)
6.对数据库的一些查询语句加了排它锁,防止缓存击穿(2019.12.20添加)
再来看看我的秒杀系统中的主要练习的知识点:
1.Springboot整合Mybatis连接Mysql
2.Spring基于注解的声明式事务 @Transactional
3.使用Spring高度封装的RedisTemplate来操作redis
4.用redis中的hash数据结构来存储秒杀信息
5.用枚举类与自定义异常整合
6.自定义返回结果的封装
7.布隆过滤器的使用
需求分析及各层设计:
我们要尽可能实现秒杀的业务场景,实现商品的查询、库存的扣减、订单的插入,使用redis减少mysql的压力,在秒杀时间段才能进行秒杀,加密秒杀的url
DAO层:
对于商品表:
最基本的查询商品的功能,包括查单个和查所有
减少库存的方法
对于订单表:
需要实现查订单的方法
需要实现同一用户不能购买多件同一商品的插入方法
redis方面:采用hash数据结构来存放商品信息
Service层
获得所有商品的秒杀列表的方法(获得后返回页面之前应当先把数据存储到redis中,下次来查询就直接走redis,这个页面的的key可以不用设置过期时间)
获得一条商品秒杀信息的方法
秒杀未开始的时候返回系统时间,如果开始就返回秒杀的url
执行秒杀操作(秒杀一次后应当把数据库和redis中的库存一起修改)
Controller层
需要有获得时间的映射,
需要有转向专项商品秒杀页面的映射,
需要有所有商品展示的映射,
需要有暴露秒杀url的映射,
需要有执行秒杀的映射
在这个系统中学到的和踩过的坑:
1.如果需要退出这个账号,需要删除页面中存储的填过的手机号的cookie
如果你用的是360极速浏览器的话,可以根据以下步骤删除,其他浏览器类似。
qq.png
①.点击浏览器的右上角菜单,单击选项。
②.选择高级设置后点击内容设置.. HB7LS`JG4B`43ABD$AJH2JM.png③.选择一个名叫killPhone的cookie删除即可
XB$%QC_O]$8ZT7WQVZ5YESH.png
2.在insert后面加一个ignore,插入失败不会报错而是返回更新的条数:0条,这个时候去判断返回的int值是不是0就可以了,而不需要去catch错误。
25X6TRJ6UT{6MXJ(1KVEB8A.png
3.建表时把create_time列设为CURRENT_TIMESTAMP,表示每次更新或者插入一行数据的时候,该字段都会更新成当前时间
注意,这里有个坑,就是可能这个时间与系统时间对不上,这是数据库时区的问题导致的,解决方法如下:
777.png
https://www.jianshu.com/p/ea7ef2d29940
4.①注意实体类中Date类型数据都用了@DateTimeFormat()(来自springframework)和@JsonFormat()(来自jackson)标识可以实现Controller在返回JSON数据(用@ResponseBody标识的方法或@RestController标识的类)的时候能将Date类型的参数值(经Mybatis查询得到的数据是英文格式的日期,因为实体类中是Date类型)转换为注解中指定的格式返回给页面(相当于经过了一层SimpleDateFormate)。
②其次要注意在编写实体类的时候尽量养成习惯继承Serializable接口,需要存储到redis的类属性都要实现这个接口。
③在SeckillOrder中我们注入了Seckill类作为一个属性,目的是为了可以使用多表查询的方式从seckill_order表中查询出来对应的seckill表数据。
44.png
5.用redis中的hash数据结构的时候,就不用像我上个系统(基于redis和websocket的聊天系统
)一样,String属性先转成JSON字符串再存储,取出来还要转成对象类,麻烦死了,直接存!
1YXJEKZ}QHA8UI75ZXQ%H8I.png
6.因为系统中关注的是秒杀的业务场景,并没有实现登录的功能,需要在前端在用户进入秒杀页面的时候,填入手机号,如果手机号符合格式,就将手机号存入cookie中,即为用户成功登录了,设置cookie名称为killPhone。如果想退出登录,只需要清除浏览器中的cookie即可。(否则会导致买完一件商品后难以再次测试)
2222.png
这个controller的映射方法是一个秒杀的接口,注意这里的CookieValue,可以用来获取网页中对应名字的cookie。
value : cookie名称
required: 设置是否必须包含该cookie
LN8@YRR%UTF@D`N6C(BR203.png