• 保存到桌面  加入收藏  设为首页
SSM/SSH

Java高并发秒杀API之业务分析与DAO层,Service层,Web层,高并发优化

时间:2017-09-19 15:50:43   作者:江节胜   来源:胜行天下   阅读:552   评论:0
Java高并发秒杀API之业务分析与DAO层,Service层,Web层,高并发优化 过程记录

本项目是Java EE完成(jee只处理了项目创建过程,后期开发的主要过程都是在IntelliJ IDEA 15.0.2中完成)
第一期:Java高并发秒杀API之业务分析与DAO层 20170425左右开始
第二期:Java高并发秒杀API之Service层 2017-05-20开始
第三期:Java高并发秒杀API之Web层 2017-05-20开始
第四期:Java高并发秒杀API之高并发优化 2017-06-01号开始


注意:自动导包输入是大小写敏感的.

1、秒杀具备典型的"事务"特性、秒杀/红包类的需求越来越多、面试常见问题

2、学习框架的使用和整合技巧、秒杀的分析过程以及优化

3、技术介绍 
	MySQL: 表设计  SQL技巧   事务和行级锁
	MyBatis DAO层设计与开发 MyBatis合理利用 MyBatis与Spring整合
	Spring:Spring IOC整合Service 声明式事务运用
	SpringMVC Restful接口设计和使用 框架运行流程 Controller开发技巧
	前端:交互设计 Bootstrap jQuery
	高并发:高并发点和高并发分析 优化思路并实现

4、基于Maven创建项目
	从0开始创建项目 从官网获取相关配置 使用Maven创建项目
	为什么从官网获取资源:文档更全民权威 避免过时或错误
	官网地址:logback配置:http://logback.qos.ch/manual/configuration.html
		   Spring配置:http://docs.spring.io/spring/docs/	
		   MyBatis配置:http://mybatis.github.io/mybatis-3/zh/index.html

5、先安装Maven http://jingyan.baidu.com/article/1709ad808ad49f4634c4f00d.html
	下载 -> 
	安装 -> 直接放在C盘
	添加环境变量-> 
	生成本地仓库->  mvn help:system 命令然后回车 会在C:\Users\Administrator\.m2\repository生成文件(Administrator路径可能不能系统也会不同)
		成功打印详细信息,最后一条显示MAVEN_PROJECTBASEDIR=C:\Users\Administrator
	自定义用户配置 -> C:\Users\Administrator\.m2 \settings.xml (来源C盘解压目录)
	
6、开始创建项目
	maven命令创建web骨架项目
	mvn archetype:create -DgroupId=org.seckill -DartifactId=seckill -
	DarchetypeArtifactId=maven-archetype-webapp
	
	使用这句创建 
	
	mvn archetype:generate -DgroupId=org.seckill -DartifactId=seckill -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeCatalog=internal
	
	(高版本中使用 archetype:generate而不是 archetype:create  ,参数 -DarchetypeCatalog=internal 不让执行过程卡主等待很久)
	
	注意:
	注意先不使用IDE工具创建项目,使用mvn创建最基本的项目结构后,
	使用eclipse import -> Maven -> Existing Maven Projects->选择创建项目根文件夹->自动勾选pom.xml
	使用IntelliJ IDEA,File —> Open 复制  目录的路径后直接打开 D:\workspace\idea\seckill
	(使用eclipse可能导致dependency不能提示自动补全)
	依赖敲完后clean一下项目开始自动更新或者下载jar
	
7、处理默认创建的项目问题
	切换servlet更高的版本:直接在别的项目中复制WEB-INF/web.xml 
	
8、main文件夹下新建Java,放置Java源文件,在src目录下新建一个test文件夹并新建java、resources配置

9、查看pom.xml
	把默认3.8.1的junit改成4.11版本(通过注释的方式运行junit)
	继续补全依赖
 	日志  Java日志 slf4j,log4j,logback,common-logging
		slf4j: 是规范/接口
		日志实现:log4j,logback,commont-logging
		使用:slf4j + logback
	注意:从视频中看只要添加artifactId,其他两个值可以自动提示 
	<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.3.0</version>
	</dependency>
	
	<!--2、 数据库相关依赖 -->
	<scope>runtime</scope>
	<!-- 数据库的连接池 (优化数据库操作)-->
	<!-- DAO框架:MYbatis依赖 (数据库持久化) -->
	<!-- mybatis自身实现的整合依赖  -->
	<!--3、 Servlet web相关依赖 -->
	<!-- 4、Spring依赖 -->
	<!-- 1)spring核心依赖 -->
	<!-- 2)spring dao层依赖 -->
	<!-- 3)spring web依赖 -->
	<!-- 4)spring test依赖 -->
	
	
	添加完依赖后自行Ctrl B,Build ALL
	
