Java高并发秒杀API(一)之业务分析与DAO层

2022-01-11 13:44:39 浏览数 (1)

本SSM实战项目使用了Maven进行依赖管理,如果有不清楚Maven是什么的可以参考这篇文章

1. 创建Maven项目和依赖

1.1 创建项目前需要先安装Maven,并设置好环境变量

  • Maven下载
  • 设置环境变量
  • 新建变量MAVEN_HOME,值为Maven的目录X:XXXapache-maven-XXX
    • %MAVEN_HOME%bin添加到Path变量下
  • 运行CMD,输入mvn -v后可以看到Maven的版本信息等则表示安装成功

1.2 创建Maven项目有两种方式,如下

第一种创建方式:使用命令行手动创建

1

mvn archetype:generate -DgroupId=com.lewis.seckill -DartifactId=seckill -Dpackage=com.lewis.seckill -Dversion=1.0-SNAPSHOT -DarchetypeArtifactId=maven-archetype-webapp

在视频中使用的是archetype:create,该方法已被废弃,请使用archetype:generate来创建。命令行执行后会创建一个maven-archetype-webapp骨架的Maven项目,其中groupId是项目组织唯一的标识符,实际对应JAVA的包的结构;artifactId是项目的唯一的标识符,实际对应项目的名称;package一般是groupId artifactId,是自动生成的,可以修改

第二种创建方式:借助IDE工具的Maven插件来创建项目

Eclipse安装Maven插件

  • 不知道怎么Maven插件的请参考该博文,推荐使用link方式手工安装的方式
    • 如果是手工安装Maven插件的,可能会缺少pom.xml 图形化编辑工具,请另外添加进去,具体情况请参考该博文
    • 已经安装了Maven插件的请走下一个步骤
  • FileNewOther...Maven ProjectNext,进入如下界面

Maven1

  • 点击Next,选择要构建的骨架maven-archetype-webapp,如下图

Maven2

  • 点击Next,填写groupId=com.lewis.seckillDartifactId=seckillpackage=com.lewis.seckill(根据实际情况填写),然后Finish

如果是第一次使用Eclipse的Maven插件来创建Maven项目的可能会遇到一些问题,可以参考该博文

1.3 修改pom.xml文件

当创建完Maven项目后会在根目录下有一个pom.xml文件,Maven项目通过pom.xml进行项目依赖的管理,如果没有该xml文件,Eclipse不会将该项目当作一个Maven项目

添加项目需要的jar包依赖

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lewis</groupId> <artifactId>seckill</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>seckill Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--补全项目依赖 --> <!--1.日志 java日志有:slf4j,log4j,logback,commons-logging slf4j,commons-logging:是规范/接口 日志实现:log4j,logback 使用:slf4j logback --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.1</version> </dependency> <!--实现slf4j接口并整合 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.1</version> </dependency> <!--2.数据库相关依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.35</version> <scope>runtime</scope> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.1</version> </dependency> <!--3.dao框架:MyBatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.3.0</version> </dependency> <!--mybatis自身实现的spring整合依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.3</version> </dependency> <!--4.Servlet web相关依赖 --> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.4</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <!--5:spring依赖 --> <!--1)spring核心依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.7.RELEASE</version> </dependency> <!--2)spring dao层依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.1.7.RELEASE</version> </dependency> <!--3)springweb相关依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.1.7.RELEASE</version> </dependency> <!--4)spring test相关依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.1.7.RELEASE</version> </dependency> <!--添加redis依赖 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.3</version> </dependency> <!--prostuff序列化依赖 --> <dependency> <groupId>com.dyuproject.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.0.8</version> </dependency> <dependency> <groupId>com.dyuproject.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>1.0.8</version> </dependency> </dependencies> <build> <finalName>seckill</finalName> <resources> <!--打包时包含源代码包下的资源文件 默认情况下只会打包src/main/java下的源代码 --> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> </build> </project>

关于maven依赖的简化写法

教学视频中老师写了很多的依赖,但其实这里面有一些是可以省略不写的,因为有些包会自动依赖其它的包(Maven的传递性依赖)。这里面可以省略的依赖有:spring-core;spring-beans(上面这两个spring-context会自动依赖);spring-context,spring-jdbc(mybatis-spring会依赖);spring-web(spring-webmvc会依赖);logback-core(logback-classic会依赖)

有想要了解Maven的依赖范围与传递性依赖的请参考该博文

2. 秒杀业务分析

2.1 业务分析

秒杀业务的核心是对库存的处理,其业务流程如下图

