2020-06-01

(一)项目介绍

项目内容:电商秒杀玩法后端具体实现
项目作者:刘吴洋
项目使用语言:JAVA
项目使用工具:IDEA
项目实现功能:秒杀功能
①分布式会话
②商品列表页
③商品详情页
④订单详情页
⑤系统压测
⑥缓存优化
⑦消息队列
⑧接口安全
项目实现模块:
①登录模块
②秒杀模块


(二)项目环境搭建

①创建一个Web项目
在这里插入图片描述
②添加依赖
在这里插入图片描述
Spring-redis配置

<?xml version="1.0" encoding="UTF-8"?>
<bean id="annotationPropertyConfigurerRedis"
	  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	<property name="order" value="1" />
	<property name="ignoreUnresolvablePlaceholders" value="true" />
	<property name="locations">


(三)登录模块功能具体实现

①书写index.jsp
<%@ page language=“java” contentType=“text/html; charset=utf-8”
pageEncoding=“utf-8”%>

登录
账号:
密码:

注释:(待写)
②设置默认主页

redis解压完成之后直接执行make命令; 可选步骤: make test 测试编译情况
(可能出现: need tcl >8.4这种情况, yum install tcl)
可能会出现缺少依赖包:
yum install cpp
yum install binutils
yum install glibc
yum install glibc-kernheaders
yum install glibc-common
yum install glibc-devel
yum install gcc

③安装到指定的目录,比如 /usr/local/redis

④make install之后,得到如下几个文件
redis-benchmark 性能测试工具
redis-check-aof 日志文件检测工(比如断电造成日志损坏,可以检测并修复)
redis-check-dump 快照文件检测工具,效果类上
redis-cli 客户端
redis-server 服务端

⑤复制配置文件
Cp /path/redis.conf /usr/local/redis

⑥启动与连接
/path/to/redis/bin/redis-server ./path/to/conf-file
例:[root@localhost redis]# ./bin/redis-server ./redis.conf

⑦让redis以后台进程的形式运行


(四)秒杀模块

①商品详情页面

②秒杀功能

③订单详情


(五)高并发秒杀API设计

前期准备

  1. 配置文件获取地址
  2. 基于maven命令创建项目
  3. 修改默认的servlet版本
  4. 修改pom文件
  5. 秒杀业务分析

dao层

  1. 创建数据库数据表
  2. 创建entity
  3. 创建dao层接口
  4. mybatis实现dao
  5. mybatis和spring的整合
  6. spring-dao的配置
  7. 进行dao的测试

service层

  1. 使用Spring托管Service依赖
    ①使用IOC的好处
    ②SpringIOC注入方式和场景
  2. Spring声明式事务

web层

  1. 前端页面流程
  2. 设计restful接口
  3. SpringMVC
  4. http请求映射的原理
  5. 设计Restful接口
  6. 请求方法的细节处理
  7. 配置springMVC的相关重要操作

高并发优化

  1. 高并发优化分析
    ①获取系统时间
    ②秒杀地址接口分析
    ③秒杀操作的优化分析
  2. 优化总结
  3. 使用redis优化地址暴露接口

前期准备

配置文件获取地址

logback配置:https://logback.qos.ch/manual/configuration.html
spring配置:https://docs.spring.io/spring/docs/
mybatis配置:http://www.mybatis.org/mybatis-3/zh/index.html

基于maven命令创建项目

**maven命令:**mvn archetype:create -DgroupId=org.seckill -DartifactId=sechkill -DarchetypeArtifactId=maven-archetype-webapp(在IDEA里面也可以图形化创建)

修改默认的servlet版本

默认的servlet版本是2.3,这个版本比较老了,其中,jsp的el表达式是不支持的,因此可以去tomcat给出的示例里面去copy一个 是3.1

修改pom文件

junit在3.x是使用编程的方式,4.x是使用注解的方式
补全项目依赖
**日志:**slf4j、log4j、logback、common-logging
slf4j:是规范、接口,可以通过很多的方式去实现这一套规范
**日志实现:**log4j、logback、common-logging
**使用:**slf4j + logback

pom文件包含:
1.junit、日志的依赖
2.数据库相关的依赖:数据库驱动、数据库连接池
3.DAO框架:mybatis
4.web相关:servlet taglib jstl jsp json
5.spring:core dao web