10、秒杀业务分析
	针对库存做处理
	针对用户:减库存、记录购买明细=>完整事务=>数据落地
	
	关于数据落地:MySQL VS NoSQL(可以理解为所有非关系型数据库 追求性能、高可用、分布式,对事务支持不是很好)
	
	事务机制依然是目前最可靠的落地方案
	
11、MySQL实现秒杀的难点分析
	"竞争":事务+行级锁(等待:如果高效的处理竞争)
	
12、秒杀功能
	秒杀接口暴露(防止提前暴露秒杀地址)
	执行秒杀
	相关查询
	
13、代码开发阶段
	DAO设计编码
	Service设计编码
	Web设计编码
	
14、数据库编码

	--MySQL数据库使用5.7版本--
	使用之前启动服务
	net start MYSQL57 (MYSQL57 是安装MySQL是设置的)

	新建sql包并新建schema.sql
	手写代码
	注意对表属性设置的方法
	ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT="秒杀库存表";
	创建索引
	PRIMARY KEY (seckill_id),
	key idx_start_time(start_time),
	key idx_create_time(create_time),

	初始化数据(values可以连续写)
	insert into seckill(name,number,start_time,end_time)
	values
	('1000元秒杀iPhone6',100,'2015-11-01 00:00:00','2015-11-02 00:00:00'),
	('500元秒杀iPad2',100,'2015-11-01 00:00:00','2015-11-02 00:00:00'),
	('300元秒杀小米4',100,'2015-11-01 00:00:00','2015-11-02 00:00:00'),
	('200元秒杀红米note',100,'2015-11-01 00:00:00','2015-11-02 00:00:00'),
	
	/*联合主键*/
	PRIMARY KEY(seckill_id,user_phone),
	
	查看创建表的语句包括语句
	show create table seckill\G
	
15、开始DAO层实体和接口编码

	Table => Entity
	在java包下新建org.seckill.entity org.seckill.dao
	继续创建entity下的实体Seckill SuccessKilled
	继续新建dao包下的接口 SeckillDao SuccessKilledDao

16、基于MyBatis实现DAO
	
	数据库 <=> 映射 <=> 对象
	MyBatis HIBERNATE 都是工作在映射这一层 ORM(Object Relational Mapping 对象关系映射)
	
	特点:参数 + SQL = Entity/List
	
	SQL写在哪? XML提供SQL(推荐) 注解提供SQL
	如何DAO接口? Mapper自动实现DAO接口(推荐) API编程方式实现DAO接口
	
17、使用MyBatis编码
	首先在resource文件夹下新建mybatis全局配置文件 mybatis-config.xml
	再创建一个目录 mapper,放mybatis映射的文件
	
	http://www.mybatis.org/mybatis-3/zh/getting-started.html(中文)
	http://www.mybatis.org/mybatis-3/getting-started.html
	
	<?xml version="1.0" encoding="UTF-8" ?>
	<!DOCTYPE configuration
	  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
	  "http://mybatis.org/dtd/mybatis-3-config.dtd">
	  
	  复制这么多
	  <configuration>
	<!-- 配置全局属性 -->
	<settings>
		<setting name="" value="" />
	</settings>
	</configuration>
	
18、在mapper在新建SeckillDao.xml 注意命名规范
	添加
	<mapper namespace="org.seckill.dao.SeckillDao"> ---指定命名空间,就是指定具体位置
		<!-- 目的:为dao接口方法提供sql语句配置 -->
		<update id="reduceNumber">
			update 
				seckill
			set 
				number = number-1
			where seckill_id = #{seckillId}
			and start_time <![CDATA[ <= ]]>  #{killTime}---<= 可能和原生语言混淆
			and end_time >= #{killTime}
			and number>0;
		</update>
	</mapper>
	
	这里的参数都是直接来源于org.seckill.dao.SeckillDao中的接口方法
	resultType="Seckill" parameterType="long" 多个参数的话就不要指定?
	一般情况下,指定类型需要指定包名+类名,这里直接使用类名,还需要配置一下
	
