您现在的位置是:首页 > 文章详情

Java高并发秒杀业务Api-Service层构建过程

日期:2018-05-04点击:238

章节目录

  • 秒杀Service 接口开发工作
    • 秒杀业务逻辑编写
    • spring-IOC 管理 service 组件
      • context:component-scan
    • Spring 声明式事务
    • junit测试

创建基本的代码包层

1.创建DTO - 数据传输层对象

网络数据到达Controller 层后会使用框架自带的数据绑定 以及反序列化为dto对 象,并作为参数传递至service层进行处理。 

2.业务接口实现
注意:业务接口的实现需要站在使用者的角度去设计接口

  • 方法定义粒度-非常明确,参数简练,直接,不要一个大map对象去传递,return 还可以抛出异常。

代码如下:
业务逻辑接口声明类 SecKillService.java

package org.seckill.service; import org.seckill.domain.SecKill; import org.seckill.dto.Exposer; import org.seckill.dto.SecKillExcution; import org.seckill.exception.RepeatKillException; import org.seckill.exception.SecKillCloseException; import org.seckill.exception.SecKillException; import java.util.List; /** * 业务接口的实现需要站在使用者的角度去设计接口 * 三个方面:方法定义粒度、参数、返回类型 dto就可以 */ public interface SecKillService { /** * 返回秒杀商品列表 * * @return */ List<SecKill> getSecKillList(); /** * 查询秒杀商品单条记录 * * @param secKillId * @return */ SecKill getSecKillById(long secKillId); /** * 秒杀开启时,输出秒杀接口的地址,否则输出系统时间,和秒杀时间 * * @param secKillId * @return */ Exposer exportSecKillUrl(long secKillId); /** * 执行秒杀操作 * 验证当前的excuteSecKill id 与 传递过来的md5是否相同 * * @param secKillId * @param userPhone * @param md5 */ SecKillExcution excuteSecKill(long secKillId, String userPhone, String md5) throws SecKillException, RepeatKillException, SecKillCloseException; } 

业务逻辑接口实现类-SecKillServiceImpl

