当前位置: 首页 > article >正文

电商项目-秒杀系统(一)秒杀业务分析

一、 秒杀业务分析

1.1 需求分析

每一个电商平台都有秒杀,打折促销的相关活 动。一般来说对于秒杀的活动用户的瞬时访问量是非常巨大 的,当到达了某一个时间点后,用户会 瞬间进入到我们的平台中,进行抢购。由于秒杀场景的特殊性,所以一般来说对于秒杀系统都是单独部署的。而且还需要考虑很多的问题,比方说如何进行分布式事务的实现,如何进行分布式锁的实现,如何预防秒杀商品的超卖,如何对秒杀系统进行高可用。对于秒杀系统的高并发,我们应该如何进行操作等等的这些问题。
对于秒杀商品来说,他最少多会存在两个限制分别是:商品库存限制,和时间的限制
秒杀商品通常有两种限制:库存限制时间限制

对于我们的项目来说,秒杀系统也是单独部署的。对于秒杀的数据来说,会单独存在秒杀数据库。

需求:

(1)秒杀频道首页列出秒杀商品
(4)点击立即抢购实现秒杀下单,下单时扣减库存。当库存为0或不在活动期范围内时无法秒杀。
(5)秒杀下单成功,直接跳转到支付页面(微信扫码),支付成功,跳转到成功页,填写收货地址、电话、收件人等信息,完成订单。
(6)当用户秒杀下单5分钟内未支付,取消预订单,调用微信支付的关闭订单接口,恢复库存。

1.2 表结构说明

秒杀商品信息表:

CREATE TABLE `tb_seckill_goods` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `goods_id` bigint(20) DEFAULT NULL COMMENT 'spu ID',
  `item_id` bigint(20) DEFAULT NULL COMMENT 'sku ID',
  `title` varchar(100) DEFAULT NULL COMMENT '标题',
  `small_pic` varchar(150) DEFAULT NULL COMMENT '商品图片',
  `price` decimal(10,2) DEFAULT NULL COMMENT '原价格',
  `cost_price` decimal(10,2) DEFAULT NULL COMMENT '秒杀价格',
  `seller_id` varchar(100) DEFAULT NULL COMMENT '商家ID',
  `create_time` datetime DEFAULT NULL COMMENT '添加日期',
  `check_time` datetime DEFAULT NULL COMMENT '审核日期',
  `status` char(1) DEFAULT NULL COMMENT '审核状态,0未审核,1审核通过,2审核不通过',
  `start_time` datetime DEFAULT NULL COMMENT '开始时间',
  `end_time` datetime DEFAULT NULL COMMENT '结束时间',
  `num` int(11) DEFAULT NULL COMMENT '秒杀商品数',
  `stock_count` int(11) DEFAULT NULL COMMENT '剩余库存数',
  `introduction` varchar(2000) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

秒杀订单表:

CREATE TABLE `tb_seckill_order` (
  `id` bigint(20) NOT NULL COMMENT '主键',
  `seckill_id` bigint(20) DEFAULT NULL COMMENT '秒杀商品ID',
  `money` decimal(10,2) DEFAULT NULL COMMENT '支付金额',
  `user_id` varchar(50) DEFAULT NULL COMMENT '用户',
  `seller_id` varchar(50) DEFAULT NULL COMMENT '商家',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `pay_time` datetime DEFAULT NULL COMMENT '支付时间',
  `status` char(1) DEFAULT NULL COMMENT '状态,0未支付,1已支付',
  `receiver_address` varchar(200) DEFAULT NULL COMMENT '收货人地址',
  `receiver_mobile` varchar(20) DEFAULT NULL COMMENT '收货人电话',
  `receiver` varchar(20) DEFAULT NULL COMMENT '收货人',
  `transaction_id` varchar(30) DEFAULT NULL COMMENT '交易流水',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

二、 秒杀商品存入缓存

为什么要把秒杀商品存入缓存?
主要是为了解决两点问题:
第一:我们可以减轻在高并发下的数据库的访问压力,
第二:我们就是为了解决在高并发下的商品超卖的问题,因为Redis他提供了原子性操作即使有很多用户他是同时到达的,但是对于Redis来说,他也是依次进行执行的。所以基于这两点我们将秒杀商品从MySQL中查询出来,存入到缓存中。

对于当前功能的操作流程如下图:
在这里插入图片描述

秒杀商品由B端存入Mysql,设置定时任务,每隔一段时间就从Mysql中将符合条件的数据从Mysql中查询出来并存入缓存中,redis以Hash类型进行数据存储。

2.1 秒杀服务搭建

步骤1)新建服务shangcheng_service_seckill