19、
 	<select id="queryByIdWithSeckill" resultType="SuccessKilled">
 		<!-- 根据id查询SuccessKilled并携带秒杀产品对象 -->
 		<!-- 如果告诉MyBatis把结果映射到SuccessKilled同时映射seckill属性 -->
 		<!-- 可以自由控制SQL -->
 		select 
 			sk.seckill_id,
 			sk.user_phone,
 			sk.create_time,
 			sk.state,
 			s.seckill_id "seckill.seckill_id",---Mybatis可以省略as,加""为了防止执行混淆
 			s.name "seckill.name",
 			s.number  "seckill.number",
 			s.start_time  "seckill.start_time",
 			s.end_time  "seckill.end_time",
 			s.create_time  "seckill.create_time"
 		from success_killed sk
 		inner join seckill s on sk.seckill_id=s.seckill_id
 		where sk.seckill_id=#{seckillId};
 	</select>
 	
20、MyBatis整合Spring
 	整合目标
 	--更少的编码:
 		只写接口,不写实现.
 		接口本身能说明很多事(参数 方法名(行为) 返回类型) => SQL
 	--更少的配置
 		别名:org.seckill.entity.Seckill => Seckill
 			(package scan => Seckill,Killed....)
 		配置扫描:
 			<mapper resource="mapper/SeckillDao.xml"/> => 自动扫描配置文件
 		dao实现:
 			<bean id="ClubDao" class="...ClubDao"/> => 自动实现DAO接口/自动注入Spring容器
 	--足够的灵活性
 		自己定制SQL
 		只有传参
 		结果集自动赋值
 		
 	XML提供SQL DAO接口Mapper
 	
21、开始配置整合mybatis过程
 	在resource下新建spring,并生成spring-dao.xml
 	http://docs.spring.io/spring/docs/current/spring-framework-reference/
 	下载pdf后搜 7.2 Container overview

 	这个头部信息不够完整,实测可运行的如下
 	<?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    </beans>

22、开始单元测试
	(先确认数据库服务已经启动)
	新建test.java.org.seckill.dao 并新建SeckillDaoTest.java

23、注意
        // org.apache.ibatis.binding.BindingException: Parameter 'offset' not found. Available parameters are [0, 1, param1, param2]
        // List<Seckill> queryAll(int offset,int limit);
        // java 没有保存新参的记录queryAll(int offset,int limit)->queryAll(arg0,arg1)
        //改成 List<Seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);
        // (多个参数时需要配置)


    第一期结束

24、Java高并发秒杀API之Service层开始 -2017-05-20
    DAO编码之后的思考:DAO层工作演变为:接口设计+SQL编写
    DAO(Data Access Object) 数据访问对象是一个面向对象的数据库接口
    代码和SQL的分离,方便Review
    DAO拼接等逻辑在Service层完成

25、在org.seckill中新建service和exception包
    再创建dto包:数据传输层 (web和service之间的数据传递,entity是业务相关)

26、在service下新建SeckillService的接口
    业务接口:站在"使用者"的角度设计接口
      1、方法定义粒度 2、参数 3、返回类型(return 类型/异常)
    在dto中新建Exposer类 -> 暴露秒杀地址DTO
    在dto中新建SeckillExecution类 -> 封装秒杀执行后结果
    在exception中新建RepeatKillException类 extends RuntimeException -> 重复秒杀异常
    在exception中新建SeckillCloseException类 extends RuntimeException -> 秒杀关闭异常
    在exception中新建SeckillException类 extends RuntimeException -> 秒杀相关业务的异常
    将前两个异常继承改成SeckillException

27、写接口的实现
    在service下新建impl,并新建SeckillServiceImpl 实现SeckillService
    并具体实现对应的方法
    生成md5 private String getMD5(long seckillId) {
                  String base = seckillId + "/" + salt;
                  String md5 = DigestUtils.md5DigestAsHex(base.getBytes());//DigestUtils有Spring提供
                  return md5;
              }
      //所有编译期异常转换成运行期异常? //运行期异常会rollback

        } catch (SeckillCloseException e1) {//先catch一些异常
                  throw e1;
        } catch (RepeatKillException e2) {
                  throw e2;
        } catch (Exception e) {
               logger.error(e.getMessage(), e);
             //所有编译期异常转换成运行期异常 运行期异常会rollback
        throw new SeckillException("seckill inner error" + e.getMessage());
    }

