高并发秒杀API之业务分析与DAO

1.秒杀业务的分析

一般的秒杀系统会存在商家,库存,用户三个实体,商家添加调整库存,库存用于发货和核账,库存用户秒杀或者预售,用户的付款,退货也会影响到库存集体如下图:


高并发秒杀API之业务分析与DAO_第1张图片
这里写图片描述

也就是秒杀业务的核心就是库存的处理。
库存业务分析:首先用户秒杀成功要相应的减去库存已经记录购买的明细,这两项操作组成了一个完整的事务。如下图:


高并发秒杀API之业务分析与DAO_第2张图片
这里写图片描述

2.难点分析的分析

主要的难点问题就是竞争多个用户同时秒杀一种商品。对于mysql 来说竞争反应到背后的技术就是事务和行级锁。
1.事务工作机制
首先是 开启事务 start transaction
update 库存数量 (竞争出现的地方)
insert 购买明细
commit 事务提交
2 行级锁

当一个用户执行减库存的操作时,其他用户执行该项操作时为等待状态如下图
高并发秒杀API之业务分析与DAO_第3张图片
这里写图片描述

秒杀的难点在于如何高效的处理竞争具体的解决方法会在单写一遍博客进行解释。接下来通过一个项目主要实现一下如下的秒杀功能。
高并发秒杀API之业务分析与DAO_第4张图片
这里写图片描述

3.设计数据库

因为主要只实现秒杀相关的功能这里只设置两张表。
1.秒杀库存表下面给出建表语句。


-- 创建秒杀库存表
CREATE TABLE seckill(
    `seckill_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '商品库存id',
    `name` VARCHAR(120) NOT NULL COMMENT '商品名称',
    `number` INT NOT NULL COMMENT '库存数量',
    `start_time` TIMESTAMP NOT NULL COMMENT '秒杀开始时间',
    `end_time` TIMESTAMP NOT NULL COMMENT '秒杀结束时间',
    `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒杀创建时间',
    PRIMARY KEY (`seckill_id`),
    /*创建时间索引是为了以后时间查询的业务提供方便*/
    KEY `idx_start_time` (`start_time`),
    KEY `idx_end_time` (`end_time`),
    KEY `idx_create_time` (`create_time`)
)ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表'

-- 初始化数据
INSERT INTO 
    seckill(name, number, start_time, end_time)
VALUES
    ('1000元秒杀iphone6', 100, '2015-11-01 00:00:00', '2018-11-02 00:00:00'),
    ('500元秒杀ipad2', 200, '2015-11-01 00:00:00', '2018-11-02 00:00:00'),
    ('300元秒杀小米4', 300, '2015-11-01 00:00:00', '2018-11-02 00:00:00'),
    ('200元秒杀红米note', 400, '2015-11-01 00:00:00', '2018-11-02 00:00:00')
  1. 秒杀成功明细表下面给出建表语句

