博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
秒杀读后感2
阅读量:7104 次
发布时间:2019-06-28

本文共 36611 字,大约阅读时间需要 122 分钟。

1:pom 相关配置

4.0.0
com.myimooc
seckill
war
0.0.1-SNAPSHOT
seckill Maven Webapp
http://maven.apache.org
junit
junit
4.12
test
org.slf4j
slf4j-api
1.7.25
ch.qos.logback
logback-core
1.1.11
ch.qos.logback
logback-classic
1.1.11
mysql
mysql-connector-java
5.1.42
runtime
c3p0
c3p0
0.9.1.2
org.mybatis
mybatis
3.3.0
org.mybatis
mybatis-spring
1.2.3
taglibs
standard
1.1.2
jstl
jstl
1.2
com.fasterxml.jackson.core
jackson-databind
2.8.8
javax.servlet
javax.servlet-api
3.1.0
org.springframework
spring-core
4.3.9.RELEASE
org.springframework
spring-beans
4.3.9.RELEASE
org.springframework
spring-context
4.3.9.RELEASE
org.springframework
spring-jdbc
4.3.9.RELEASE
org.springframework
spring-tx
4.3.9.RELEASE
org.springframework
spring-web
4.3.9.RELEASE
org.springframework
spring-webmvc
4.3.9.RELEASE
org.springframework
spring-test
4.3.9.RELEASE
redis.clients
jedis
2.7.3
com.dyuproject.protostuff
protostuff-core
1.0.8
com.dyuproject.protostuff
protostuff-runtime
1.0.8
commons-collections
commons-collections
3.2.1
seckill
org.apache.maven.plugins
maven-compiler-plugin
1.8
1.8
2:修改servlet版本为3.1 支持el表达式

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
3:减库存和购买记录行为要放在一个事务里面执行

如果减了库存没记录购买行为 会存在超卖,如果记录了购买行为而没减库存会出现少卖

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

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

主要用到事务和行级锁,秒杀的难点在于如果高效的处理资源竞争

5:秒杀相关的功能

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

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

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

6:dao层 相关代码

/**  * @describe 商品库存dao  * @author zc  * @version 1.0 2017-08-22  */ 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(long seckillId); /** * 根据偏移量查询秒杀商品列表 * @param offset * @param limit * @return */ List
queryAll(@Param("offset") int offset, @Param("limit") int limit); /** * 使用存储过程执行秒杀 * @param paramMap */ void killByProcedure(Map
paramMap); }
/**  * @describe 成功秒杀明细dao  * @author zc  * @version 1.0 2017-08-22  */ public interface SuccessKilledDao {
/** * 新增购买明细,可过滤重复 * @param seckillId * @param userPhone * @return 插入的行数 */ int insertSuccessKilled(@Param("seckillId")long seckillId,@Param("userPhone") long userPhone); /** * 根据id查询SuccessKilled并携带秒杀产品对象实体 * @param seckillId 秒杀IM * @param userPhone 手机号码 * @return 状态 */ SuccessSeckilled queryByIdWithSeckill(@Param("seckillId")long seckillId, @Param("userPhone") long userPhone); } 7:从上面的代码可以发现,当方法的形参在两个及两个以上时,需要在参数前加上@Param, 如果不加上该注解会在之后的测试运行时报错。这是Sun提供的默认编译器(javac)在编译后的Class文件中 会丢失参数的实际名称,方法中的形参会变成无意义的arg0、arg1等,在只有一个参数时就无所谓, 但当参数在两个和两个以上时,传入方法的参数就会找不到对应的形参。因为Java形参的问题, 所以在多个基本类型参数时需要用@Param注解区分开来 8::Mybatis有两种提供SQL的方式:XML提供SQL、注解提供SQL(注解是java5.0之后提供的一个新特性)。

对于实际的使用中建议使用XML文件的方式提供SQL。如果通过注解的方式提供SQL,由于注解本身还是java源码,这对于修改和调整SQL其实是非常不方便的,一样需要重新编译类,当我们写复杂的SQL尤其拼接逻辑时,注解处理起来就会非常繁琐。而XML提供了很多的SQL拼接和处理逻辑的标签,可以非常方便的帮我们去做封装。