28、新建enums包存放常量
    SeckillStateEnum
    注意枚举中可是使用 values()得到所有的枚举常量

29、基于Spring管理Service依赖
    Spring IOC功能理解:依赖注入 控制反转(Inversion of Control,英文缩写为IoC)
    对象工厂 + 依赖管理 -> 一致的访问接口
    业务对象依赖图

                     ↗    SeckillDao   ↘  //体现在SeckillServiceImpl.java
    SeckillService <                       > SqlSessionFactory
                     ↘ SuccessKilledDao ↗           ↓
                                                      ↓
                                                  DataSource...
    为什么使用IOC
        对象创建统一托管
        规范的生命周期管理
        灵活的依赖注入
        一致的获取对象(默认都是单例singleton)
    Spring-IOC注入方式和场景
        XML:1、Bean实现来自第三方类库,如:DataSource等;2,需要命名空间配置,如:context,aop,mvc等
        注解:项目中自身开发使用的类,可直接在代码中使用注解如:@Service,@Controller等
        Java配置类:需要通过代码控制对象创建逻辑的场景 如:自定义修改依赖类库

    本项目IOC使用
        XML配置 ->  package-scan ->  Annotation注解

30、在spring包下新建spring-service.xml
    new -> XML Configuration File -> Spring Config -> 创建并自动生成头部信息
    但是头部信息好像不全,所以最好还是复制。

      <context:component-scan base-package="org.seckill.service"/>
      <!--扫描service包下所有使用注解的类型-->
      再到service实现类中添加注解
      SeckillServiceImpl.java
      //@Component(统称) @Service @Dao @Controller
        @Autowired // @Resource @Inject

31、Spring声明式事务
    什么是生命式事务(解脱事务代码,交给第三方框架)
    开始事务 -> 修改SQL1 -> 修改SQL2 -> 修改SQL3 -> 修改SQLn -> 提交/回滚事务
    声明式事务使用方式
        ProxyFactoryBean + XML --> 早期使用方式(2.0)
        tx:advice+aop命名空间 --> 一次配置永久生效
        注解 @Transactional --> 注解控制(推荐)
    事务方法嵌套
        声明式事务独有的概念
            传播行为 -> propagation_required(新事务加入到原有事务中,没有就创建新事务)
                        ...
    什么时候回滚事务
        抛出运行期异常(RuntimeException)
        小心不当的try-catch(Spring没有感觉到异常)

32、配置Spring声明式事务
    在spring-service.xml中配置
    然后再SeckillServiceImpl.java中的executeSeckill()方法添加@Transactional
     /**
         *使用注解控制事务方法的优点
         *1:开发团队一致约定,明确标注事务方法的编码风格
         *2:保证事务方法的执行时间尽可能短,不要穿插其他的网络操作,RPC/HTTP请求或者剥离到事务方法外部
         *3:不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制(注:这个说法跟其他的视频介绍的优点不同)
         */
        
33、单元测试
    配置Ctrl + Shift + 创建单元测试类
    http://blog.csdn.net/jiaotuwoaini/article/details/52767007
    在SeckillService.java的类名后Ctrl+Shift+T,创建单元测试类(未生效)
    手动创建 org.seckill.service包和SeckillServiceTest类

    给Test类添加注解
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration({
            "classpath:/spring/spring-dao.xml",
            "classpath:/spring/spring-service.xml"})
            注意多个xml实验数组(Java的数据初始化是{})

    slf4j的日志工具
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    相关配置 https://logback.qos.ch/manual/configuration.html
    在resources下新建logback.xml
    复制(并加入XML头)
       <configuration debug="true">
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
          <!-- encoders are  by default assigned the type
               ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
          <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
          </encoder>
        </appender>
        <root level="debug">
          <appender-ref ref="STDOUT" />
        </root>
      </configuration>

34、执行测试
    testExportSeckillUrl()注意seckill表确定数据中数据(开始时间、结束时间)
    logger.info("seckillExecution----------------{}", seckillExecution);
    //返回的一直是null 和 {},具体问题原因未知,toSting()都写了

    新增一个单元测试方法合并
    testExportSeckillUrl()
    testExecuteSeckill()
    为testSeckillLogic()

    Java高并发秒杀API之Service层 2017-05-20结束

 35、Java高并发秒杀API之Web层 2017-05-20开始

    前端交互设计
    Restful
    SpringMVC
    Bootstrap+jQuery

    前端页面流程:
       列表页   →   详情页
                     ↓
      登录操作  ←  login
         ↓          ↓
     写入Cookie →  展示逻辑

     详情页流程逻辑:

                            1:获取标准系统时间
                                     ↓
     4:        3: ←  倒计时  ←  2:时间判断
     执行  ←  秒杀                  开始时间    秒杀
     秒杀      地址       ←         结束时间 → 结束
      ↓
     结束

