【秒杀系统】秒杀系统和拓展优化(1)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
简介: 【秒杀系统】秒杀系统和拓展优化(1)

高并发秒杀系统

分析需求

场景分析


秒杀时大量用户会在同一时间同时进行抢购,网站瞬时访问流量激增。

秒杀一般是访问请求数量远远大于库存数量,只有少部分用户能够秒杀成功。

秒杀业务流程比较简单,一般就是下订单减库存。

问题分析


秒杀系统一般要注意的问题就是 :


库存少卖,超卖问题(原子性)

流量削峰,这里我们设定的时候每个用户只能秒杀一次所以比较好处理

执行流程


初始化数据,提前预热要秒杀的商品(项目里设置为启动,如果秒杀列表有就预热)

使用 redis 缓存秒杀的商品信息,使用redis来承担秒杀的压力最后生产秒杀到的用户,再到mysql生成订单

在秒杀时使用(事务,分布式锁两种方式都实现)对商品库存,保证原子性

设计思路图

2.png



秒杀系统

技术选型

开发语言 : Java 8 或 Java 11


框架技术: SpringBoot2.x ,Mybatis-plus ,Thymeleaf


中间件: Redis


数据库:MySQL 8.0


数据源: druid 1.16


测试工具: apache jmeter


数据库表设计

三张表,分别是


商品表: id 商品id 商品name 商品图片 商品类别 商品价格 库存

秒杀商品表 : id 商品id 秒杀开始时间 秒杀结束时间 秒杀价 可秒杀的数量

订单表 id 订单id 商品id 秒杀价格 用户id 地址 电话


sql表