秒杀业务分析
数据落地:MySQL or NoSQL
但是Nosql为了追求高性能,对于事务的支持不是很好,事务机制依然是目前最有效的数据落地方案,因此需要采用MySQL。

秒杀难点分析:竞争 –> 如何高效的处理竞争。
竞争反映到背后的技术:事务 + 行级锁
事务的具体步骤:

	start transection –> Update库存数量 –> insert购买明细 –> commit
update table set number = number - 1 where id = 10 and number > 1

需要做的工作:秒杀接口暴露、执行秒杀、相关查询

dao层

创建数据库数据表

seckill表–>秒杀项目表
success_killed表–>秒杀成功明细表(主键为联合主键:primary key(seckill_id, user_phone))作用:防止用户重复秒杀,基于这个唯一性可以做过滤

创建entity

Successkilled实体中有seckill实体

创建dao层接口

SeckillDao–>int reduceNumber(seckillId, killTime)如果返回的数据大于1,表示影响的行数,如果等于0,表示没有插入成功

SuccessKillDao–> int insertSuccessKill(seckillId, phoneNumber) 同上
SuccessKillDao–>SuccessKilled queryByIdWithSeckill(long seckillId )这个时候需要将seckill的数据也返回回来,需要做一个sql链接

sql链接:根据id查询SuccessKilled并携带SecKill实体
关键在于如何告诉Mybatis把结果映射到SuccessKilled的同时,映射seckill属性

select 
    sk.seckill_id,
    sk.user_phone,
    sk.create_time,
    sk.state,
    s.seckill_id "seckill.seckill_id",
    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}
success

mybatis实现dao

mybatis实现dao的方式:通过Mapper自动实现DAO接口 and API编程方式实现dao接口
mybatis的配置:1.使用jdbc的方式获取数据库自增主键 2.使用列别名替换列名 3.开启驼峰命名转换 当有<= 的时候,需要使用

mybatis和spring的整合

目标:
更少的编码(只写接口,不写实现类)更少的配置(去掉包名–>包扫描 自动配置扫描 自动实现dao接口并且注入spring容器)足够的灵活性(自己定制sql 自由传参 结果集自动赋值)

xml提供sql 和 DAO接口提供Mapper

spring-dao的配置

1、数据源—>包括数据库参数和c3p0数据库连接池
2、SqlSessioinFactory–>包括加入数据源以及配置mybatis全局配置文件
3、扫描entity包使用别名
4、扫描sql配置文件:mapper需要的xml文件
5、配置扫描dao接口包–>为了动态实现dao接口并且注入到spring容器中(可以节省工作量 mapper是mybatis帮我们实现dao的实现类的名字) 是SqlSessionFactoryBeanName而不是SqlSessionFactory,因为这个可以避免提前加载配置文件,只有当用到的时候才会加载

进行dao的测试

使用spring-test和junit依赖实现的
RunWith(SpringJunit4ClassRunner.class) (Spring启动时加载SpringIOC的容器)
ContextConfigration({“classpath:spring-dao.xml”}) (告诉Junit Spring的配置在那个地方)

当进行编写的方法中传递多个形参的时候,由于java没有保存多个形参的记录,所以List< secKill > queryAll(int offset, int limit) –> List< secKill > queryAll(int args0, int args1)
需要告诉mybatis那个是那个参数–> List< secKill > queryAll(@Param(“offset”) int offset, @Param(“limit”) int limit)

service层

dto层:数据传输对象,关注的是web层和service层的数据传输。

业务接口:站在“使用者”的角度设计接口,而不是实现
主要是三个方面:
1.方法定义粒度 2.参数–>越简练越好 3.返回类型 return的类型要很友(允许异常)

秒杀地址:秒杀开启时输出秒杀接口地址,否则输出系统时间和秒杀时间,因此应该返回一个dto–>Exposer(用来暴露秒杀接口)–> expose md5 seckillId now start end 这些字段大多都是业务不相关的,主要是用来方便我们的service返回数据的一个封装