9:在src/main/resources目录下配置mybatis-config.xml(配置MyBatis的全局属性)

10:在src/main/java目录下的com.lewis.mapper包里创建SeckillDao.xml

update 和insert不需要设置返回值 都是明确的

<!-- 这里id必须和对应的DAO接口的方法名一样 -->

update seckill set number = number - 1 where seckill_id = #{seckillId} and start_time #{killTime} and end_time >= #{killTime} and number > 0;
select 如果返回的是list 那么返回值设置为返回的的list中的对象类型
可以通过别名的方式列明到java名的转换,如果开启了驼峰命名法就可以不用这么写了
11:在src/main/java目录下的com.lewis.mapper包里创建SuccessKilledDao.xml
insert ignore into success_killed(seckill_id,user_phone,state) values (#{seckillId},#{userPhone},0)

注:上面的s.seckill_id “seckill.seckill_id”表示s.seckill_id这一列的数据是Success_killed实体类里的seckill属性里的seckill_id属性,是一个级联的过程,使用的就是别名只是忽略了as关键字,别名要加上双引号。

为什么要用<![CDATA[]]>把<=给包起来

CDATA指的是不应由 XML 解析器进行解析的文本数据,在XML元素中,<和&是非法的:

<会产生错误,因为解析器会把该字符解释为新元素的开始。

&也会产生错误,因为解析器会把该字符解释为字符实体的开始。(字符实体:比如 表示一个空格)
所以在这里我们需要使用<![CDATA[ <= ]]>来告诉XML<=不是XML的语言。

 12:整合Spring和MyBatis

resources目录下创建一个新的目录spring(存放所有Spring相关的配置)

在resources包下创建jdbc.properties,用于配置数据库的连接信息

driver=com.mysql.jdbc.Driver

url=jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf-8
jdbc.username=root
password=123
resources/spring目录下创建Spring关于DAO层的配置文件spring-dao.xml

在jdbc.properties里使用的是jdbc.username,而不是username或者name,这是因为后两个属性名可能会与全局变量冲突,导致连接的数据库用户名变成了电脑的用户名,所以使用了jdbc.username。 13:测试

/**

* 配置Spring和Junit整合,junit启动时加载springIOC容器 spring-test,junit
*/
@RunWith(SpringJUnit4ClassRunner.class)
// 告诉junit spring的配置文件
@ContextConfiguration({ "classpath:spring/spring-dao.xml" })
public class SeckillDaoTest {

// 注入Dao实现类依赖

@Resource
private SeckillDao seckillDao;

@Test

public void testQueryById() {

long seckillId = 1000;

Seckill seckill = seckillDao.queryById(seckillId);
System.out.println(seckill.getName());
System.out.println(seckill);
}
}

 

14:service层相关接口

public interface SeckillService {
/** * 查询所有秒杀记录 * @return */ List
getSeckillList(); /** * 查询单个秒杀记录 * @param seckillId * @return */ Seckill getById(long seckillId); /** * 秒杀开启时输出秒杀接口地址,否则输出系统时间和秒杀时间 * @param seckillId 秒杀ID * @return 接口地址 */ Exposer exportSeckillUrl(long seckillId); /** * 执行秒杀操作 * @param seckillId 秒杀ID * @param userPhone 手机号码 * @param md5 MD5 * @return 执行 * @throws SeckillException 秒杀异常 * @throws RepeatKillException 重复秒杀异常 * @throws SeckillCloseException 秒杀关闭异常 */ SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException,RepeatKillException,SeckillCloseException; /** * 执行秒杀操作,存储过程 * @param seckillId 秒杀ID * @param userPhone 手机号码 * @param md5 MD5 * @return 执行 */ SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5); } 15:Exposer 对象

public class Exposer {

/**
* 是否开启秒杀
*/
private Boolean exposed;
/**
* 一种加密措施
*/
private String md5;

private long seckillId;

/**
* 系统当前时间(单位:毫秒)
*/
private long now;
/**
* 开启时间
*/
private long start;
/**
* 结束时间
*/
private long end;
}:

16:封装秒杀执行后结果

public class SeckillExecution {
private long seckillId; /** * 秒杀执行结果状态 */ private int state; /** * 状态标识 */ private String stateInfo; /** * 秒杀成功对象 */ private SuccessSeckilled successSeckilled; }