CREATE DATABASE /*!32312 IF NOT EXISTS*/`seckill` /*!40100 DEFAULT CHARACTER SET utf8 */ 
USE `seckill`;
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (
  `id` int NOT NULL AUTO_INCREMENT,
  `goods_id` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '商品id',
  `goods_name` varchar(100) DEFAULT NULL COMMENT '商品名字',
  `goodtype` varchar(100) DEFAULT NULL COMMENT '商品类别',
  `price` double DEFAULT NULL COMMENT '商品价格',
  `img_path` varchar(200) DEFAULT NULL COMMENT '商品图片地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3;
/*Data for the table `goods` */
insert  into `goods`(`id`,`goods_id`,`goods_name`,`goodtype`,`price`,`img_path`) values 
(5,'1001','华为手机','电子用品',5000,'/img/huaweiphone.jpg');
/*Table structure for table `goodsorder` */
DROP TABLE IF EXISTS `goodsorder`;
CREATE TABLE `goodsorder` (
  `id` int NOT NULL AUTO_INCREMENT,
  `order_id` varchar(100) DEFAULT NULL,
  `user_id` varchar(100) DEFAULT NULL,
  `goods_id` varchar(100) DEFAULT NULL,
  `telephone` varchar(20) DEFAULT NULL,
  `address` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb3;
/*Data for the table `goodsorder` */
/*Table structure for table `seckillgoods` */
DROP TABLE IF EXISTS `seckillgoods`;
CREATE TABLE `seckillgoods` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `goods_id` varchar(100) DEFAULT NULL,
  `seckill_price` double DEFAULT NULL COMMENT '秒杀价格',
  `stock_num` int DEFAULT NULL COMMENT '秒杀库存',
  `start_time` datetime DEFAULT NULL COMMENT '开始时间',
  `end_time` datetime DEFAULT NULL COMMENT '结束时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
/*Data for the table `seckillgoods` */
insert  into `seckillgoods`(`id`,`goods_id`,`seckill_price`,`stock_num`,`start_time`,`end_time`) values 
(1,'1001',3599,10,'2022-03-07 00:00:00','2022-03-09 00:00:00');

项目配置文件

application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/seckill?    userSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    database: 0
    host: 120.79.14.203
    port: 6379
    password: root
    timeout: 1000
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    suffix: .html
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: false
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  #  configuration:
  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*

redis 配置类


@Configuration
public class RedisConfig {
    /**
     * @author 冷环渊 Doomwatcher
     * @context:redis 存储数据 序列化方式
     * @date: 2022/3/7 21:19
     * @param redisConnectionFactory
     * @return: org.springframework.data.redis.core.RedisTemplate<java.lang.String, java.lang.Object>
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
                                                               redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //声明 key 和 value 的序列化方式
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        GenericFastJsonRedisSerializer valueSerializer = new GenericFastJsonRedisSerializer();
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashKeySerializer(keySerializer);
        redisTemplate.setValueSerializer(valueSerializer);
        //多种的序列化方式 最好是谁序列化的谁处理
        //GenericFastJsonRedisSerializer
        //Jackson2JsonRedisSerializer
        return redisTemplate;
    }
}

redis工具类

@Configuration
public class RedisConfig {
    /**
     * @author 冷环渊 Doomwatcher
     * @context:redis 存储数据 序列化方式
     * @date: 2022/3/7 21:19
     * @param redisConnectionFactory
     * @return: org.springframework.data.redis.core.RedisTemplate<java.lang.String, java.lang.Object>
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
                                                               redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //声明 key 和 value 的序列化方式
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        GenericFastJsonRedisSerializer valueSerializer = new GenericFastJsonRedisSerializer();
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashKeySerializer(keySerializer);
        redisTemplate.setValueSerializer(valueSerializer);
        //多种的序列化方式 最好是谁序列化的谁处理
        //GenericFastJsonRedisSerializer
        //Jackson2JsonRedisSerializer
        return redisTemplate;
    }
}

商品列表

商品列表界面,是比较简单的,设计思路还是基于传统的三层开发


Controller -> servier -> dao这样的分层开发模式

3.png



实体类


这里为了方面展示,就展示实体类的属性,小伙伴们自行补充getter/setter以及构造方法等


public class goods {
    @Id
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String goods_id;
    private String goods_name;
    private String goodtype;
    private double price;
    private String img_path;
    @TableField(exist = false)
    private SeckillGoods seckillgoods;
}

DAO


虽然使用了 mybatis-plus 但是我们还是根据自己的查询需求新增了自己的方法,


@Repository
public interface GoodsMapper extends BaseMapper<goods> {
    goods getGoodsByGoodsId(String goodId);
    List<goods> selectGoods();
    List<goods> getGoodList();
}

映射文件


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hyc.seckillsystem.dao.GoodsMapper">
    <select id="getGoodsByGoodsId" resultType="com.hyc.seckillsystem.pojo.goods">
        select seckill.goods.goods_id,
               seckill.goods.goods_name,
               seckill.goods.goodtype,
               seckill.goods.price,
               seckill.goods.img_path
        from seckill.goods
        where goods_id = #{goodId};
    </select>
    <select id="getGoodList" resultType="com.hyc.seckillsystem.pojo.goods">
        select seckill.goods.goods_id,
               seckill.goods.goods_name,
               seckill.goods.goodtype,
               seckill.goods.price,
               seckill.goods.img_path
        from seckill.goods
    </select>
    <resultMap id="goodsResult" type="com.hyc.seckillsystem.pojo.goods">
        <id property="id" column="id"/>
        <result property="goods_id" column="goods_id"/>
        <result property="goods_name" column="goods_name"/>
        <result property="goodtype" column="goods_type"/>
        <result property="price" column="price"/>
        <result property="img_path" column="img_path"/>
        <association property="seckillgoods" javaType="com.hyc.seckillsystem.pojo.SeckillGoods">
            <result property="seckill_price" column="seckill_price"/>
            <result property="stock_num" column="stock_num"/>
        </association>
    </resultMap>
    <select id="selectGoods" resultMap="goodsResult">
        SELECT a.`goods_id`,
               a.`goods_name`,
               a.`goodtype`,
               a.`price`,
               a.`img_path`,
               b.`seckill_price`,
               b.`stock_num`
        FROM seckill.goods AS a
                 LEFT JOIN seckill.seckillgoods AS b
                           ON a.`goods_id` = b.`goods_id`
    </select>
</mapper>

service 实现类


两种查找商品数据的方法


getlist:逐个表 匹配查询

selectGoods链表查询

返回商品详情(包括秒杀价格等)VO getGoodsDetail(String goodId)


service 层的设计思路就是 调用DAO层接口 实现对数据库中取出数据的处理,并且提供给controller封装好的接口

@Service
public class GoodsServiceImpl implements GoodsService {
    @Autowired
    GoodsMapper goodsMapper;
    @Autowired
    SeckillGoodsMapper seckillGoodsMapper;
    /**
     * @author 冷环渊 Doomwatcher
     * @context:逐个表 匹配查询
     * @date: 2022/3/7 16:20
     * @param
     * @return: java.util.List<com.hyc.seckillsystem.vo.GoodVo>
     */
    @Override
    public List<GoodVo> getlist() {
        /* 两种方式
         * 每个表单独查询
         * 从第一个表查多个数据,再根据数据去第二个表中查询
         * */
        List<goods> goods = goodsMapper.getGoodList();
        ArrayList<GoodVo> result = new ArrayList<>();
        for (goods good : goods) {
            Map<String, Object> map = new HashMap();
            map.put("goods_id", good.getGoods_id());
            SeckillGoods seckillGoods = seckillGoodsMapper.getSeckillByGoodsId(good.getGoods_id());
            GoodVo vo = new GoodVo();
            vo.setGoodsId(good.getGoods_id());
            vo.setGoodsName(good.getGoods_name());
            vo.setGoodType(good.getGoodtype());
            vo.setPrice(good.getPrice());
            vo.setImgPath(good.getImg_path());
            vo.setSeckillPrice(seckillGoods.getSeckill_price());
            vo.setStockNum(seckillGoods.getStock_num());
            result.add(vo);
        }
        return result;
    }
    @Override
    //返回秒杀商品详情页面
    public GoodsDetailVo getGoodsDetail(String goodId) {
        SeckillGoods seckillGoods = seckillGoodsMapper.getSeckillByGoodsId(goodId);
        goods good = goodsMapper.getGoodsByGoodsId(goodId);
        //商品表信息1
        GoodsDetailVo goodsDetailVo = new GoodsDetailVo();
        goodsDetailVo.setGoodsId(good.getGoods_id());
        goodsDetailVo.setGoodsName(good.getGoods_name());
        goodsDetailVo.setGoodType(good.getGoodtype());
        goodsDetailVo.setPrice(good.getPrice());
        goodsDetailVo.setImgPath(good.getImg_path());
        //秒杀表信息
        goodsDetailVo.setSeckillPrice(seckillGoods.getSeckill_price());
        goodsDetailVo.setStockNum(seckillGoods.getStock_num());
        goodsDetailVo.setStartTime(seckillGoods.getStart_time());
        goodsDetailVo.setEndTime(seckillGoods.getEnd_time());
        return goodsDetailVo;
    }
    /**
     * @author 冷环渊 Doomwatcher
     * @context: 联表查询
     * @date: 2022/3/7 16:20
     * @param
     * @return: com.hyc.seckillsystem.vo.GoodVo
     */
    @Override
    public List<GoodVo> selectGoods() {
        List<goods> goods = goodsMapper.selectGoods();
        ArrayList<GoodVo> result = new ArrayList<>();
        for (goods good : goods) {
            GoodVo vo = new GoodVo();
            vo.setGoodsId(good.getGoods_id());
            vo.setGoodsName(good.getGoods_name());
            vo.setGoodType(good.getGoodtype());
            vo.setPrice(good.getPrice());
            vo.setImgPath(good.getImg_path());
            vo.setSeckillPrice(good.getSeckillGoods().getSeckill_price());
            vo.setStockNum(good.getSeckillGoods().getStock_num());
            result.add(vo);
        }
        return result;
    }
}

Controller


Controller层,负责控制接口数据和渲染界面的值传递 , 跳转 , 这里基本上不包含业务代码


尽可能的再service层 封装好方法 让Controller只负责调用


@Controller
@Slf4j
public class GoodsController {
    @Autowired
    GoodsService goodsService;
    @GetMapping(value = "/index")
    public String list(Model model) {
        //    逐层调用
        List<GoodVo> getlist = goodsService.getlist();
        model.addAttribute("goodslist", getlist);
        return "list";
    }
    @GetMapping(value = "/goodDetail/{goodsId}")
    public String getGoodDetail(@PathVariable String goodsId, Model model) {
        GoodsDetailVo goodsDetail = goodsService.getGoodsDetail(goodsId);
        model.addAttribute("GoodsDetail", goodsDetail);
        //获取到时间
        Date startTime = goodsDetail.getStartTime();
        Date endTime = goodsDetail.getEndTime();
        Date nowtime = new Date();
        /*设置状态值
         * 0 未开始
         * 1 正在秒杀
         * 2 秒杀结束
         * */
        int status;
        int remainSeconds = -1;
        if (nowtime.before(startTime)) {
            //    秒杀未开始
            status = 0;
            remainSeconds = (int) ((startTime.getTime() - nowtime.getTime()) / 1000);
        } else if (nowtime.after(endTime)) {
            //秒杀已经结束
            status = 2;
        } else {
            //正在秒杀中
            status = 1;
        }
        model.addAttribute("status", status);
        model.addAttribute("remainSeconds", remainSeconds);
        return "Detail";
    }
}


相关文章
|
12月前
|
机器学习/深度学习 编解码 数据可视化
转置卷积-清晰易懂
转置卷积(Transpose Convolution)是一种用于图像上采样的技术,常用于图像分割、生成对抗网络(GAN)等领域。与传统的上采样方法不同,转置卷积通过学习参数来实现更优的插值效果。本文介绍了转置卷积的背景、应用、与标准卷积的区别以及数学推导,帮助读者深入理解其原理和应用场景。
1043 1
|
负载均衡 监控 网络协议
深入理解并实现负载均衡技术
【5月更文挑战第23天】本文探讨了负载均衡技术,旨在应对互联网高并发需求。负载均衡通过分散请求至多台服务器,提升系统性能和可靠性。核心是负载均衡器,其工作流程包括接收请求、解析、选择服务器、转发及返回响应。负载均衡技术分类包括反向代理(如Nginx、HAProxy)、DNS、IP(如LVS)和应用层负载均衡。实现时,以Nginx为例,需安装、配置反向代理、分发策略并启动服务。监控和优化是持续过程。负载均衡技术将持续发展,适应云计算和大数据时代。
|
11月前
|
Rust 安全 Java
探索Rust语言的并发编程模型
探索Rust语言的并发编程模型
287 2
|
JavaScript Java 测试技术
基于springboot+vue.js的图书管理系统附带文章和源代码设计说明文档ppt
基于springboot+vue.js的图书管理系统附带文章和源代码设计说明文档ppt
217 1
费德勒权变模型(Fiedler Contingency Model)详解与Python代码示例
费德勒权变模型(Fiedler Contingency Model)详解与Python代码示例
|
消息中间件 Kafka API
这些年背过的面试题——Kafka篇
本文是技术人面试系列Kafka篇,面试中关于Kafka都需要了解哪些基础?一文带你详细了解,欢迎收藏!
|
SpringCloudAlibaba Dubbo 应用服务中间件
SpringCloudAlibaba:3.1dubbo
SpringCloudAlibaba:3.1dubbo
302 2
|
设计模式
【设计模式系列笔记】桥接模式
桥接模式(Bridge Pattern)是一种结构性设计模式,它将抽象部分与实现部分分离,使它们可以独立变化而互不影响。桥接模式通过组合而不是继承的方式来实现这种分离。
287 6
|
编解码 应用服务中间件 nginx
如何实现rtsp h265 转 rtmp (rtsp hevc 转 rtmp)并转发到CDN或自建服务器
rtsp h265 转 rtmp,首先要对rtmp协议做扩展,目前国内已有相应扩展标准,国内开发者基本上都按这个扩展协议做的。协议层问题已解决。剩下关键问题是要实现rtmp h265推送模块, 支持rtmp h265 的server, rtmp h265播放模块。
322 1
|
人工智能 编解码 算法
使用LabVIEW AI视觉工具包快速实现霍夫圆和霍夫直线检测(含源码)
使用LabVIEW AI视觉工具包快速实现霍夫圆和霍夫直线检测(含源码)
400 0
下一篇
oss教程