GithubHelp home page GithubHelp logo

zaiyunduan123 / springboot-seckill Goto Github PK

View Code? Open in Web Editor NEW
2.8K 62.0 1.0K 4.92 MB

:racehorse:基于SpringBoot + MySQL + Redis + RabbitMQ + Guava开发的高并发商品限时秒杀系统

Java 74.80% HTML 23.55% JavaScript 1.65%

springboot-seckill's Introduction

系统介绍

本系统是使用SpringBoot开发的高并发限时抢购秒杀系统,除了实现基本的登录、查看商品列表、秒杀、下单等功能,项目中还针对高并发情况实现了系统缓存、降级和限流。

开发工具

IntelliJ IDEA + Navicat + Sublime Text3 + Git + Chrome

压测工具

JMeter

开发技术

前端技术 :Bootstrap + jQuery + Thymeleaf

后端技术 :SpringBoot + MyBatis + MySQL

中间件技术 : Druid + Redis + RabbitMQ + Guava

秒杀优化方向

  1. 将请求尽量拦截在系统上游:传统秒杀系统之所以挂,请求都压倒了后端数据层,数据读写锁冲突严重,几乎所有请求都超时,流量虽大,下单成功的有效流量甚小,我们可以通过限流、降级等措施来最大化减少对数据库的访问,从而保护系统。

  2. 充分利用缓存:秒杀商品是一个典型的读多写少的应用场景,充分利用缓存将大大提高并发量

实现技术点

1. 两次MD5加密

将用户输入的密码和固定Salt通过MD5加密生成第一次加密后的密码,再讲该密码和随机生成的Salt通过MD5进行第二次加密,最后将第二次加密后的密码和第一次的固定Salt存数据库

好处:

  1. 第一次作用:防止用户明文密码在网络进行传输
  2. 第二次作用:防止数据库被盗,避免通过MD5反推出密码,双重保险

2. session共享

验证用户账号密码都正确情况下,通过UUID生成唯一id作为token,再将token作为key、用户信息作为value模拟session存储到redis,同时将token存储到cookie,保存登录状态

好处: 在分布式集群情况下,服务器间需要同步,定时同步各个服务器的session信息,会因为延迟到导致session不一致,使用redis把session数据集中存储起来,解决session不一致问题。

3. JSR303自定义参数验证

使用JSR303自定义校验器,实现对用户账号、密码的验证,使得验证逻辑从业务代码中脱离出来。

4. 全局异常统一处理

通过拦截所有异常,对各种异常进行相应的处理,当遇到异常就逐层上抛,一直抛到最终由一个统一的、专门负责异常处理的地方处理,这有利于对异常的维护。

5. 页面缓存 + 对象缓存

  1. 页面缓存:通过在手动渲染得到的html页面缓存到redis
  2. 对象缓存:包括对用户信息、商品信息、订单信息和token等数据进行缓存,利用缓存来减少对数据库的访问,大大加快查询速度。

6. 页面静态化

对商品详情和订单详情进行页面静态化处理,页面是存在html,动态数据是通过接口从服务端获取,实现前后端分离,静态页面无需连接数据库打开速度较动态页面会有明显提高

7. 本地标记 + redis预处理 + RabbitMQ异步下单 + 客户端轮询

描述:通过三级缓冲保护,1、本地标记 2、redis预处理 3、RabbitMQ异步下单,最后才会访问数据库,这样做是为了最大力度减少对数据库的访问。

实现:

  1. 在秒杀阶段使用本地标记对用户秒杀过的商品做标记,若被标记过直接返回重复秒杀,未被标记才查询redis,通过本地标记来减少对redis的访问
  2. 抢购开始前,将商品和库存数据同步到redis中,所有的抢购操作都在redis中进行处理,通过Redis预减少库存减少数据库访问
  3. 为了保护系统不受高流量的冲击而导致系统崩溃的问题,使用RabbitMQ用异步队列处理下单,实际做了一层缓冲保护,做了一个窗口模型,窗口模型会实时的刷新用户秒杀的状态。
  4. client端用js轮询一个接口,用来获取处理状态

8. 解决超卖

描述:比如某商品的库存为1,此时用户1和用户2并发购买该商品,用户1提交订单后该商品的库存被修改为0,而此时用户2并不知道的情况下提交订单,该商品的库存再次被修改为-1,这就是超卖现象