步骤2)添加依赖信息,详情如下:

<dependencies>
    <dependency>
        <groupId>com.shangcheng</groupId>
        <artifactId>shangcheng_common_db</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>com.shangcheng</groupId>
        <artifactId>shangcheng_service_order_api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.shangcheng</groupId>
        <artifactId>shangcheng_service_seckill_api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.shangcheng</groupId>
        <artifactId>shangcheng_service_goods_api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
    </dependency>
    <!--oauth依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
</dependencies>

步骤 3) 添加启动类

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan(basePackages = {"com.shangcheng.seckill.dao"})
@EnableScheduling
public class SecKillApplication {public static void main(String[] args) {
        //TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
        SpringApplication.run(SecKillApplication.class,args);
    }@Bean
    public IdWorker idWorker(){
        return new IdWorker(1,1);
    }/**
     * 设置 redisTemplate 的序列化设置
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 1.创建 redisTemplate 模版
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        // 2.关联 redisConnectionFactory
        template.setConnectionFactory(redisConnectionFactory);
        // 3.创建 序列化类
        GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
        // 6.序列化类,对象映射设置
        // 7.设置 value 的转化格式和 key 的转化格式
        template.setValueSerializer(genericToStringSerializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }}

步骤 4) 添加application.yml

server:
  port: 9011
spring:
  jackson:
    time-zone: GMT+8
  application:
    name: seckill
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.200.128:3306/shangcheng_seckill?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2b8
    username: root
    password: root
  main:
    allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
  redis:
    host: 192.168.200.128
  rabbitmq:
    host: 192.168.200.128
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:6868/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
  client:
    config:
      default:   #配置全局的feign的调用超时时间  如果 有指定的服务配置 默认的配置不会生效
        connectTimeout: 60000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接  单位是毫秒
        readTimeout: 20000  # 指定的是调用服务提供者的 服务 的超时时间()  单位是毫秒
#hystrix 配置
hystrix:
  command:
    default:
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制
          enabled: true
        isolation:
          strategy: SEMAPHORE
          thread:
            # 熔断器超时时间,默认:1000/毫秒
            timeoutInMilliseconds: 20000

步骤 5) 添加公钥

步骤 6) 添加Oauth配置类

@Configuration
@EnableResourceServer
//开启方法上的PreAuthorize注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {//公钥
    private static final String PUBLIC_KEY = "public.key";/***
     * 定义JwtTokenStore
     * @param jwtAccessTokenConverter
     * @return
     */
    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }/***
     * 定义JJwtAccessTokenConverter
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(getPubKey());
        return converter;
    }
    /**
     * 获取非对称加密公钥 Key
     * @return 公钥 Key
     */
    private String getPubKey() {
        Resource resource = new ClassPathResource(PUBLIC_KEY);
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
            BufferedReader br = new BufferedReader(inputStreamReader);
            return br.lines().collect(Collectors.joining("\n"));
        } catch (IOException ioe) {
            return null;
        }
    }/***
     * Http安全配置,对每个到达系统的http请求链接进行校验
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //所有请求必须认证通过
        http.authorizeRequests()
                .anyRequest().
                authenticated();    //其他地址需要认证授权
    }
}

步骤 7) 更改网关路径过滤类,添加秒杀工程过滤信息

步骤 8) 更改网关配置文件,添加请求路由转发

#秒杀微服务
        - id: shangcheng_seckill_route
          uri: lb://seckill
          predicates:
            - Path=/api/seckill/**
          filters:
            - StripPrefix=1

2.2 时间操作工具类

2.2.1 秒杀商品时间段分析

根据产品原型图结合秒杀商品表设计可以得知,秒杀商品是存在开始时间与结束时间的,当前秒杀商品是按照秒杀时间段进行显示,如果当前时间在符合条件的时间段范围之内,则用户可以秒杀购买当前时间段之内的秒杀商品。

缓存数据加载思路:定义定时任务,每天凌晨会进行当天所有时间段秒杀商品预加载。并且在B端进行限制,添加秒杀商品的话,只能添加当前日期+1的时间限制,比如说:当前日期为8月5日,则添加秒杀商品时,开始时间必须为6日的某一个时间段,否则不能添加。

2.2.2 秒杀商品时间段计算

将资源/DateUtil.java添加到公共服务中。基于当前工具类可以进行时间段的计算。

在该工具类中,进行时间计算测试:

public static void main(String[] args) {//定义存储结果的集合
    List<Date> dateList = new ArrayList<>();//获取本日凌晨时间点
    Date currentData = toDayStartHour(new Date());//循环12次 (因为要获取每隔两个时间为一个时间段的值)
    for (int i=0;i<12;i++){
        dateList.add(addDateHour(currentData,i*2));
    }for (Date date : dateList) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = simpleDateFormat.format(date);
        System.out.println(format);
    }
}

测试结果:

2.2.3 当前业务整体流程分析

1.查询所有符合条件的秒杀商品
–1) 获取时间段集合并循环遍历出每一个时间段
–2) 获取每一个时间段名称,用于后续redis中key的设置
–3) 状态必须为审核通过 status=1
–4) 商品库存个数>0
–5) 秒杀商品开始时间>=当前时间段
–6) 秒杀商品结束<当前时间段+2小时
–7) 排除之前已经加载到Redis缓存中的商品数据
–8) 执行查询获取对应的结果集
2.将秒杀商品存入缓存

2.3 代码实现

步骤一: 更改启动类,添加开启定时任务注解

@EnableScheduling

步骤二: 定义定时任务类
秒杀工程新建task包,并新建任务类SeckillGoodsPushTask

业务逻辑:

1)获取秒杀时间段菜单信息

2)遍历每一个时间段,添加该时间段下秒杀商品

2.1)将当前时间段转换为String,作为redis中的key

2.2)查询商品信息(状态为1,库存大于0,秒杀商品开始时间大于当前时间段,秒杀商品结束时间小于当前时间段,当前商品的id不在redis中)

3)添加redis

/**
 * 添加秒杀秒伤定时任务
 */
