/JavaMiaosha

Java商城秒杀项目

Primary LanguageJava

项目描述

基于 SpringBoot+Maven+Mybatis+Redis+RabbitMQ 高并发商城秒杀系统

具体优化

  1. 使用分布式Session,让多台服务器可以响应。

    实际应用中, 不会只有一个应用服务器, 肯定是分布式多台应用服务器. 假如用户登录是在服务器A,第一个请求到了服务器A,但是第二个请求到了服务器B,这时候服务器B并不存在该Session,所以就会将用户踢到登录页面。这将大大降低用户体验度,导致用户的流失,这种情况是项目绝不应该出现的。

    处理: 登录成功之后,给用户生成类似于sessionId的东西token来标识用户, 然后写入cookie当中传递给客户端, 并将此(token -> 用户信息) 信息存入redis中, 客户端在随后的访问中上传该token,然后服务端取到token后,就跟据这个token去取用户对应的session信息(redis中).

  2. 使用redis做缓存提高访问速度和并发量,减少数据库压力。

    页面缓存(URL缓存)

    ( 页面缓存保存的时间不宜过长, 需要折中, 保证数据更新的及时性又能减少服务器的压力 )

    1. 取缓存 2) 如果缓存中不存在, 则手动渲染, 并保存到缓存中
    // 1.取缓存
    String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
    if (!StringUtils.isEmpty(html)) {
        return html;
    }
    
    // 2.手动渲染 使用模板引擎 templateName:模板名称 String templateName="goods_list";
    SpringWebContext context = new SpringWebContext(request, response, request.getServletContext(),
            request.getLocale(), model.asMap(), applicationContext);
    html = thymeleafViewResolver.getTemplateEngine().process("goods_list", context);
    // 保存至缓存
    if (!StringUtils.isEmpty(html)) {
        redisService.set(GoodsKey.getGoodsList, "", html);// key---GoodsKey:gl---缓存goodslist这个页面
    }
    return html;

    对象缓存

    相比页面缓存是更细粒度缓存。在实际项目中, 不会大规模使用页面缓存,对象缓存就是当用到用户数据的时候,可以从缓存中取出。比如:更新用户密码,根据token来获取用户缓存对象。

    /**
     * 根据id取得对象,先去缓存中取
     * @param id
     * @return
     */
    public MiaoshaUser getById(long id) {
        // 1.取缓存 ---先根据id来取得缓存
        MiaoshaUser user = redisService.get(MiaoshaUserKey.getById, "" + id, MiaoshaUser.class);
        // 能再缓存中拿到
        if (user != null) {
            return user;
        }
        // 2.缓存中拿不到,那么就去取数据库
        user = miaoshaUserDao.getById(id);
        // 3.设置缓存
        if (user != null) {
            redisService.set(MiaoshaUserKey.getById, "" + id, user);
        }
        return user;
    }

    缓存更新策略是先更新数据库后删除缓存

  3. 使用页面静态化,缓存页面至浏览器,前后端分离降低服务器压力。

    常用技术: Vue.js, AngularJS

    此处只是简单的实现页面静态化.

    对比

    未作页面静态化:请求某一个页面,访问缓存,查看缓存中是否有,缓存中有直接返回,缓存中没有的话,将数据渲染到html页面再存到缓存,再将整个html页面返回给客户端显示。

    做了页面静态化:第一次是去请求后台要渲染好的html页面,之后的请求都是直接访问用户本地浏览器的缓存的html页面 ,静态资源,然后前端通过Ajax来访问后端,只去获取页面需要显示的数据返回即可。

  4. 使用消息队列完成异步下单,提升用户体验,削峰和降流。

    秒杀接口优化

    1)系统初始化,将商品库存数量加载到redis

    2)收到请求, Redis预减库存,库存不足, 直接返回, 否则进入3)

    3)请求入队,立即返回排队中

    4)请求出队,生成订单,减少库存

    5)客户端轮询,是否秒杀成功

  5. 安全性优化:双重md5密码校验,秒杀接口地址的隐藏,接口限流防刷,数学公式验证码。

解决超卖问题

超卖场景: 不同用户在读请求的时候,发现商品库存足够,然后同时发起请求,进行秒杀操作,减库存,导致库存减为负数。 最简单的方法,更新数据库减库存的时候,进行库存限制条件,在reduceStock(GoodsVo goodsvo)这个方法里,sql要多加一个stock_count > 0即:

//stock_count>0的时候才执行更新,数据库本身会有锁,那么就不会在数据库中同时多个线程更新一条记录,使用数据库特性来保证超卖的问题
@Update("update miaosha_goods set stock_count=stock_count-1 where goods_id=#{goodsId} and stock_count>0")
public void reduceStock(MiaoshaGoods goods);  

InnoDB默认是可重复读的(REPEATABLE READ)

运行说明

1.需要启动redis

通过配置文件启动redis
redis-server ./redis.conf

查看Redis进程
ps -e|grep redis

关闭redis进程
> redis-cli
127.0.0.1:6379> SHUTDOWN save
not connected> exit

2.需要启动rabbitmq

启动rabbitmq
root@ubuntu:/usr/local/rabbitmq/sbin# ./rabbitmq-server

查看rabbitmq启动日志
root@ubuntu:/usr/local/rabbitmq/sbin# tail -f /usr/local/rabbitmq/var/log/rabbitmq/rabbit\@ubuntu.log

关闭rabbitmq
root@ubuntu:/usr/local/rabbitmq/sbin# ./rabbitmqctl stop

演示

登录界面

秒杀商品界面

秒杀商品详情界面

秒杀成功界面

秒杀订单详情