17:秒杀关闭异常(运行期异常)

public class SeckillCloseException extends SeckillException{
private static final long serialVersionUID = 1L; public SeckillCloseException(String message) {
super(message); } public SeckillCloseException(String message, Throwable cause) {
super(message, cause); } }

 18:重复秒杀异常(运行期异常)

public class RepeatKillException extends SeckillException{
private static final long serialVersionUID = 1L; public RepeatKillException(String message) {
super(message); } public RepeatKillException(String message, Throwable cause) {
super(message, cause); } } 20:秒杀相关业务异常异常
public class SeckillException extends RuntimeException{
private static final long serialVersionUID = 1L; public SeckillException(String message) {
super(message); } public SeckillException(String message, Throwable cause) {
super(message, cause); } } 21:加密字符串
private String getMD5(long seckillId) {
String base = seckillId + "/" + slat; String md5 = DigestUtils.md5DigestAsHex(base.getBytes()); return md5; } 22:service实现代码
@Service public class SeckillServiceImpl implements SeckillService {
private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private SeckillDao seckillDao; @Autowired private SuccessKilledDao successKilledDao; @Autowired private RedisDao redisDao; /** * md5盐值字符串,用于混淆md5 */ private final String slat = "fdhasjfhu5GERGTEiweayrwe$%#$%$#546@wdasdfas"; /** * 查询所有秒杀记录 * * @return */ @Override public List
getSeckillList() {
return seckillDao.queryAll(0, 4); } /** * 查询单个秒杀记录 * * @param seckillId * @return */ @Override public Seckill getById(long seckillId) {
return seckillDao.queryById(seckillId); } /** * 秒杀开启时输出秒杀接口地址,否则输出系统时间和秒杀时间 * * @param seckillId */ @Override public Exposer exportSeckillUrl(long seckillId) {
// 优化点:缓存优化:超时的基础上维护一致性 //1:访问redis Seckill seckill = redisDao.getSeckill(seckillId); if (null == seckill) {
//2:访问数据库 seckill = seckillDao.queryById(seckillId); if (null == seckill) {
return new Exposer(false, seckillId); } else {
//3:放入redis redisDao.putSeckill(seckill); } } Date startTime = seckill.getStartTime(); Date endTime = seckill.getEndTime(); // 系统时间 Date nowTime = new Date(); if (nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()) {
return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime()); } // 转换特定字符串的过程,不可逆 String md5 = this.getMD5(seckillId); return new Exposer(true, md5, seckillId); } /** * 执行秒杀操作 * * @param seckillId * @param userPhone * @param md5 */ @Transactional(rollbackFor = Exception.class) /** * 使用注解控制事务方法的优点: * 1:开发团结达成一致约定,明确标注事务方法的编程风格 * 2:保证事务方法的执行时间尽可能短,不要穿插其他网络操作,RPC/HTTP请求或者剥离到事务方法外部 * 3:不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制 */ @Override public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {
if (null == md5 || !md5.equals(getMD5(seckillId))) {
throw new SeckillException("seckill data rewrite"); } // 执行秒杀逻辑:减库存 + 记录购买行为 Date nowTime = new Date(); try {
// 记录购买行为 int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone); // 唯一:seckillId,userPhone if (insertCount <= 0) {
// 重复秒杀 throw new RepeatKillException("seckill repeated"); } else {
// 减库存,热点商品竞争 int updateCount = seckillDao.reduceNumber(seckillId, nowTime); if (updateCount <= 0) {
// 没有更新到记录,秒杀结束 throw new SeckillCloseException("seckill is closed"); } else {
// 秒杀成功 SuccessSeckilled successSeckilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone); return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successSeckilled); } } } catch (SeckillCloseException e1) {
throw e1; } catch (RepeatKillException e2) {
throw e2; } catch (Exception e) {
logger.error(e.getMessage(), e); // 把所有编译期异常转化成运行期异常 throw new SeckillException("seckill inner error:" + e.getMessage()); } } /** * 执行秒杀操作,存储过程 * * @param seckillId * @param userPhone * @param md5 */ @Override public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) {
if (md5 == null || !md5.equals(getMD5(seckillId))) {
return new SeckillExecution(seckillId, SeckillStatEnum.DATA_REWRITE); } Date killTime = new Date(); Map
map = new HashMap<>(16); map.put("seckillId", seckillId); map.put("phone", userPhone); map.put("killTime", killTime); map.put("result", null); // 执行存储过程,result被赋值 try {
seckillDao.killByProcedure(map); // 获取result int result = MapUtils.getInteger(map, "result", -2); if (result == 1) {
SuccessSeckilled sk = successKilledDao.queryByIdWithSeckill(seckillId, userPhone); return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, sk); } else {
return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result)); } } catch (Exception e) {
logger.error(e.getMessage(), e); return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR); } } private String getMD5(long seckillId) {
String base = seckillId + "/" + slat; String md5 = DigestUtils.md5DigestAsHex(base.getBytes()); return md5; } } 23:使用枚举表述常量数据字典
public enum SeckillStatEnum {
/** * 秒杀成功 */ SUCCESS(1, "秒杀成功"), /** * 秒杀结束 */ END(0, "秒杀结束"), /** * 重复秒杀 */ REPEAT_KILL(-1, "重复秒杀"), /** * 系统异常 */ INNER_ERROR(-2, "系统异常"), /** * 数据篡改 */ DATA_REWRITE(-3, "数据篡改"); private int state; private String stateInfo; SeckillStatEnum(int state, String stateInfo) {
this.state = state; this.stateInfo = stateInfo; } public int getState() {
return state; } public String getStateInfo() {
return stateInfo; } public static SeckillStatEnum stateOf(int index) {
for (SeckillStatEnum state : values()) {
if (state.getState() == index) {
return state; } } return null; } } 24:spring托管service