实现:

  1. 对库存更新时,先对库存判断,只有当库存大于0才能更新库存
  2. 对用户id和商品id建立一个唯一索引,通过这种约束避免同一用户发同时两个请求秒杀到两件相同商品
  3. 实现乐观锁,给商品信息表增加一个version字段,为每一条数据加上版本。每次更新的时候version+1,并且更新时候带上版本号,当提交前版本号等于更新前版本号,说明此时没有被其他线程影响到,正常更新,如果冲突了则不会进行提交更新。当库存是足够的情况下发生乐观锁冲突就进行一定次数的重试。

9. 使用数学公式验证码

描述:点击秒杀前,先让用户输入数学公式验证码,验证正确才能进行秒杀。

好处:

  1. 防止恶意的机器人和爬虫
  2. 分散用户的请求

实现:

  1. 前端通过把商品id作为参数调用服务端创建验证码接口
  2. 服务端根据前端传过来的商品id和用户id生成验证码,并将商品id+用户id作为key,生成的验证码作为value存入redis,同时将生成的验证码输入图片写入imageIO让前端展示
  3. 将用户输入的验证码与根据商品id+用户id从redis查询到的验证码对比,相同就返回验证成功,进入秒杀;不同或从redis查询的验证码为空都返回验证失败,刷新验证码重试

10. 使用RateLimiter实现限流

描述:当我们去秒杀一些商品时,此时可能会因为访问量太大而导致系统崩溃,此时要使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:返回排队页面(高峰期访问太频繁,等一会重试)、错误页等。

实现:项目使用RateLimiter来实现限流,RateLimiter是guava提供的基于令牌桶算法的限流实现类,通过调整生成token的速率来限制用户频繁访问秒杀页面,从而达到防止超大流量冲垮系统。(令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务)

压测效果

优化前 :开启1000个线程循环10次同时访问,QPS = 423 优化前 优化后:QPS = 2501 优化后

关于项目运行的步骤

  1. 把sql目录下的seckill.sql脚本在你MySQL跑一遍,生成数据库表和数据
  2. 启动项目需要用到的组件Redis和RabbitMQ
  3. 直接运行启动类MainApplication.java
  4. 访问localhost:8080/login/to_login
  5. 登录的用户名是18181818181,密码是123456

本项目是学习了imooc网视频之后的个人理解和知识汇总,学习链接:https://coding.imooc.com/class/168.html

springboot-seckill's People

Contributors

zaiyunduan123 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

springboot-seckill's Issues

bug

哥们 一个bug 加载的时候要把订单信息加载上去
不然服务器宕机了已下单用户再次请求的话插入数据库会出错
mq的消息会一直轮询

关于隐藏秒杀地址

大佬,想问你一个问题,你这里为什么没有去实现隐藏秒杀地址的功能,你觉得这个没必要吗,希望你能解答一下

启动步骤

方便详细写下启动步骤吗,我这种小白来学习的话就是一路坑。

如果version失效了,更新失败 再次执行时没有取出表中最新的version,所以不管循环几次,更新也是失败的????

/**
* 减少库存,每次减一
*
* @return
*/
public boolean reduceStock(GoodsVo goods) {
int numAttempts = 0;
int ret = 0;
SeckillGoods sg = new SeckillGoods();
sg.setGoodsId(goods.getId());
sg.setVersion(goods.getVersion());
do {
numAttempts++;
try {
//TODO 如果version失效了,更新失败 再次执行时没有取出表中最新的version,所以不管循环几次,更新也是失败的????
ret = goodsMapper.reduceStockByVersion(sg);
} catch (Exception e) {
e.printStackTrace();
}
if (ret != 0)
break;
} while (numAttempts < DEFAULT_MAX_RETRIES);

    return ret > 0;
}

md5 二次加密

  1. 两次MD5加密
    将用户输入的密码和固定Salt通过MD5加密生成第一次加密后的密码,再讲该密码和随机生成的Salt通过MD5进行第二次加密,最后将第二次加密后的密码和第一次的固定Salt存数据库

这里存到 db 的 salt 应该是第二次加密时随机生成的 salt 吧

关于乐观锁

本人小白,mysql的update本身具有排他锁,@update("update sk_goods_seckill set stock_count = stock_count - 1, version= version + 1 where goods_id = #{goodsId} and stock_count > 0 and version = #{version}"),更新库存应该不存在安全问题,为什么还需要根据版本来实现乐观锁,希望释疑。

请教一下这个异步下单,真的算异步吗?

我看代码里面的controller层的SeckillController#list()方法,即执行秒杀的方法,redis的操作完成之后,只是把消息发送到rabbitmq中,然后rabbitmq监听取出消息,就直接和数据库交互了,所以我想问一下这个异步是体现在哪个地方呀?

重复下单redis中库存还是会减小