-- 秒杀成功明细表
-- 用户登录认证相关的信息
CREATE TABLE success_killed(
    `seckill_id` BIGINT NOT NULL COMMENT '商品库存id',
    `user_phone` BIGINT NOT NULL COMMENT '用户手机号',
    `state` TINYINT NOT NULL DEFAULT -1 COMMENT '状态信息:-1无效,0成功,1已付款,2已发货',
    `create_time` TIMESTAMP NOT NULL COMMENT '创建时间',
    PRIMARY KEY (`seckill_id`, `user_phone`),/*联合主键*/
    KEY `idx_create_time` (`create_time`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀库存表'

4.DAO编码

1.创建工程
首先创建一个maven工程seckill工程目录如下


高并发秒杀API之业务分析与DAO_第5张图片
这里写图片描述

2.添加依赖


  4.0.0
  com.wen
  seckill
  0.0.1-SNAPSHOT
   
        org.springframework.boot
        spring-boot-starter-parent
        1.5.2.RELEASE
        
    
      
        UTF-8
        1.8
         3.0.2.RELEASE 
         2.1.1 
        7.0.69
    
    
        
            org.springframework.boot
            spring-boot-starter
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
            
        
        
        
            org.springframework.boot
            spring-boot-starter-log4j2
        
        
        
            org.springframework.boot
            spring-boot-starter-web
         
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.3.0
        
        
        
            com.github.pagehelper
            pagehelper-spring-boot-starter
            1.1.1
        
        
        
            mysql
            mysql-connector-java
            5.1.35
        
        
        
            org.apache.commons
            commons-lang3
            3.4
        
        
        
            com.google.guava
            guava
            21.0
        
        
        
            org.springframework.boot
            spring-boot-devtools
            true
        
        
        
            org.springframework.boot
            spring-boot-starter-test
        
        
        
            org.springframework.boot
            spring-boot-starter-aop
        
        
        
            com.alibaba
            fastjson
            1.2.31
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                    false
                    true
                
            
        
          
        seckill
    

3工程配置

#数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/seckill
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
  thymeleaf:
    mode: HTML5
  #字符集和json格式工具
  http:
    encoding:
      charset: utf-8
    converters:
      preferred-json-mapper: fastjson
    multipart:
      max-file-size: 10MB
  application:
    name: seckill
#mynatis配置
mybatis:
  type-aliases-package: com.wen.seckill.model
  #mapper加载路径
  mapper-locations: classpath:mapper/*.xml
  #myatbis配置文件
  config-location: classpath:mybatis-conf.xml
  
#加载log4j2
logging:
  config: classpath:log4j2.xml
  level: debug
  file:
server:
  session-timeout : 3600
  port: 80

日志配置文件



    
        
        %d{yyyy-MM-dd HH:mm:ss.SSS} |-%-5level [%thread] %c [%L] -| %msg%n
    

    
        
            
        
    
    
    

        
            
        
        
            
        
        
        
            
        
    


mybatis 一些功能的配置文件




    
    
        
        
        
        
        
        
        
        
        
        
        

4 dao层实体编写
根据表结构创建实体
库存表

import java.util.Date;
/**
 * 秒杀库存实体
 */
public class Seckill {

    private long seckillId;

    private String name;

    private int number;

    private Date startTime;

    private Date endTime;

    private Date createTime;

    public long getSeckillId() {
        return seckillId;
    }

    public void setSeckillId(long seckillId) {
        this.seckillId = seckillId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Seckill [seckillId=" + seckillId + ", name=" + name + ", number=" + number + ", startTime=" + startTime
                + ", endTime=" + endTime + ", createTime=" + createTime + "]";
    }

}

秒杀记录表

import java.util.Date;
/**
 * 成功秒杀实体
 * 
 */
public class SuccessKilled {

    private long seckillId;

    private long userPhone;

    private short state;

    private Date creteTime;

    // 多对一的复合属性
    private Seckill seckill;

    public long getSeckillId() {
        return seckillId;
    }

    public void setSeckillId(long seckillId) {
        this.seckillId = seckillId;
    }

    public long getUserPhone() {
        return userPhone;
    }

    public void setUserPhone(long userPhone) {
        this.userPhone = userPhone;
    }

    public short getState() {
        return state;
    }

    public void setState(short state) {
        this.state = state;
    }

    public Date getCreteTime() {
        return creteTime;
    }

    public void setCreteTime(Date creteTime) {
        this.creteTime = creteTime;
    }

    public Seckill getSeckill() {
        return seckill;
    }

    public void setSeckill(Seckill seckill) {
        this.seckill = seckill;
    }

    @Override
    public String toString() {
        return "SuccessKilled [seckillId=" + seckillId + ", userPhone=" + userPhone + ", state=" + state
                + ", creteTime=" + creteTime + "]";
    }

}

4 dao层借口编写
实体类接口
主要需要的功能有减库存,秒杀列表,根据id 检索商品信息


import java.util.Date;
import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import com.wen.seckill.model.Seckill;
@Mapper
public interface SeckillDao {
    /**
     * 减库存
     * @param seckillId
     * @param killTime
     * @return 如果影响的行数大于1 则表示更新库存成功
     */
    int reduceNumber(@Param("seckillId")long seckillId,@Param("killTime")Date killTime);
    /**
     * 根据id  查询秒杀对象
     * @param seckillId
     * @return 
     */
    Seckill queryById(@Param("seckillId")long seckillId);
    /**
     * 获取秒杀列表
     */
    List queryAll(); 
}

为接口编写相应的xml 代码




    
    
        update 
            seckill 
        set number=number-1
        where seckill_id=#{seckillId}
        AND start_time  <=#{killTime}
        and end_time>=#{killTime}
        and number>0
    
    
    
        
    

秒杀接口主要需要两个功能 1插入秒杀记录 2秒杀记录检索

import java.util.Date;
/**
 * 成功秒杀实体
 * 
 */
public class SuccessKilled {

    private long seckillId;

    private long userPhone;

    private short state;

    private Date creteTime;

    // 多对一的复合属性
    private Seckill seckill;

    public long getSeckillId() {
        return seckillId;
    }

    public void setSeckillId(long seckillId) {
        this.seckillId = seckillId;
    }

    public long getUserPhone() {
        return userPhone;
    }

    public void setUserPhone(long userPhone) {
        this.userPhone = userPhone;
    }

    public short getState() {
        return state;
    }

    public void setState(short state) {
        this.state = state;
    }

    public Date getCreteTime() {
        return creteTime;
    }

    public void setCreteTime(Date creteTime) {
        this.creteTime = creteTime;
    }

    public Seckill getSeckill() {
        return seckill;
    }

    public void setSeckill(Seckill seckill) {
        this.seckill = seckill;
    }

    @Override
    public String toString() {
        return "SuccessKilled [seckillId=" + seckillId + ", userPhone=" + userPhone + ", state=" + state
                + ", creteTime=" + creteTime + "]";
    }

}

为接口编写相应的xml 代码




    
    
        
        insert ignore into success_killed(seckill_id,user_phone) values(#{seckillId},#{userPhone})
    
    

5.单元测试

编写完相应的代码后自然要编写单元测试,测试相应的代码的正确性。
首先编写一个公用的单元测试类引入相应的测试注解配置

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
@WebAppConfiguration
public class BaseTest {

}

编写秒杀库存dao的单元测试给出测试数据测试秒杀库存dao中的三个方法。


public class SeckillDaoTest extends BaseTest {

    //注入Dao实现类依赖
    @Resource
    private SeckillDao seckillDao;
    
    @Test
    public void testQueryById()  {
        long id = 1000;
        try {
            Seckill seckill = seckillDao.queryById(id);
            System.out.println(seckill.getName());
            System.out.println(seckill);
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
    @Test
    public void testReduceNumber() throws Exception {
        Date killTime = new Date();
        int updateCount = seckillDao.reduceNumber(1000L, killTime);
        System.out.println("updateCount=" + updateCount);
    }
    @Test
    public void testQueryAll() throws Exception  {
        List seckills = seckillDao.queryAll();
        for (Seckill seckill : seckills) {
            System.out.println(seckill);
        }
    }

    

}

启动junit 查看测试结果。
编写秒杀记录dao的单元测试给出测试数据测试秒杀记录dao中的二个方法。


public class SuccessKilledDaoTest extends BaseTest {

    @Resource
    private SuccessKilledDao successKilledDao;

    @Test
    public void testInsertSuccessKilled() throws Exception {
        long id = 1001;
        long phone = 13631231234L;
        int insertCount = successKilledDao.insertSuccessKilled(id, phone);
        System.out.println("insertCount=" + insertCount);
    }

    @Test
    public void testQueryByIdWithSeckill() throws Exception {
        long id = 1001;
        long phone = 13631231234L;
        SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(id, phone);
        System.out.println(successKilled);
        System.out.println(successKilled.getSeckill());
    }

}

启动junit 查看测试结果。到此dao 层就算完成了 下一遍将接受service 层实现以及测试。
源码地址 :https://github.com/haha174/seckill.git
文章地址: http://www.haha174.top/article/details/256198
教程地址:http://www.imooc.com/learn/587

你可能感兴趣的:(高并发秒杀API之业务分析与DAO)