36、什么是Restful
    兴起于Rails    一种优雅的URI表述方式    资源的状态和状态转移
    GET     /seckill/list
    POST    /seckill/execute/{seckillId} ╳
            /seckill/{seckillId}/execution √(资源的状态)
    DELETE  /seckill/{id}/delete

    GET     -> 查询操作
    POST    -> 添加/修改操作
    PUT     -> 修改操作
    DELETE  -> 删除操作

    URL设计
    /模块/资源/{标示}/集合1/...
    /user/{uid}/friends -> 好友列表
    /user/{uid}/followers -> 关注着列表

    秒杀API的URL设计
    GET /seckill/list 秒杀列表
    GET /seckill/{id}/detail 详情页
    GET /seckill/time/now 系统时间
    POST /seckill/{id}/exposer 暴露秒杀
    POST /seckill/{id}/{md5}/execution 执行秒杀

37、使用SpringMVC框架理论
    围绕Handlder开发
                        数据Model
            Handler →
                        页面View

38、HTTP请求地址映射原理
                            Servlet容器(Tomcat,Jetty等)

     Http请求 → SpringMVC Handler Mapping注解,XML编程等 → Handler处理方法

     注解映射技巧
     @RequestMapping注解:
        (1)支持标准的URL
        (2)Ant风格URL (?和*和**等字符)
        (3)带(xxx)占位符的URL.
       例如
        /user/*/creation
            匹配/user/aaa/creation、/user/bbb/creation等URL
        /user/**/creation
            匹配/user/creation、/user/aaa/bbb/creation等URL
        /user/{userId}
            匹配/user/123、/user/abc等URL
        /company/{companyId}/user/{userId}/detail
            匹配/company/123/user/456/detail等URL

    请求方法细节处理
    1、请求参数绑定
    2、请求方式限制
    3、请求转发和重定向
    4、数据模型赋值
    5、返回json数据
    6、cookie访问
                              例    子
    @RequestMapping(value = "/{seckill}/detail",method=RequestMethod.GET)
    public String detail(@PathVariable("seckill") Long seckillId,Model model){
         if(seckillId==null){
            return "redirect:/seckill/list";
         }
        Seckill seckill = seckillService.getById(seckill);
        if(seckill==null){
            return "forward:/seckill/list";
        }
        model.addAttribute("seckill",seckill);//model
        return "detail";//view
    }

                            返回json数据
    @RequestMapping(value = "/{seckill}/detail",method=RequestMethod.POST,produces={
        "application/json;charset=UTF-8"
    })
    @ResponseBody
    public SeckillResult<Exposer> exportSeckillURL(@PathVariable("id") long id){
        SeckillResult<Exposer> result;
        try{
            Exposer exposer=...
        }catch(Exception e){
            logger.error(e.getMessage(),e);
            result = new SeckillResult<Exposer>(false,e.getMessage());
        }
        return result;
     }
                             Cookie访问
    @RequestMapping(value="/{seckillId}/{md5}/execution",method=RequestMethod.POST)
    public SeckillResult<SeckillExecution> execute(
        @PathVariable("seckillId") long seckillId,
        @PathVariable("md5") String md5,
        @CookieValue(value="killPhone",required=false) Long phone){
          if(phone == null){
                return new SeckillResult<SeckillExecution>(false,"未注册电话");
          }
          SeckillExecution execution = seckillService.executeSeckillKillByProcedure(seckillId,phone,md5);
          SeckillResult<SeckillExecution> result = new SeckillResult<SeckillExecution>(true,execution);
          return result;
      }

39、整合配置SpringMVC框架
    首先打开 webapp/WEB_INF/web.xml
    整合顺序 MyBatis -> spring -> springMVC

    新建spring-web.xml

    注意这条命名空间不要选错
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"