spring会通过spring工厂创建对象

seckillservice 依赖 SeckillDao和SuccessKillDao,

SeckillDao和SuccessKillDao依赖SqlSessionFactory,
SqlSessionFactory 依赖 数据源..

25:为什么使用Spring IOC呢?

  • 对象的创建统一托管
  • 规范的生命周期的管理
  • 灵活的依赖注入
  • 一致的对象注入

26:Spring-IOC注入方式和场景是怎样的

 

 

 27:第三种不常用

这也是大多数使用spring的方式

 

在spring包下创建一个spring-service.xml文件,内容如下:

 

然后采用注解的方式将Service的实现类加入到Spring IOC容器中:

//注解有 @Component @Service @Dao @Controller(web层),这里已知是service层@Servicepublic class SeckillServiceImpl implements SeckillService{        //日志对象slf4g    private Logger logger = LoggerFactory.getLogger(this.getClass());        //注入service的依赖    @Autowired    private SeckillDao seckillDao;    @Autowired    private SuccessKilledDao successKilledDao;

 28:事务控制

声明式事务的使用方式:1.早期使用的方式:ProxyFactoryBean+XMl.2.tx:advice+aop命名空间,这种配置的好处就是一次配置永久生效。3.注解@Transactional的方式。在实际开发中,建议使用第三种对我们的事务进行控制

29:配置声明式事务,在spring-service.xml中添加对事务的配置:

然后在Service实现类的方法中,在需要进行事务声明的方法上加上事务的注解:

@Override @Transactional /** * 使用注解控制事务方法的优点: 1.开发团队达成一致约定,明确标注事务方法的编程风格 * 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部 * 3.不是所有的方法都需要事务,如只有一条修改操作、只读操作不要事务控制

 

30:SeckillServiceImpl集成测试类

**  * SeckillServiceImpl集成测试类  */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({
"classpath:spring/spring-dao.xml", "classpath:spring/spring-service.xml"}) public class SeckillServiceImplTest {
} 31:前端流程

 

 32:秒杀API的URL设计

GET / seckill/ list       秒杀列表

GET / seckill/{id}/ detail    详情页

GET / seckill/time/now    系统时间    

GET / seckill/{id}/exposer        暴露秒杀

GET / seckill/{id}/{md5}/execution  执行秒杀

33:SpringMVC运行流程

34:注解映射技巧

@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。 ID以参数形式传入

/company/{companyId}/user/{userId}/detail

  匹配/company/123/user/456/detail等URL

35:相关细节

