业务分析与DAO层
相关技术介绍
MySQL:表设计, SQL技巧, 事务和行级锁
MyBatis: DAO层的设计与开发, MyBatis的合理使用, MyBatis与Spring的整合
Spring: Spring IoC整合Service, 声明式事务的运用
Spring MVC: Restful接口的设计与实现, 框架的运作流程, Controller开发技巧
- 前端: 交互设计, Bootstrap, JQuery
- 高并发: 高并发点和高并发分析, 高并发性能优化
pom文件配置
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.seckill</groupId>
<artifactId>seckill</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>seckill Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<!--junit4 使用注解的方式进行测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--补全项目的依赖-->
<!--1: 日志 java日志:slf4j, log4j, logback, common-logging
slf4j 是规范/接口
日志实现:log4j logback common-logging
使用: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>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--3.DAO框架依赖-->
<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.2</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依赖-->
<!--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>
<!--spring DAO依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!--spring web依赖-->
<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>
<!--spring test 相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<finalName>seckill</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
秒杀系统的功能
- 秒杀接口暴露
- 执行秒杀
- 相关查询
代码开发阶段
- DAO设计编码
- Service设计编码
- Web设计编码
数据库设计编码
ENGINE = InnoDB
基于MyBatis的DAO实现
Mybatis特点:
- 参数
- SQL,SQL由自己写,灵活
- 封装出来一个Entity实体或者List
SQL写在什么地方:
- XML提供SQL—>推荐
- 注解提供SQL
如何实现DAO接口
- Mapper自动实现DAO接口—>推荐
- API编程方式实现DAO接口
基于MyBatis实现DAO接口
- 创建全局MyBatis-config.xml
- 创建一个目录,用来存放SQL映射
配置MyBatis->configration
- 从官方文档中拷贝xml规范
- 创建一个configration标签–配置全局属性
- 配置Settings
配置MyBatis -> mapper各个DAO映射
- 为DAO接口方法提供sql语句配置
MyBatis整合Spring
整合的目标
- 更少的编码
- 更少的配置
- 足够的灵活性
更少的编码
- 只写接口,不写实现
- 接口本身就可以说明很多事情
- 参数
- 行为–>通过接口的名字
- 返回值可以确定结果集—->调用SQL
更少的配置
- 别名org.seckill.entity.Seckill—>Seckill
- MyBatis会自动实现一个package scan的过程之后扫描到的东西加入MyBatis的别名系统中,Seckill,所以只需要原生的类名
- 配置扫描
- –>自动扫描配置文件
- 更少的配置
- —>自动实现DAO接口,自动注入Spring容器
- 足够的灵活性
- 自己定制SQL
- 自由传参
- 结果集自动赋值
- XML提供SQL, DAO接口Mapper
- 实际编码工作的完成—2018-5.14结束
Service层
Service层开发前的说明
- DAO层工作演变为:接口设计+SQL编写
好处:
- 代码和SQL分离,方便review
- DAO拼接等逻辑在Service层完成
- DAO是数据访问层的简称,这个层之关心对远程数据的操作,对SQL的操作 ,对Hbase的操作等,而对这些操作的组装想要做的事情应该放在Service层来完成
秒杀Service接口设计
coding…..
使用spring托管service依赖
Spring IoC功能理解
对象工厂 + 依赖管理–> 一致性的访问接口业务对象的依赖图
- SeckillService 依赖于 两个Dao, SeckillDao + SuccessSeckillDao
- SeckillDao + SuccessSeckillDao 依赖于 sqlSessionFactory因为使用的是MyBatis —> data Sourece
- 为什么使用IoC
- 对象创建同意托管
- 规范的声明周期管理
- 灵活的依赖注入
- 一致的获取对象 singleton
- spring-IoC注入方式和场景
- XML**
- 1.Bean实现类来自第三方类库,如DataSource等
- 2.需要命名空间的配置如context, aop,mvc等
- 注解**
- 项目中自身开发使用的类, 可直接在代码中使用注解如:@Service, @Contriller等
- Java配置类
- 用的比较少,适用于需要使用代码来控制对象创建逻辑的场景,如自定义修改依赖类库
- XML**
- 本项目中的 使用
- XMl配置第三方类库
- package-scan
- Annotation注解
coding
spring声明式事务
- 什么是声明式事务
开启事务->修改sql1->修改sql2->修改sql3->修改sqln->提交/回滚
用来解脱事务管理,全自动管理实务叫做声明式事务 - 声明式事务的使用方式
- ProxyFactoryBean + XML:早期使用2.0
- tx:advice+aop命名空间:一次配置永久生效
- 直接@Transactional:注解控制(推荐)
- 事务方法的嵌套
这个概念是spring声明式事务独有的
传播行为:propagation_required如果有一个事务直接加入,否则创建新事务 - 什么时候回滚事务
方法抛出运行期异常(RuntimeException)
小心不当的try catch
Web层
前端交互流程设计
- 秒杀系统使用到的技术
- Restful
- SpringMVC
- bootstrap + jQuery
- 1-2 设计Restful接口,Restful设计URL
- 秒杀API的URL设计
- GET /seckill/list —> 秒杀列表
- GET /seckill/{id}/detail —> 秒杀详情页
- GET /seckill/time/now —> 系统时间
- POST /seckill/{id}/exposer —> 暴露秒杀
- POST /seckill/{id}/{md5}/execution —> 执行秒杀
- SpringMVC
- 围绕Handler进行开发Handler-> 数据Model + 页面View
系统优化
高并发分析
CDN(Content Delivery Network) 内容分发网络
- 基本思想:避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过各个节点服务器所构成的在现在的互联网基础上的一层智能虚拟网络,CDN系统能够实时的根据网络流量和各个节点之间的连接,负载状况以及到用户的距离和响应时间等综合信息来将用户的请求导向离用户最近的服务器节点上。其目的就是使用户可以就近取得所需内容,解决Internet网络拥塞的状况,提高网站的响应速度,一般CDN上面放的都是一些静态的资源如:html js css jquery plugin等
- CDN的理解:
- 加速用户获取数据的速度,优酷,搜狐等。。。
- 部署在距离用户最近的网络节点上
- 命中CDN之后,不需要访问后端服务器
- 互联网公司自己搭建CDN或者租用CDN集群
获取系统时间分析
- 获取系统时间不用优化,因为一次内存访问大约10ns。获取系统时间大概可以再1s进行10亿次。
秒杀接口地址分析
- 无法使用CDN缓存
- 适合服务器缓存:redis等
- 一致性维护成本低
秒杀地址接口优化
- 请求接口 –> Redis –> MysQL
- 当下次缓存命中之后直接使用Redis的数据
- 一致性维护方便:设置缓存时间,超时直接穿透到MySQL。或者当MySQL更新的时候直接对Redis进行主动更新。
秒杀操作优化分析
- 无法使用CDN缓存
- 后端缓存困难:库存问题,无法使用Redis直接去减库存
- 一行数据竞争:热点商品–>对一行数据进行大量的update减库存操作。
其他方案分析
- 架构
- 使用Redis或者NoSQL实现一个原子计数器,保证原子性,使用这个原子计数器进行减库存操作,操作完成之后记录行为消息。
- 记录的行为消息放入一个分布式的MQ中如rebbitMQ,rocketMQ kafka。
- 后端服务器消费这个消息,并落地到mysql当中,记录
- 架构问题以及成本分析
- 运维成本和稳定性:Nosql MQ的稳定性和运维成本高昂
- 开发成本:数据的一致性,回滚方案等
- 幂等性难保证:重复秒杀问题的结局方案,是重新在维护一个数据结构用来记录那些用户已经秒杀过了
- 不适合新手的架构
什么是QPS:每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
回到最初的问题,为什么不用MySQL呢?难道是真的因为MySQL低效?
- 经过压力测试,对一行数据进行单纯的update操作大约4WQPS
- Java控制事务行为分析
- 一条update语句 update table set num = num - 1 where id = 10 and num > 0;
- 执行一条update语句就会有一条insert,插入购买明细。
- 与此同时进行的另一个用户也进行了update操作,这个操作就会被block等待行锁。
- 当第一个insert结束之后,这个事务就会commit/roolback 这个时候等待行锁的用户就会拿到这个lock。
- 整个系统就会被行锁限制为一个串行化的过程。
- 瓶颈分析
- update减库存 会有网络延迟,或者GC
- insert购买明细 网络延迟,GC
- commit/roolback
- 之后第二个等待的才能拿到这个行锁
- 优化分析
- 行级锁是在commit/roolback之后才会释放
- 优化的方向就是如何减少一个行级锁的持有时间
- 延迟分析
- 同城机房网络(0.5ms-2ms)max(1000qps)
- update之后JVM(50ms)max(20qps)
- 如果是异地机房,传输的时间会消耗大约20ms左右
- 如何判断update成功呢?
两个条件:
- update自身没有出错
- 客户端确认update影响的记录数
- 优化思路:
- 把客户端的逻辑放在MySQL服务器,避免网络延迟和GC的影响
- 如何放到MySQL服务器
- 定制SQL方案:早期阿里做法:update /* + auto_commit */,成本很高需要修改MySQL源码。
- 使用存储过程:整个事务在MySQL端完成
- 优化总结
- 前端控制:暴露接口,按钮防重复
- 动静态的数据分离:CDN缓存,后端缓存Redis memcached
- 事务竞争优化:减少事务锁的时间
redis后端优化编码
- 启动redis数据库命令:
首先切换到redis安装目录下进行如下操作:
- 启动redis服务:
./redis-server.exe redis.windows.conf - 连接redis服务器:
./redis-cli.exe -h 127.0.0.1 -p 6379
- 启动redis服务:
简单优化SQL
- 问题是什么?
- 整个事务的流程就是首先拿到行级锁然后进行update操作,然后经过网络延迟和GC的时间
- 第二步进行insert操作,经过网络延迟和GC
- 如果第二步成功或者失败,mysql选择commit/roolback释放行级锁
- 优化
- 首先镜像insert操作,挡住一部分的重复秒杀
- 第二步拿到行级锁进行update操作
- commit/roolback释放行级锁
重点调整源码的顺序
深度优化
- 事务SQL在MySQL端执行(存储过程)
-- 秒杀执行的存储过程
DELIMITER $$ -- console ; 转换为
$$
-- 定义存储过程
-- 参数:in 输入参数。 out 输出参数
-- row_count() 返回上一条修改类型sql影响的行数(delete insert update)
-- row_count(): 0: 未修改数据 >0: 表示修改的行数 <0: sql错误/未执行修改sql
CREATE PROCEDURE `seckill`.`execute_seckill`
(IN v_seckill_id BIGINT, IN v_phone BIGINT,
IN v_kill_time TIMESTAMP, OUT r_result INT)
BEGIN
DECLARE insert_count INT DEFAULT 0;
START TRANSACTION;
INSERT IGNORE INTO success_killed
(seckill_id, user_phone, create_time)
VALUES (v_seckill_id, v_phone, v_kill_time);
SELECT row_count()
INTO insert_count;
IF (insert_count = 0)
THEN
ROLLBACK;
SET r_result = -1;
ELSEIF (insert_count < 0)
THEN
ROLLBACK;
SET r_result = -2;
ELSE
UPDATE seckill
SET number = number - 1
WHERE seckill_id = v_seckill_id
AND end_time > v_seckill_id
AND start_time < v_seckill_id
AND number > 0;
SELECT row_count()
INTO insert_count;
IF (insert_count = 0)
THEN
ROLLBACK;
SET r_result = 0;
ELSEIF (insert_count < 0)
THEN
ROLLBACK;
SET r_result = -2;
ELSE
COMMIT;
SET r_result = 1;
END IF;
END IF;
END $$
-- 存储过程定义结束
DELIMITER ;
set @r_result = -4;
-- 执行存储过程
call execute_seckill(1010, 13498238734, now(), @r_result);
-- 获取结果
SELECT @r_result;
-- 存储过程:
-- 1.存储过程优化:事务行级锁的持有时间
-- 2.不要过度的依赖存储过程
-- 3.简单的逻辑可以应用存储过程
-- 4.QPS:一个秒杀单6000/qps
大型系统部署架构
部署可能用到那些服务
- CDN,静态,和强制静态,降低服务器请求量
- webServer:Nginx集群部署在多个服务器上,用来做http服务器,还会把后端的jetty tomcat 这样的servlet容器做一个反向代理
- Redis:用来做服务器端的缓存,通过redis的api达到热点数据的快速存取过程
- MySQL:借助MySQL事务,来达到数据的一致性和完整性
大型系统部署的架构是什么样的?
- 流量:
- 一部分被CDN缓存拦截,但是秒杀操作会来到我们的服务器
- 我们的服务器会通过智能DNS解析查找到服务器地址
- 一般是多个Nginx地址,部署在不同的机房,请求最近的Nginx服务器。Nginx还会帮助servlet容器做负载均衡
- 之后会是我们的逻辑集群类似于tomcat jetty的集群。
- 访问tomcat jetty集群的时候会访问到我们的缓存集群比如Redis集群。
- 之后是我们的MySQL,MySQL会通过关键的id比如秒杀id来进行分库分表的操作。会根据mod(seckillId)做一个求模运算来分表,一般分表会是512张或者1024张防备。一般会使用一个分库分表的框架比如:阿里的TDDL
- 之后一般还会有一个统计分析的组件 可以是Hadoop这个的存储服务,做各种数据统计报表
- 可能参与的角色:
- 开发:前端+后端
- 测试:高端的测试会进行压力测试
- DBA:与DBA商议分库分表的资源和机器,以及存储过程的
- 运维:负责机器,甚至是Nginx,高性能高可用的集群
课程总结
数据库的设计与实现
- 首先的手写DDL等语句
- 完成数据库schema的设计与实现
MyBatis理解与使用技巧
- 设计我们的Dao接口
- 每个Dao接口都会对应于一条sql语句
- 上层的service会调用组合dao来实现我们的业务逻辑
MyBatis和Spring的整合和搞笑使用
- 通过配置自动的包扫描和sql.xml文件的扫描及别名的识别,配置之后无需修改
业务层的技术回顾
业务接口的设计与封装
- 站在“使用者”的角度去设计一个接口
springIoC的配置技巧
- 对于第三方类库和一次性配置像声明式事务,使用xml配置
- 对于我们自己开发的dao,service,controller使用注解
- 加上包扫描可以非常完整并简单的完成注入
spring的声明式事务的使用和理解
- 在标注方法的过程当中,应该是这个方法可以快速完成
- 同时掌握声明式事务何时回滚 何时提交。
Web层的技术回顾
Restful接口运用
- Restful接口的使用
- Restful接口设计的规范,我们使用过不同的提交方式,达到描述行为的目的,比如写通过POST,读通过GET
SpringMVC的使用技巧
- 如何配置
- 参数映射
- 如何打包成json返回给浏览器
前端交互
- 前端交互式很重要的点,不能忽略
Bootstrap和JS的使用
- Bootstrap包含css和js的组件库,快速开发用户体验很好的页面
- JQuary和jQuery插件,如何优雅的书写js代码。js使用json的书写格式,进行分模块
并发优化
系统的瓶颈分析
- 如何分析
- 如何识别
- 重点优化
事务,锁,网络延迟的理解
- 对mysql的锁的理解
- 对java程序可能还有GC的影响
- 对于长期持有锁的程序,需要考虑为网络延迟的影响
前端,CDN,后端缓存Redis的使用和理解
- 各个部分应用于那个层面
- redis的使用
集群化部署
- 集群化部署的了解