40、实现Restful的API
    新建org.seckill.web包并新建SeckillController包

    @Controller // @Service @Component
    @RequestMapping("/seckill")// URL:/模块/资源/{id}/细分  /seckill/list

     @RequestMapping(value = "/list", method = RequestMethod.GET)
        public String list(Model model) {
        //list.jsp+model=ModelAndView
            return null;
        }

    并写好详情页的接口

    然后写返回秒杀地址的ajax接口,返回json
    在dto中新建SeckillResult<T> 泛型 用于封装json结果

    //重点理解如何转换成json的

     ---暴露秒杀地址---
     //ajax 返回json
        @RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.POST,
                produces = {"application/json;charset=UTF-8"})
        @ResponseBody
        public SeckillResult<Exposer> exposer(Long seckillId) {
            SeckillResult<Exposer> result;
            try {
                Exposer exposer = seckillService.exportSeckillUrl(seckillId);
                result = new SeckillResult<Exposer>(true, exposer);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                result = new SeckillResult<Exposer>(false, e.getMessage());
            }
            return result;
        }

     ---执行秒杀接口---
          @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST,
                    produces = {"application/json;charset:UTF-8"})
            @ResponseBody
            public SeckillResult<SeckillExecution> execute(
                    @PathVariable("seckillId") Long seckillId,
                    @PathVariable("md5") String md5,
                    @CookieValue(value = "killPhone", required = false) Long phone) {
                //这里设置 required = false防止报错,验证放在程序中
                //也可使用springMVC valid来验证 适合很多参数
                if (phone == null) {
                    return new SeckillResult<SeckillExecution>(false, "未注册");
                }
                SeckillResult<SeckillExecution> result;
                try {
                    SeckillExecution seckillExecution = seckillService.executeSeckill(seckillId, phone, md5);
                    return new SeckillResult<SeckillExecution>(true, seckillExecution);
                } catch (RepeatKillException e) {//注意处理异常的优先级
                    SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL);
                    return new SeckillResult<SeckillExecution>(false, execution);
                } catch (SeckillCloseException e) {
                    SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END);
                    return new SeckillResult<SeckillExecution>(false, execution);
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                    SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
                    return new SeckillResult<SeckillExecution>(false, execution);
                }
            }

      ---获取系统时间---
      完成public SeckillResult<Long> time() now.getTime());//注意这里需要getTime()

41、
    使用bootstrap开发前端页面
    环境安装http://www.runoob.com/bootstrap/bootstrap-environment-setup.html
    拷贝HTML模板,新建jsp/list.jsp jsp/detail.jsp
    将要CDN改成推荐版本(链接) 去掉css和主题,并将引用改到</body>后面
    新建common文件夹存放通用的jsp, /common/head.jsp ---静态包含---(动态包含会有多个servlet)
    新建common文件夹存放通用的jsp, /common/tag.jsp
    完成list.jsp
    
42、
    运行测试
    不能连接数据库要注意执行 net start mysql启动数据库服务
    http://localhost:8080/seckill/list
    修改网页代码后也需要重新编译
    Build->Build Artifacts->选择一个war, 不重启服务器

43、
    在detail.jsp中实现cookie登录交互,首先完成相应的布局
    从http://www.bootcdn.cn/获取jQuery cookie操作插件和countDown倒计时插件
    开始编写交互逻辑
    在webapp下新建resources/script/seckill.js

      $.post(killUrl, {}, function (result) {

        }
    );//post请求,但是把参数都放在url上面了.

    针对重复秒杀的情况 出现系统异常的恢复 json不显示