CookieValue以及返回json
@PostMapping(value = "/{seckillId}/{md5}/execution", produces = {"application/json;charset=UTF-8"}) @ResponseBody public SeckillResult
execute(@PathVariable("seckillId") Long seckillId,
@PathVariable("md5") String md5,                                                @CookieValue(value = "killPhone", required = false) Long phone) {
}
@GetMapping(value = "/list") public ModelAndView list(Model model) {
logger.info("进入列表页"); // 获取列表页 List
list = seckillService.getSeckillList(); logger.info("list = {}", list); model.addAttribute(list); // list.jsp + model = ModelAndView /WEB-INF/jsp/list.jsp return new ModelAndView("list").addObject("list", list); } @GetMapping("/{seckillId}/detail") public String detail(@PathVariable("seckillId") Long seckillId, Model model) {
if (null == seckillId) {
// 重定向 return "redirect:/seckill/list"; } Seckill seckill = seckillService.getById(seckillId); if (null == seckill) {
// 请求转发 return "forward:/seckill/list"; } model.addAttribute("seckill", seckill); return "detail"; }
36:web.xml相关设置
contextConfigLocation
classpath:spring/spring-*.xml
org.springframework.web.context.ContextLoaderListener
seckill-dispatcher
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/spring-*.xml
seckill-dispatcher
/
37:spring-web.xml 配置
38:所有ajax请求返回类型,封装json结果
public class SeckillResult
{
private boolean success; private T data; private String error; }
39:导出秒杀url
@PostMapping(value = "/{seckillId}/exposer", produces = {"application/json;charset=UTF-8"}) @ResponseBody public SeckillResult
exposer(@PathVariable("seckillId") Long seckillId) {
SeckillResult
result; try {
Exposer exposer = seckillService.exportSeckillUrl(seckillId); result = new SeckillResult
(true, exposer); } catch (Exception e) {
logger.error(e.getMessage(), e); result = new SeckillResult
(false, e.getMessage()); } return result; } 40:静态包含和动态包含的区别  <%@include...%>   静态包含:会将引用的源代码原封不动的附加过来,合并过来成一个jsp,对应一个servlet。
  动态包含:分别编译,被包含的jsp独立编译成servlet,然后和包涵的jsp页面编译生成的静态文档html做合并;总是会检查所包含文件的变化,适合包含动态文件。  静态包含是被包含的JSP合并到该servlet中。(一个servlet)  动态包含是被包含的JSP先运行servlet,再把运行结果合并到包含的html中(多个servlet)。 41:tag.jsp 引入标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>

 

42:在detail.jsp中

$(function (){
// 使用EL表达式传入参数 seckill.detail.init({
seckillId : ${seckill.seckillId}, startTime : ${seckill.startTime.time},//毫秒 endTime : ${seckill.endTime.time} }); }); 43:seckill.js 代码
// 存放主要交互逻辑js代码 // javascript模块化 var seckill = {
// 封装秒杀相关ajax的url URL:{
now : function(){
return '/seckill/time/now'; }, exposer : function (seckillId) {
return '/seckill/'+seckillId+'/exposer'; }, execution : function (seckillId,md5) {
return '/seckill/'+seckillId+'/'+md5+'/execution'; } }, // 处理秒杀逻辑 handleSeckillKill : function (seckillId,node) {
// 获取秒杀地址,控制显示逻辑,执行秒杀 node.hide() .html('');//按钮 $.post(seckill.URL.exposer(seckillId),{},function (result) {
// 在回调函数中,执行交互流程 if(result && result['success']){
var exposer = result['data']; if(exposer['exposed']){
// 开启秒杀 // 获取秒杀地址 var md5 = exposer['md5']; var killUrl = seckill.URL.execution(seckillId,md5); console.log("killUrl:"+killUrl); // 绑定一次点击事件 $('#killBtn').one('click',function () {
// 执行秒杀请求 // 1:先禁用按钮 $(this).addClass('disabled'); // 2:发送秒杀请求 $.post(killUrl,{},function(result){
if(result && result['success']){
var killResult = result['data']; var state = killResult['state']; var stateInfo = killResult['stateInfo']; // 3:显示秒杀结果 node.html(''+stateInfo+''); } }); }); node.show(); }else {
// 未开启秒杀 var now = exposer['now']; var start = exposer['start']; var end = exposer['end']; // 重新计算计时逻辑 seckill.mycountdown(seckillId,now,start,end); } }else{
console.log('result:'+result); } }); }, // 验证手机号 validatePhone:function (phone) {
if(phone && phone.length == 11 && !isNaN(phone)){
return true; }else{
return false; } }, mycountdown : function(seckillId,nowTime,startTime,endTime){
var seckillBox = $('#seckill-box'); // 时间判断 if(nowTime > endTime){
// 秒杀结束 seckillBox.html('秒杀结束!'); }else if(nowTime < startTime){
// 秒杀未开始 var killTime = new Date(startTime + 1000); seckillBox.countdown(killTime,function(event){
// 时间格式 var format = event.strftime('秒杀倒计时: %D天 %H时 %M分 %S秒'); seckillBox.html(format); // 时间完成后回调时间 }).on('finish.countdown',function () {
// 获取秒杀地址,控制显示逻辑,执行秒杀 seckill.handleSeckillKill(seckillId,seckillBox); }); }else {
// 秒杀开始 seckill.handleSeckillKill(seckillId,seckillBox); } }, // 详情页秒杀逻辑 detail:{
// 详情页初始化 init : function (params) {
// 手机验证和登录 , 计时交互 // 规划我们的交互流程 // 在cookie中查找手机号 var killPhone = $.cookie('killPhone'); // 验证手机号 if(!seckill.validatePhone(killPhone)){
// 绑定phone // 控制输出 var killPhoneModal = $('#killPhoneModal'); // 显示弹出层 killPhoneModal.modal({
//显示弹出层 show:true, // 禁止位置关闭 backdrop:'static', // 关闭键盘事件 keyboard:false }); $('#killPhoneBtn').click(function(){
var inputPhone = $('#killPhoneKey').val(); console.log('inputPhone='+inputPhone); if(!seckill.validatePhone(killPhone)){
// 电话写入cookie $.cookie('killPhone',inputPhone,{expires:7,path:'/seckill'}); // 刷新页面 window.location.reload(); }else {
$('#killPhoneMessage').hide().html('').show(300); } }); } // 已经登录 // 计时交互 var startTime = params['startTime']; var endTime = params['endTime']; var seckillId = params['seckillId']; $.get(seckill.URL.now(),{},function(result){
if(result &&result['success']){
var nowTime = result['data']; // 时间判断,计时交互 seckill.mycountdown(seckillId,nowTime,startTime,endTime); }else{
console.log('result:'+result); } }); } } }
44:执行秒杀相关代码
@PostMapping(value = "/{seckillId}/{md5}/execution", produces = {"application/json;charset=UTF-8"})     @ResponseBody     public SeckillResult
execute(@PathVariable("seckillId") Long seckillId, @PathVariable("md5") String md5, @CookieValue(value = "killPhone", required = false) Long phone) {
if (StringUtils.isEmpty(phone)) {
return new SeckillResult
(false, "未注册"); } // SeckillResult
result; try {
// 存储过程调用 SeckillExecution execution = seckillService.executeSeckill(seckillId, phone, md5); return new SeckillResult
(true, execution); } catch (SeckillCloseException e1) {
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.END); return new SeckillResult
(true, execution); } catch (RepeatKillException e2) {
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL); return new SeckillResult
(true, execution); } catch (Exception e) { logger.error(e.getMessage(), e); SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR); return new SeckillResult
(true, execution); } } @GetMapping("/time/now") @ResponseBody public SeckillResult
time() { Date now = new Date(); return new SeckillResult
(true, now.getTime()); } 45:红色的部分就表示会发生高并发的地方,绿色部分表示对于高并发没有影响