@Component
public class SeckillGoodsPushTask {@Autowired
    private SeckillGoodsMapper seckillGoodsMapper;@Autowired
    private RedisTemplate redisTemplate;private static final String SECKILL_GOODS_KEY="seckill_goods_";/**
     * 定时将秒杀商品存入redis
     * 暂定为30秒一次,正常业务为每天凌晨触发
     */
    @Scheduled(cron = "0/30 * * * * ?")
    public void loadSecKillGoodsToRedis(){List<Date> dateMenus = DateUtil.getDateMenus();
        for (Date dateMenu : dateMenus) {
            //每次用最好都重新new
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String redisExtName = DateUtil.date2Str(dateMenu);Example example = new Example(SeckillGoods.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("status","1");
            criteria.andGreaterThan("stockCount",0);
            criteria.andGreaterThanOrEqualTo("startTime",simpleDateFormat.format(dateMenu));
            criteria.andLessThan("endTime",simpleDateFormat1.format(DateUtil.addDateHour(dateMenu,2)));
            Set keys = redisTemplate.boundHashOps(SECKILL_KEY + redisExtName).keys();
            if (keys!=null && keys.size()>0){
                criteria.andNotIn("id",keys);
            }
            List<SeckillGoods> seckillGoodsList = seckillGoodsMapper.selectByExample(example);//添加到缓存中
            for (SeckillGoods seckillGoods : seckillGoodsList) {
                redisTemplate.boundHashOps(SECKILL_KEY + redisExtName).put(seckillGoods.getId(),seckillGoods);
            }
        }
    }
}

http://www.kler.cn/a/563871.html

相关文章:

  • MySQL——创建与管理视图
  • 基于 Python 开发分布式任务调度系统案例剖析
  • 《深度学习实战》第2集-补充:卷积神经网络(CNN)与图像分类 实战代码解析和改进
  • 基于CNN的FashionMNIST数据集识别2——模型训练
  • Java+SpringBoot+Vue+数据可视化的在线家具定制服务平台(程序+论文+讲解+安装+调试+售后)
  • 网络安全体系
  • OpenGL 04--GLSL、数据类型、Uniform、着色器类
  • 服务器虚拟化是一种将物理服务器资源(如CPU、内存、存储、网络等)通过软件技术抽象、分割和整合,创建多个独立、隔离的虚拟服务器(虚拟机,VM)的技术。
  • C ++ 静态存储区+堆空间
  • 常见锁类型介绍
  • <网络> 网络基础3
  • AI大模型-提示工程学习笔记20-多模态思维链提示
  • C++ STL(二)deque
  • JVM垃圾回收器深度底层原理分析与知识体系构建
  • 神经网络 - 激活函数(Sigmoid 型函数)
  • 利用粒子群算法(PSO)来优化BP神经网络的权值和阈值
  • Flutter_boost混编开发系统MethodChannel之坑
  • Redis与MySQL数据一致性问题解决方案
  • 数据结构秘籍(一)线性数据结构
  • 【深度解析】Java接入DeepSeek大模型:从零实现流式对话+多轮会话管理(完整项目实战) —— SpringBoot整合、API安全封装、性能优化全攻略