44、
第三期:Java高并发秒杀API之Web层 2017-05-31号完成
第四期:Java高并发秒杀API之高并发优化 2017-06-01号开始

    可能出现高并发的点
        详情页
        系统时间
        地址暴露接口
        执行秒杀操作

    为什么要单独获取系统时间
        用户大量刷新->CDN(detail页静态化 静态资源css,js等)->秒杀系统

    获取系统时间不用优化
        访问一次内存(Cacheline)大约10ns(1秒钟可执行1亿次)
    秒杀地址接口分析
        无法使用CDN,适合服务端缓存:redis等,一致性维护成本低
    秒杀地址接口优化
        请求地址->redis->Mysql(两者一致性维护:超时穿透/主动更新)
    秒杀操作优化分析
        无法使用cdn,后端缓存困难:库存问题,一行数据竞争:热点商品

    其他方案分析
                    执行秒杀

        原子计数器     ->  技术 -> redis/NoSql
            |
        记录行为消息   ->  技术 -> 分布式MQ
            |
        消费消息并落地 ->  技术 -> MySql
    成本分析:运维成本和稳定性:NoSQL,MQ等 开发成本:数据一致性,回滚方案等,幂等性难保证:重复秒杀问题,不适合新手

    为什么不用MySQL解决?认为MySql低效。MySql真的低效?
    测试:一条update压力测试(约4wQPS)
    Java控制事务行为分析
    sql->insert购买明细->等待行锁......
    瓶颈分析:
       update减库存-> 网络延迟、GC -> insert购买明细 -> 网络延迟、GC -> commit/rollback
    优化分析:
        行级锁是在Commit之后释放 -> 优化方向减小行级锁持有时间 ->
    延迟分析
        同城机房:
        异地机房:北京到上海总距离1300公里,真空中30w公里/s,光在玻璃的传播速度是真空中的2/3,
                  往返距离1300*2=2600公里,1300* 2/(300000 * 2 / 3)=13毫秒
                  实际在20毫秒左右,QPS最好只有50(只考虑一次网络连接时间)
    如何判断Update更新库存成功
       两个条件:1、Update自身没有错 2、客户端确认Update影响记录数
       优化思路:把客户端逻辑放到MySql服务器,避免网络延迟和GC影响
                如何放在Mysql服务器?
                    两种解决方案:
                        -定制SQL方案:update/*+[auto_commit]*/  需要修改MySql源码
                        -使用存储过程:整个事务在MySql端完成

    优化总结
        前端控制:暴露接口、按钮防重复
        动静态数据分离:CDN缓存(好像有API推送),后端缓存(redis、memcache)
        事务竞争优化:减少事务锁时间(好像是把几条语句放在一起执行,相当于一条语句执行,(一秒钟可以执行几万次)
                     这样同时减少了网络连接过程中的延迟时间)

45、开始秒杀优化
    redis后端缓存优化
    使用redis优化"地址暴露接口"
    redis官方不支持windows,但是window开放技术团队开发支持了redis的应用
    本次测试在linux下配置redis服务
    下载->解压->进入->make->make install->redis-server->redis-cli -p 6379(默认端口可省略)->info 查看运行情况
    打开pom.xml,引入Java访问redis客户端的jar包https://redis.io/clients
    注意redis-server 和 redis-cli 没有空格

    打开SeckillServiceImpl.java
        开始优化exportSeckillUrl()
        在dao中新建cache包和RedisDao类

        配置protostuff序列化依赖(空间、速度、cpu)
        Java序列化性能对比https://github.com/eishay/jvm-serializers/wiki
        protostuff的简单实用http://blog.csdn.net/canot/article/details/53750443

        RedisDaoTest的单元测试
        spring-dao.xml中配置
        <!--RedisDao-->
        <bean id="redisDao" class="org.seckill.dao.cache.RedisDao">
            <constructor-arg index="0" value="192.168.0.107"/><!--ifconfig localhost 192.168.0.107-->
            <constructor-arg index="1" value="6379"/>
            <!--JedisDataException: DENIED Redis is running in protected mode because protected mode is....
            http://blog.csdn.net/u010003835/article/details/52853574
            先redis-cli ,然后执行CONFIG SET protected-mode no-->
        </bean>

        执行dbsize查看已经写入的缓存条数
        执行keys * 查看所有的key
        执行get xxxkey 查看被序列化的值

        单元测试通过:
        开始优化exportSeckillUrl()方法