46:cdn使用

47:秒杀接口地址可以优化为从redis获取相关秒杀产品信息

48:秒杀操作优化

  • 一行数据竞争:热点商品

 

Java控制事务行为分析

图片描述

瓶颈分析

图片描述

优化分析

行级锁在Commit之后释放优化方向减少行级锁持有时间

 

比如一个热点商品所有人都在抢,那么会在同一时间对数据表中的一行数据进行大量的update set操作。

行级锁在commit之后才释放,所以优化方向是减少行级锁的持有时间

优化思路:

  • 把客户端逻辑放到MySQL服务端,避免网络延迟和GC影响。使用存储过程:整个事务在MySQL端完成,用存储过程写业务逻辑,服务端负责调用。

 

 
 49:其他方案实现

50:redis 访问

 

redis.clients
jedis
2.7.3
com.dyuproject.protostuff
protostuff-core
1.0.8
com.dyuproject.protostuff
protostuff-runtime
1.0.8
public class RedisDao {
private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final JedisPool jedisPool; public RedisDao(String ip,int port){
jedisPool = new JedisPool(ip,port); } private RuntimeSchema
schema = RuntimeSchema.createFrom(Seckill.class); public Seckill getSeckill(long seckillId){
// redis操作逻辑 try {
Jedis jedis = jedisPool.getResource(); try{
String key = "seckill:"+seckillId; // 并没有实现内部序列化操作 // get->byte[] ->反序列化 ->Object(Seckill) // 采用自定义序列化 // protostuff : pojo byte[] bytes = jedis.get(key.getBytes()); // 缓存中获取到 if(bytes != null){
Seckill seckill = schema.newMessage(); ProtostuffIOUtil.mergeFrom(bytes,seckill,schema); // seckill 被反序列 return seckill; } }finally {
jedis.close(); } } catch (Exception e){
logger.error(e.getMessage(),e); } return null; } public String putSeckill(Seckill seckill){
// set Object(Seckill) -> 序列化 ->byte[] try {
Jedis jedis = jedisPool.getResource(); try{
String key = "seckill:"+seckill.getSeckillId(); byte[] bytes = ProtostuffIOUtil.toByteArray(seckill,schema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); // 超时缓存 1小时 int timeout = 60 * 60; String result = jedis.setex(key.getBytes(),timeout,bytes); return result; }finally {
jedis.close(); } } catch (Exception e){
logger.error(e.getMessage(),e); } return null; } } 加入ioc
51:Run to Cursor ( 运行到光标处 ) 52:简单优化把插入购买明细操作放在减库存前面减少行锁的时间 53:使用存储过程减少网路延迟和gc操作
-- 秒杀执行存储过程 DELIMITER $$ -- console ; 转换为 $$ -- 定义存储过程 -- 参数:in 输入参数;out 输出参数 -- row_count():返回上一条修改类型sql的影响行数 -- 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_kill_time         and start_time < v_kill_time         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=-3; -- 执行存储过程 call execute_seckill(1003,13521542111,new(),@r_result); select @r_result -- 存储过程 -- 1:存储过程优化:事务行级锁持有的时间 -- 2:不要过度依赖存储过程 -- 3:简单的逻辑,可以应用存储过程 -- 4:QPS:一个秒杀单6000/qps 54:存储过程调用
/**  * 使用存储过程执行秒杀  * @param paramMap  */ void killByProcedure(Map
paramMap);
55:相关依赖