创建Exception(extends RuntimeException):spring的声明式事务,只会接受运行期异常回滚策略,如果出现了编译期异常,spring是不会给我们做回滚的
将编译期异常转化为运行期异常:通过try catch 捕获到后重新throw新的exception即可,这样spring就能够进行rollback 数据字典通过枚举表示。
RepeatException–>重复秒杀异常 SeckillCloseException–>秒杀关闭异常(有很多原因)SeckillException通用的秒杀异常

使用Spring托管Service依赖

使用IOC的好处

1.为对象创建统一管理
2.规范的生命周期管理
3.灵活的依赖注入
4.一致的获取对象的方式

SpringIOC注入方式和场景

xml:1.bean实现来自第三方类库,如:beanFactory dataSource 2.bean需要命名空间的配置如:aop context mvc等
注解:项目中开发的类,可以在代码中直接使用如:@Service @Controller
java配置类:需要通过代码控制对象创建逻辑的场景 如自定义修改依赖类库(不常见)

Spring声明式事务
目的;解脱事务代码,将事务的开启提交等步骤交个第三方框架
开启事务–>sql1 sql2 sql3… –>commit/rollback
事务并不是所有对于数据库的操作都需要的,当进行单表单条的操作可以不用,但是进行例如模糊查询等操作的时候,多条语句可能有一条出错,如果不进行回滚,则会出问题

Spring使用声明式事务的方式

	ProxyFactoryBean + XML —> Spring2.0 
	tx:advice + aop命名空间 —> 一次配置永久生效 
	注解@Transactional —> 注解控制,spring会在进入和退出这个方法的时候自动加上进入和退出的时候自动加入事务控制的逻辑(推荐使用)

事务方法的嵌套:声明式事务独有的概念,体现在传播行为上–> propagation_required 如果没有事务,那么就创建一个新的事务,如果有的话就加入原有的事务

什么时候回滚事务:当抛出运行期异常(RunTimeException)的时候。因此需要小心使用try catch, 不然spring可能感知不到从而不会回滚

使用直注解控制事务方法的优点: (最好不要使用aop,因为这样一不小心就会导致数据库链接或者提交的延迟造成的阻塞,尤其是秒杀业务)

	1.开发团队达成一致的约定,明确标注事务方法的编程风格 
	2.保证事务方法的执行时间尽可能短,不要穿插其他的网络操作(像redis memecache http –> rpc/http请求(大概毫秒级) 让这个方法尽量是一个很干净的事务操作流程,将其他网络操作剥离,再写一个更加上层的方法) 
	3.不是所有的方法都需要事务 如只有一条修改操作,只读操作也不需要修改事务控制

web层

前端页面流程

列表页(用来展示所有的秒杀产品)–>详情页(用户是否登录)–>展示逻辑—具体—->获取系统当前标准时间–>时间判断(看秒杀是否开始和结束)–>秒杀地址–点击–>执行秒杀–>结果

设计restful接口

是一种优雅的url表达方式, 其意义是资源的状态和资源的状态转移
Restful规范:get –> 查询操作 post –> 添加/修改操作 put –> 修改操作 delete –> 删除操作 通过不同的提交方式来体现动作

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

SpringMVC

始终围绕Handler开发
Handler的产出:数据Model和页面view

1.DispatcherServlet:所有的请求都会映射到DispatcherServlet,这是一个中央控制器的Servlet,这个Servlet会拦截所有的请求
2.DefaultAnnotationHandlerMapping:用来映射url,具体的内容就是我们的那个url对应的那个handler
3.DefaultAnnotationHandlerAdapter:用来做handler适配,用来衔接我们自己开发的Controller,如果用到拦截器也会将拦截器绑定到我们的流程中
4.ModelAndView:view可以用一个字符串表示,将model和view交付到DispatcherServlet这样的中央控制器中
5.InternalResourceViewResovler:将model和view(这个resovler是jsp的)相结合,最终返回给用户

http请求映射的原理
设计Restful接口

@RequestMapping注解:
(1)支持标注的url
(2)Ant风格的url(即? * 和 **)
(3)带{xxx}占位符的url

请求方法的细节处理

1.请求参数绑定
2.请求方法的限制
3.请求的转发和重定向
4.数据模型的赋值
5.返回json数据
6.cookie的访问

返回json数据:produces = {“application/json;charset = UTF- 8”} 和 @ResponseBody
访问cookie:@CookieValue(value = “killPhone”, required = false) Long phone (不强制匹配传入cookie)