package org.seckill.service.impl; import org.seckill.dao.SecKillDao; import org.seckill.dao.SuccessKilledDao; import org.seckill.domain.SecKill; import org.seckill.domain.SuccessKilled; import org.seckill.dto.Exposer; import org.seckill.dto.SecKillExcution; import org.seckill.enums.SecKillStateEnum; import org.seckill.exception.RepeatKillException; import org.seckill.exception.SecKillCloseException; import org.seckill.exception.SecKillException; import org.seckill.service.SecKillService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.DigestUtils; import java.util.Date; import java.util.List; @Service public class SecKillServiceImpl implements SecKillService { //在业务逻辑层打日志 private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowire private SecKillDao secKillDao; @Autowire private SuccessKilledDao successKilledDao; //md5加盐,混淆加密 private final String salt = "asdasd8Zy*&ZCY87ywer7t678tzt67wer"; public List<SecKill> getSecKillList() { return secKillDao.queryAll(0, 4); } public SecKill getSecKillById(long secKillId) { return secKillDao.queryById(secKillId); } public Exposer exportSecKillUrl(long secKillId) { SecKill secKill = secKillDao.queryById(secKillId); if (secKill == null) { return new Exposer(false, secKillId);//没有相关产品的秒杀活动 } 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 = null; return new Exposer(secKillId, md5, true); } /** * 生成对应秒杀商品的md5值,做参数校验 * 保证可重用 * * @param secKillId * @return */ private String getMD5(long secKillId) { String base = secKillId + "/" + salt;//用户不知道salt String md5 = DigestUtils.md5DigestAsHex(base.getBytes()); return md5; } /** * 执行秒杀 * * @param secKillId * @param userPhone * @param md5 * @return * @throws SecKillException * @throws RepeatKillException * @throws SecKillCloseException */ public SecKillExcution excuteSecKill(long secKillId, String userPhone, String md5) throws SecKillException, RepeatKillException, SecKillCloseException { try { //1.用户输入的md5值验证 if (md5 == null || !md5.equals(getMD5(secKillId))) { throw new SecKillException("seckill data rewrite"); } //2.减库存、执行秒杀逻辑+记录购买行为,执行秒杀时间 Date nowTime = new Date(); int updateCount = secKillDao.reduceStock(secKillId, nowTime); if (updateCount <= 0) { //没有更新到记录,秒杀结束 throw new SecKillCloseException("seckill is closed"); } else { //3.记录购买行为 insertSuccessKilled 可能出现数据库连接超时等问题,所以需要外层try int insertCount = successKilledDao.insertSuccessKilled(secKillId, userPhone); //唯一验证,secKillId + userPhone if (insertCount <= 0) {//数据库联合主键冲突 throw new RepeatKillException("seckill repeated"); } else {//秒杀成功,返回秒杀成功的实体 SuccessKilled successKilled = successKilledDao.queryByIdWithSecKill(secKillId, userPhone); //不优雅的实现方式,在多处需要用到提示信息时,我们可以采用统一的常量去返回,这样待客户端提示语改变时,我们可以统一进行更改。 // return new SecKillExcution(secKillId, 1, "秒杀成功", successKilled); return new SecKillExcution(secKillId, SecKillStateEnum.SUCCESS, successKilled); } } } catch (SecKillCloseException e1) { throw e1;//还是要回滚 } catch (RepeatKillException e2) { throw e2;//还是要回滚 } catch (Exception e) { logger.error(e.getMessage()); //所有异常最终会转化为 运行时异常,spring 的声明式事务会帮我们做rollback。 throw new SecKillException("seckill inner error" + e.getMessage()); } } } 

数据传输层类- Exposer

package org.seckill.dto; /** * 暴露秒杀接口 */ public class Exposer { private boolean exposed;//秒杀是否开启标志位 private String md5; //加密措施,暴露地址包括一个md5值 private long now; //系统当前时间(毫秒),方便浏览器计算距离服务器秒杀开启时间 private long secKillId; //秒杀商品的id private long start; private long end; //秒杀正在进行 public Exposer(long secKillId, String md5, boolean exposed) { this.secKillId = secKillId; this.md5 = md5; this.exposed = exposed; } //秒杀结束或还没开启 public Exposer(boolean exposed, long secKillId, long now, long start, long end) { this.exposed = exposed; this.secKillId = secKillId; this.now = now; this.start = start; this.end = end; } //没有相关商品秒杀活动 public Exposer(boolean exposed, long secKillId) { this.exposed = exposed; this.secKillId = secKillId; } public boolean isExposed() { return exposed; } public void setExposed(boolean exposed) { this.exposed = exposed; } public String getMd5() { return md5; } public void setMd5(String md5) { this.md5 = md5; } public long getNow() { return now; } public void setNow(long now) { this.now = now; } public long getStart() { return start; } public void setStart(long start) { this.start = start; } public long getEnd() { return end; } public void setEnd(long end) { this.end = end; } public long getSecKillId() { return secKillId; } public void setSecKillId(long secKillId) { this.secKillId = secKillId; } @Override public String toString() { return "Exposer{" + "exposed=" + exposed + ", md5='" + md5 + '\'' + ", now=" + now + ", secKillId=" + secKillId + ", start=" + start + ", end=" + end + '}'; } } 

数据传输层-SecKillExcution

package org.seckill.dto; import org.seckill.domain.SuccessKilled; import org.seckill.enums.SecKillStateEnum; /** * 执行秒杀之后的结果 */ public class SecKillExcution { private long secKillId; private int state;//状态的标识 private String stateInfo;//状态表示 private SuccessKilled successSecKilled;//秒杀成功对象 //成功 jakson 在转化枚举的时候会出现问题,不支持枚举序列化 public SecKillExcution(long secKillId, SecKillStateEnum secKillStateEnum, SuccessKilled successSecKilled) { this.secKillId = secKillId; this.state = secKillStateEnum.getState(); this.stateInfo = secKillStateEnum.getStateInfo(); this.successSecKilled = successSecKilled; } //失败,使用到枚举 public SecKillExcution(long secKillId, SecKillStateEnum secKillStateEnum) { this.secKillId = secKillId; this.state = secKillStateEnum.getState(); this.stateInfo = secKillStateEnum.getStateInfo(); } public long getSecKillId() { return secKillId; } public void setSecKillId(long secKillId) { this.secKillId = secKillId; } public int getState() { return state; } public void setState(int state) { this.state = state; } public String getStateInfo() { return stateInfo; } public void setStateInfo(String stateInfo) { this.stateInfo = stateInfo; } public SuccessKilled getSuccessSecKilled() { return successSecKilled; } public void setSuccessSecKilled(SuccessKilled successSecKilled) { this.successSecKilled = successSecKilled; } @Override public String toString() { return "SecKillExcution{" + "secKillId=" + secKillId + ", state=" + state + ", stateInfo='" + stateInfo + '\'' + ", successSecKilled=" + successSecKilled + '}'; } } 

业务运行时异常类-SecKillException、RepeatKillException、SecKillCloseException

package org.seckill.exception; /** * 秒杀相关业务异常 */ public class SecKillException extends RuntimeException { public SecKillException(String message) { super(message); } public SecKillException(String message, Throwable cause) { super(message, cause); } } 
package org.seckill.exception; /** * 重复秒杀的异常(运行时异常) * 声明式事务,只接收运行时异常 */ public class RepeatKillException extends SecKillException { public RepeatKillException(String message) { super(message); } public RepeatKillException(String message, Throwable cause) { super(message, cause); } } 
package org.seckill.exception; /** * 秒杀关闭异常-友好响应给用户 */ public class SecKillCloseException extends SecKillException { public SecKillCloseException(String message) { super(message); } public SecKillCloseException(String message, Throwable cause) { super(message, cause); } } 

2.spring-IOC管理 service 组件

spring-service.xml 配置

<?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" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 扫描service包下所有使用注解的类型--> <context:component-scan base-package="org.seckill.service" /> <!-- 配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据库连接池--> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置基于注解的声明式事务 默认使用注解来管理事务行为 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans> 

Spring 声明式事务

什么是声明式事务

img_bb50e6fbb41f3016d02150fe84de5b95.png
声明式事务

底层原理:采用动态代理的方式为我们的事务核心逻辑添加开启事务、回滚提交事务的控制操作

声明式事务使用方式

img_0210c1bcfe58549ca8840f410bb9c1b2.png
声明式事务

推荐使用@Transactional

声明式事务的传播行为
定义:即事务方法的嵌套
一个业务需要调用多个声明了事务控制的方法,那么最新组合的事务是重新启动一个事务,还是说沿用老的事务呢?

默认的事务传播行为: propagation_required 浅析如下: ServiceA { void methodA() { ServiceB.methodB(); } } ServiceB { void methodB() { } } 

比如说ServiceB.methodB 事务传播行为定义为PROPAGATION_REQUIRED

  • 那么当ServiceA.methodA 调用 ServiceB.methodB 时,methodA起了新的事
    务,那么ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内
    部,就不再起新的事务。
  • 而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己
    分配一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的任何
    地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被提交,
    但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚

相当于 methodB 通过自己的事务传播行为告诉methodA 自己使用事务的原
则,告诉methodA 你要有事务我methodB就用你的,如果methodA没有事务,
那你methodA就需要创建一个事务。

什么时候回滚事务
当业务方法抛出运行时异常(RuntimeException)的时候spring 事务管理器会进行commit

配置事务管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"> </bean> 

配置基于注解的声明式事务

<tx:annotation-driven transaction-manager="transactionManager"/> 

使用注解控制事务方法的优点

  • 开发团队达成一致约定,约定明确标注事务方法的编程风格
  • 保证事务方法的执行时间尽可能的短,不要穿插其他的网络操作,RPC/HTTP请求,如果必须需要的话,那么将这些请求剥离出来,形成一个干净的方法调用。不要混合编写和外部系统进行网络通信的代码。
  • 不是所有的方法都需要事务,select、insert操作,单条语句的insert、update都不需要事务操作、不需要并发控制&多个操作联合形成一个事务时,不需要设置事务,因为mysql有autocommit=1的设置。

上述是性能杀手啊 ,注意再注意。

单元测试

package org.seckill.service; import org.junit.Test; import org.junit.runner.RunWith; import org.seckill.domain.SecKill; import org.seckill.dto.Exposer; import org.seckill.dto.SecKillExcution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({"classpath:spring/spring-dao.xml", "classpath:spring/spring-service.xml"}) public class SecKillServiceTest { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private SecKillService secKillService; @Test public void getSecKillList() throws Exception { List<SecKill> secKillList = secKillService.getSecKillList(); logger.info("list{}", secKillList); } @Test public void getSecKillById() throws Exception { long secKillId = 1000L; SecKill secKill = secKillService.getSecKillById(secKillId); logger.info("seckill{}", secKill); } @Test public void exportSecKillUrl() throws Exception { long secKillId = 1000L; Exposer exposer = secKillService.exportSecKillUrl(secKillId); logger.info("exposer{}", exposer); } @Test public void excuteSecKill() throws Exception { long secKillId = 1000L; String userPhone = "15300815981"; String md5 = "6e3cc65f3b42e656bdbc55a6a381f5d0"; SecKillExcution secKillExcution = secKillService.excuteSecKill(secKillId, userPhone, md5); logger.info("secKillExcution{}", secKillExcution); } } 

intellj idea 下单元测试快捷键:ctrl+shift+t

原文链接:https://yq.aliyun.com/articles/649969
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章