long stock = redisService.decr(GoodsKey.getGoodsStock, "" + goodsId);//10
        if (stock < 0) {
            afterPropertiesSet();
            long stock2 = redisService.decr(GoodsKey.getGoodsStock, "" + goodsId);//10
            if(stock2 < 0){
                localOverMap.put(goodsId, true);
                return Result.error(CodeMsg.SECKILL_OVER);
            }
        }
        //判断重复秒杀
        //如果重复秒杀,是否多次减库存?
        SeckillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(), goodsId);
        if (order != null) {
            return Result.error(CodeMsg.REPEATE_SECKILL);
        }

预减库存后,如果是重复秒杀,redis中的库存量还是会减少,导致其他用户不能下单

跑不了

java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@117159c0: startup date [Wed Mar 23 20:50:25 CST 2022]; root of context hierarchy

用户密码

你好,我想运行这个项目,密码可以说下吗

redis password

redis pass 没有通过配置读取到实例化中,导致redis 设置了pass就会报错。
JedisPool jp = new JedisPool(config, redisConfig.getHost(), redisConfig.getPort(),
redisConfig.getTimeout()*1000, redisConfig.getPassword(), 0);
git中没有加redisConifg.getPassword()
在RedisPoolFactory.java中

启动项目报错了

Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.

预减库存会不会存在卖不完的情况

SeckillController.list 方法中,先预减库存,然后判断是否重复秒杀。如果重复秒杀或者下订单异常,应该会出现有库存,但是返回秒杀完毕情况

多服务部署

项目当前是单服务,我想了解下有没有多服务的解决方案,比如1000个秒杀商品,拆分5个服务,每个服务分配200个商品供秒杀。这个解决方案需要解决的点在于请求路由。

立即支付后无响应

查看了代码,不知道是否只更新到了支付后的代码,并无找到点击支付按钮后的处理代码

关于MD5加密的笔误问题

数据库里存的应该是随机生成的salt吧,存写死的salt,自己也没办法比较用户输入的密码是否正确了啊

点击立即秒杀 后台user对象内的属性值都为null

点击立即秒杀 后台/do_seckill 方法中 user对象内的属性值都为null,当执行到//判断重复秒杀
SeckillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(), goodsId);报空指针异常,请问为什么呢?

乐观锁的version number在更新时没有传送

两个问题
1、更新库存时没有送version number,所以更新库存失败
2、秒杀成功以后,返回的int 永远是1,作为订单号返回,是查不到订单的

GoodsVo
增加
int version 和getter setter

GoodsMapper
getGoodsVoByGoodsId 增加返回 sg.version

GoodsService
reduceStock()方法增加
sg.setVersion(goods.getVersion());

OrderService
createOrder()方法
订单号要从实体类中取,否则update返回的永远是1
long orderId = orderInfo.getId();

怎么运行这个项目

用idea试了好几次都失败了,有没有运行成功的大佬教一下怎么运行,谢谢了

consumer减库存失败之后存入redis的goodOver的key有问题

同一个商品减库存key为SeckillKey:go1,失败一次,则后续所有该商品的所有减库存操作在redis中设为失败,一旦getSeckillResult查询时,mq暂时没执行则会通知秒杀失败。"对不起,秒杀失败" ,即一直失败
建议rediskey添加用户id,并给这个key设置过期时间,即超过最大mq延迟执行时间

项目启动后访问不了网页

数据什么的都导入了。该安装的也安装了。启动后也没报错。
但是访问localhost:8080一直都404加上/login啥的也是404.想问下大佬是怎么回事

预减库存如果库存小于0,为什么要调用afterPropertiesSet()更新每件商品?

秒杀下单时的一段代码:
//预减库存 long stock = redisService.decr(GoodsKey.getGoodsStock, "" + goodsId);//10 if (stock < 0) { afterPropertiesSet(); long stock2 = redisService.decr(GoodsKey.getGoodsStock, "" + goodsId);//10 if(stock2 < 0){ localOverMap.put(goodsId, true); return Result.error(CodeMsg.SECKILL_OVER); } }

下面这个是afterPropertiesSet方法定义,每次调用都要获取所有商品并且遍历商品,这样会消耗性能吧,而且,我只是下单某一件商品,为什么在遍历中将每件商品标记为false?原本某些标记为true的商品变为false了
public void afterPropertiesSet() { List<GoodsVo> goodsVoList = goodsService.listGoodsVo(); if (goodsVoList == null) { return; } for (GoodsVo goods : goodsVoList) { redisService.set(GoodsKey.getGoodsStock, "" + goods.getId(), goods.getStockCount()); //初始化商品都是没有处理过的 localOverMap.put(goods.getId(), false); } }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.