配置springMVC的相关重要操作

在web.xml中注册DispatcherServlet
< mvc:annotation-driven />:自动注册DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter到SpringIOC容器中
提供了一系列功能:数据绑定、数字和日期的format @NumberFormat @DateTimeFormat 、xml和json的默认读写支持

静态资源默认servlet配置:< mvc : default-servlet-handler />
加入对静态资源的处理 :css js png
允许对于 “ / ”做整体映射

配置视图解析器:扫描web相关的包

用泛型封装json的结果


高并发优化

可能发生高并发的地方:
详情页–>系统时间–>暴露地址接口–>执行秒杀操作–>返回结果(这里不会高并发)
详情页:用户大量刷新–>CDN(detail页面静态化) 获取静态资源也会部署到CDN上 但是拿不到我们系统的时间,所以就需要单独去获取系统时间 –> 秒杀系统
CDN的理解

  • 叫做内容分发网络,是用来加速用户获取数据的系统,既可以是静态资源,也可以是动态资源,甚至大部分的视频加速也是依赖于我们的CDN(优酷、搜狐等)
  • 部署都是在离用户最近的网络节点上,是用户通过运营商的接口去访问他的最近的城域网的地址,通过城域网调到主干网,通过ip来访问到资源
  • 命中CDN不需要访问后端服务器

高并发优化分析

获取系统时间

**不用优化。**java访问一次内存(Cacheline)的时间是10ns(纳秒),一秒等于十亿纳秒。

秒杀地址接口分析

无法使用CDN,适合放在服务端缓存(redis这种数据库内存等),官方给出的是一秒十万的QPS,集群之后可以抗百万的QPS,一致性维护的成本比较低
一致性维护:超时穿透、主动更新

秒杀操作的优化分析

无法使用CDN缓存,后端缓存也很困难,一致性差,同时也会出现一行数据的竞争
方案分析
1.原子计数器(库存)——->redis/NoSQL
2.记录行为消息———->分布式MQ(开源RabbitMQ alibaba的rocketMQ(支持事务) apache的activeMQ linkedIn的kafka)
3.消费消息并落地——–>MySQL
成本分析:痛点 运维成本和稳定性NoSQL MQ等,需要一个强大的运维团队和开发成本,数据一致性模型以及了解逻辑处理回滚等 幂等性难以保证:重复秒杀问题 需要还要维护一个减库存的分布式的NoSQL的访问方案,记录哪些用户减过库存

不适用MySQL的原因:低效。
当同一个id执行update减库存,一秒钟可以抗4w的QPS
java控制事务的行为分析:等待行级锁,存在大量的阻塞操作,成为了串行化了
瓶颈分析:sql传递给MySQL的网络延迟,GC(新生代GC和老年代GC,新生代GC会暂停所有的事务代码,也就是java代码,一般在几十毫秒)
优化分析:行级锁在Commit后释放–>优化方向减少行级锁持有时间
一般的GC都是在50ms左右,并发越高,GC的时间也就越高

优化思路:把客户端逻辑放到MySQL服务端,避免网络延迟和GC的影响,因为同一行就能够抗几万的QPS,不同行能够抗几十万的QPS
如何放到MySQL服务端(两种解决方法):
1.定制SQL方案:update + /[auto commit]/ 需要修改MySQL的源码,不需要java去进行提交和rollback,本质上是降低了网络延迟和GC的干扰(一般的团队很难做到支撑得起这个事情)
2.使用存储过程,整个事务在MySQL端完成,不需要Spring声明式事务或者我们手动控制事务

优化总结

1.前端控制:暴露接口,前端按钮防止重复
2.动静态的数据分离:CDN缓存,后端缓存(CDN每个公司都会有自己的api去向CDN做推送,后端缓存就是redis)
3.事务竞争优化:减少事务锁持有的时间

使用redis优化地址暴露接口

希望通过redis缓存暴露的接口地址,以此来降低数据库访问的压力,数据库需要做到执行秒杀的使用场景
redis或者jedis,并没有实现内部序列化。

具体步骤:get->byte[](得到一个二进制的数组,他是不关心获取到的是一个java对象还是文件还是啥+
//todo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值