int result = MapUtils.getInteger(map, "result", -2);

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

转载于:https://www.cnblogs.com/zyy1688/p/9810376.html

你可能感兴趣的文章
【MongoDB for Java】Java操作MongoDB
查看>>
0c-42-ARC模式下如何兼容非ARC的类
查看>>
程序员进阶之路—如何独当一面
查看>>
闲话WPF之十一(Dependency属性 [3] )
查看>>
JS组件系列——基于Bootstrap Ace模板的菜单Tab页效果优化
查看>>
eclipse中tomcat快速启动关闭参数设置
查看>>
C++ extern "c "的作用
查看>>
实践:几十亿条数据分布在几十个节点上的毫秒级实时排序方法
查看>>
PMWiki安装教程
查看>>
JAVA多线程之volatile 与 synchronized 的比较
查看>>
一个经典编程面试题的“隐退”
查看>>
POJ2109
查看>>
显示创建一个表的SQL语句
查看>>
光流和KLT
查看>>
Linux c括号作用域【原创笔记】
查看>>
分分钟带你玩转 Web Services【2】CXF
查看>>
ASP.NET MVC+LINQ开发一个图书销售站点(7):图书分类管理
查看>>
如何做一名技术管理者
查看>>
Resouce, platform_device 和 platform_driver 的关系【转】
查看>>
HTML标记大全参考手册(转载)
查看>>