1.png

用户针对库存业务分析

当用户执行秒杀成功时,应该发生以下两个操作:

  • 减库存
  • 记录购买明细

这两个操作属于一个完整事务,通过事务来实现数据落地

为什么需要事务?

  • 减库存却没有记录购买明细,会导致商品少卖
  • 记录购买明细却没有减库存,会导致商品超卖

在实际中,以上都是很严重的事故,会给商家或买家带来损失,这是不能被允许的。一旦发生这种事故,事故责任很自然的就会去找设计实现业务的程序员

如何实现数据落地?

MySQL与NoSQL两种数据落地的方案

  • MySQL属于关系型数据库,而MySQL内置的事务机制来可以准确的帮我们完成减库存和记录购买明细的过程。MySQL有多种存储引擎,但只有InnoDB存储引擎支持事务。InnoDB支持行级锁和表级锁,默认使用行级锁
  • NoSQL属于非关系型数据库,近些年来在数据存储方面承担了很大的职责,但是对于事务的支持做的并不是很好,更多追求的是性能、高复用、分布式。

事务机制依然是目前最可靠的数据落地方案。

数据落地与不数据落地

  • 落地数据:就是被持久化的数据,这种数据一般放在硬盘或是其他的持久化存储设备里,例如:图片、系统日志、在页面上显示的数据以及保存在关系数据库里的数据等等,落地数据一定会有一个固定的载体,他们不会瞬时消失的。
  • 不落地数据:一般指存储在内存或者是网络传输里的数据,这些数据是瞬时,使用完毕就会消失,例如:我们在浏览器发送给服务器的请求;从数据库读取出来的一直到页面展示前的数据等等。
  • “不落地”传输能够满足用户在性能上的要求。

2.2 使用MySQL实现秒杀的难点分析

难点问题:如何高效地处理竞争?

当一个用户在执行秒杀某件商品时,其他也想要秒杀该商品的用户就只能等待,直到上一个用户提交或回滚了事务,他才能够得到该商品的锁执行秒杀操作。这里就涉及到了锁的竞争。

2.jpg

对于MySQL来说,竞争反应到背后的技术是就是事务 行级锁:

start transaction(开启事务)→ update库存数量 → insert购买明细 → commit(提交事务)

在秒杀系统中,在同一时刻会有很多用户在秒杀同一件商品,那么如何高效低处理这些竞争?如何高效地提交事务?这些将在Java高并发秒杀API(四)之高并发优化进行分析总结。

实现哪些秒杀功能?

下面先以天猫的秒杀库存系统为例,如下图

3.jpg

可以看到,天猫的秒杀库存系统是很复杂的,需要很多工程师共同开发。在这里,我们只实现秒杀相关的功能

  • 秒杀接口暴露
  • 执行秒杀
  • 相关查询

为什么要进行秒杀接口暴露的操作?

现实中有的用户回通过浏览器插件提前知道秒杀接口,填入参数和地址来实现自动秒杀,这对于其他用户来说是不公平的,我们也不希望看到这种情况

3. DAO层设计

3.1 创建数据库

源码里有个sql文件夹,可以给出了sql语句;也可以选择自己手写。数据库一共就两个表:秒杀库存表、秒杀成功明细表。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

-- 数据库初始化脚本 -- 创建数据库 CREATE DATABASE seckill; -- 使用数据库 use seckill; CREATE TABLE seckill( `seckill_id` BIGINT NOT NUll AUTO_INCREMENT COMMENT '商品库存ID', `name` VARCHAR(120) NOT NULL COMMENT '商品名称', `number` int NOT NULL COMMENT '库存数量', `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `start_time` TIMESTAMP NOT NULL COMMENT '秒杀开始时间', `end_time` TIMESTAMP NOT NULL 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,'2016-01-01 00:00:00','2016-01-02 00:00:00'), ('800元秒杀ipad',200,'2016-01-01 00:00:00','2016-01-02 00:00:00'), ('6600元秒杀mac book pro',300,'2016-01-01 00:00:00','2016-01-02 00:00:00'), ('7000元秒杀iMac',400,'2016-01-01 00:00:00','2016-01-02 00:00:00'); -- 秒杀成功明细表 -- 用户登录认证相关信息(简化为手机号) CREATE TABLE success_killed( `seckill_id` BIGINT NOT NULL COMMENT '秒杀商品ID', `user_phone` BIGINT NOT NULL COMMENT '用户手机号', `state` TINYINT NOT NULL DEFAULT -1 COMMENT '状态标识

0 人点赞