46、
    简单优化
    调整减库存和插入购买明细操作的顺序
    insert购买明细  -> 网络延迟/GC -> update减库存rowLock -> 网络延迟/GC -> commit/rollback freeLock
    进入SeckillServiceImpl.java 中的executeSeckill()
    //先屏蔽一部分重复秒杀的请求

    深度优化
    事务SQL在MySQL端执行(存储过程) 减少网络延迟的影响,提高TPS
    在sql包中创建sckill.sql

    连接数据库mysql -uadmin -padmin -Dseckill
    分别执行
    DELIMITER $$ 和 定义存储过程 部分
    再执行show create procedure execute_seckill\G
    执行DELIMITER  ; --还原
    执行set @r_result=-3; 并查看一下数据 select @r_result;
    执行存储过程前查看数据 select * from seckill where seckill_id=1001\G
        mysql> select * from seckill where seckill_id=1001\G
        *************************** 1. row ***************************
         seckill_id: 1001
               name: 500元秒杀iPad2
             number: 198
         start_time: 2017-06-01 23:33:35
           end_time: 2017-07-19 00:00:00
        create_time: 2017-05-02 22:27:23
        1 row in set (0.00 sec)

    查看秒杀成功表中的1003的数据或者直接删除所有1003的数据
    select * from success_killed;
    delete from success_killed where seckill_id = 1001;
    执行存储过程 call execute_seckill(1001,13502172008,now(),@r_result);
    查看输出 select @r_result; 返回 1
    查看成功记录表 select * from success_killed;
    继续执行存储过程 并查看返回值,返回 -1,并确定成功记录表和商品数量表中数据没有变化

    总结
    -- 存储过程
    -- 1:存储构成优化:事务行级锁私持有时间
    -- 2:不要过度依赖存储过程
    -- 3: 简单的逻辑可以应用存储过程(银行业使用,一般的互联网应用不用)
    -- 4:QPS:一个秒杀单6000/qps

47、seckill项目调用存储过程代码实现
    SeckillService.java中定义一个新接口executeSeckillProcedure(),不需要抛出异常
    并在SeckillServiceImpl.java中实现这个接口
        会在SeckillDao.java中新建void killByProcedure(Map<String,Object> paramMap);
        并在SeckillDao.xml中写sql
        继续实现接口(其中会添加依赖 commons-collections MapUtils获得工具)
    写单元测试testExecuteSeckillProcedure()
    注意SeckillDao.xml中的call execute_seckill,
    这个"存储过程"就是seckill.sql中创建并在命令行中执行的,而不是后期在代码中实现!!!
    通过Navicat for MySQL工具连接数据库后,数据库中并没有execute_seckill表,而是有一个execute_seckill函数

    单元测试通过后修改SeckillController中的execute()方式
    调用执行秒杀调用executeSeckillProcedure()

    重新编译后启动web测试效果

48、大型系统部署架构
    系统会用到哪些服务?
    CDN
    WebServer:Nginx+Jetty
    Redis
    MySQL

    系统部署的架构应该是什么样的?
              智能DNS解析              ←     流量
             Nginx  Nginx                     ↓
                  ↓                        CDN缓存
               逻辑集群
           Jetty Jetty Jetty           →   缓存集群
                  ↓                      Redis Redis
                  ↓mod(seckillId)
               分库分表                →    统计分析
            DB-1 DB-2 DB-3

      分库分表框架:阿里巴巴阿里巴巴中间件TDDL

    可能参与的角色?
        开发:前端+后端
        测试
        DBA
        运维:机器、监控、初始化脚本、Nginx之间配置


49、课程总结
    ---数据库设计和实现
    ---MyBatis理解和使用技巧
    ---MyBatis整合Spring技巧(自动包扫描、sql文件扫描、别名识别,做到一次配置之后不需要修改)

    业务层技术回顾
        ---业务接口设计和封装(站在使用者的角度去设计)
        ---SpringIOC配置技巧:声明式事务:xml配置,自己开发的dao、service、controller:使用注解
        ---Spring声明式事务使用和理解:什么情况下回滚,什么情况下提交
    Web技术回顾
        ---Restful接口运用(规范和理解:描述一个资源,通过不同的提交方式来达到描述行为的目的,写通过POST,读通过GET)
        ---SpringMVC使用技巧 (如何配置 参数如何映射 如何打包成json返回给浏览器
            (返回json是通过Spring框架(包package org.springframework.web.bind.annotation;),而不是Restful,Resultful只是一种设计规范))
        ---前端交互分析过程
        ---Bootstrap和JS使用
        ---系统瓶颈点分析
        ---事务,锁,网络延迟理解(还有GC),
        ---前端,CDN,缓存等理解使用
        ---集群化部署

    2017-06-06 21:11 学习结束

 https://tech.jiangjiesheng.cn/dev/study/java/seckill/study-record.html

有任何疑问或技术合作都可联系我

微信:yanfahezuo 【推荐】

QQ:596957738


标签:Java  高并发  秒杀  API  业务分析  DAO层  Service层  Web层  
上一篇:没有了
下一篇:没有了
相关文章
相关评论

加我微信 596957738 (QQ同号)加我微信     QQ联系:596957738    地址:江苏省南京市浦口区

苏ICP备2023050353号

   

苏公网安备32011402010305号

江节胜的Gitee,江节胜的Git地址