Javaのシステムサービススパイク高い同時実行
特定の参照githubの
サービス・インターフェースの設計
当社のサービス・インターフェースとその実装クラスを格納するサービスパッケージの下に作成されたパッケージOrg.seckill、このような層は、異常が現れた繰り返しの異常スパイクサービス品として保管パッケージの例外を作成し、異常なスパイクは、パッケージDTOとして、閉じられましたサービスデータをカプセル化し、データ転送DTOとWebサービス層を実行するためのエンティティ:トランスポート層、およびその中のDTOエンティティとの間の差。
package org.seckill.service;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import java.util.List;
public interface SeckillService {
/**
* 查询全部的秒杀记录
* @return
*/
List<Seckill> getSeckillList();
/**
*查询单个秒杀记录
* @param seckillId
* @return
*/
Seckill getById(long seckillId);
//再往下,是我们最重要的行为的一些接口
/**
* 在秒杀开启时输出秒杀接口的地址,否则输出系统时间和秒杀时间
* @param seckillId
*/
Exposer exportSeckillUrl(long seckillId);
/**
* 执行秒杀操作,有可能失败,有可能成功,所以要抛出我们允许的异常
* @param seckillId
* @param userPhone
* @param md5
* @return
*/
SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException,RepeatKillException,SeckillCloseException;
}
次のようにExposer.java、アドレス情報パッケージスパイクDTO作成し、それぞれのパッケージには、各属性の役割は、コード内のコメントに記載されている、コードは次のとおりです。
package org.seckill.dto;
public class Exposer {
//是否开启秒杀
private boolean exposed;
//MD5加密措施
private String md5;
//id
private long seckillId;
//系统当前时间(毫秒)
private long now;
//开启时间
private long start;
//关闭时间
private long end;
public Exposer(boolean exposed, String md5, long seckillId) {
this.exposed = exposed;
this.md5 = md5;
this.seckillId = seckillId;
}
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 getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
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;
}
@Override
public String toString() {
return "Exposer{" +
"exposed=" + exposed +
", md5='" + md5 + '\'' +
", seckillId=" + seckillId +
", now=" + now +
", start=" + start +
", end=" + end +
'}';
}
}
そしてSeckillExecution.javaは、かどうかスパイク成功したかどうかを判断するために使用され、成功は、すべての情報(製品IDスパイクを含め、スパイク成功した状態、成功情報、ユーザの詳細)成功したスパイクを返します。我々は(繰り返しスパイク許可例外をスローに失敗異常、異常なスパイクの端部)は、次の通り:
package org.seckill.dto;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
public class SeckillExecution {
private long seckillId;
//秒杀执行结果状态
private int state;
//状态表示
private String stateInfo;
//秒杀成功对象
private SuccessKilled successKilled;
public SeckillExecution(long seckillId, SeckillStatEnum seckillStatEnum, SuccessKilled successKilled) {
this.seckillId = seckillId;
this.state = seckillStatEnum.getState();
this.stateInfo = seckillStatEnum.getStateInfo();
this.successKilled = successKilled;
}
public SeckillExecution(long seckillId, SeckillStatEnum seckillStatEnum) {
this.seckillId = seckillId;
this.state = seckillStatEnum.getState();
this.stateInfo = seckillStatEnum.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 getSuccessKilled() {
return successKilled;
}
public void setSuccessKilled(SuccessKilled successKilled) {
this.successKilled = successKilled;
}
@Override
public String toString() {
return "SeckillExecution{" +
"seckillId=" + seckillId +
", state=" + state +
", stateInfo='" + stateInfo + '\'' +
", successKilled=" + successKilled +
'}';
}
}
その後、我々は、スパイクが異常RepeatKillException.javaを繰り返し、異常スパイクの過程で事業許可証を作成する必要があります。
package org.seckill.exception;
import org.seckill.entity.Seckill;
/**
* 重复秒杀异常,是一个运行期异常,不需要我们手动try catch
* Mysql只支持运行期异常的回滚操作
*/
public class RepeatKillException extends SeckillException{
public RepeatKillException(String message) {
super(message);
}
public RepeatKillException(String message, Throwable cause) {
super(message, cause);
}
}
閉じるスパイク異常SeckillCloseException.java:
package org.seckill.exception;
import org.seckill.entity.Seckill;
//秒杀关闭异常
public class SeckillCloseException extends SeckillException{
public SeckillCloseException(String message) {
super(message);
}
public SeckillCloseException(String message, Throwable cause) {
super(message, cause);
}
}
そして、異常なスパイクのすべてが含まれている例外がSeckillException.javaビジネスを表示されます。
package org.seckill.exception;
public class SeckillException extends RuntimeException {
public SeckillException(String message) {
super(message);
}
public SeckillException(String message, Throwable cause) {
super(message, cause);
}
}
スパイクサービスインタフェースを実装します
次のようにサービスパックImplをパッケージストアの実装クラスで作成し、SeckillServiceImpl.javaは、読み取ります。
package org.seckill.service.Impl;
import org.seckill.dao.SeckillDao;
import org.seckill.dao.SuccessKilledDao;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import org.seckill.service.SeckillService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import java.security.DigestException;
import java.util.Date;
import java.util.List;
//@Component @Service @Dao @Controller
@Service
public class SeckillServiceImpl implements SeckillService{
private org.slf4j.Logger logger= LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
//MD5盐值字符串,用于混淆MD5
private final String slat="dsnvs&&(&(*ndkasj23dbkasbdkj43sabdka543bdkab%^%$%$%";
public List<Seckill> getSeckillList() {
return seckillDao.queryAll(0,4);
}
public Seckill getById(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=getMD5(seckillId);
return new Exposer(true,md5,seckillId);
}
private String getMD5(long seckillId){
String base=seckillId+"/"+slat;
String md5= DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
@Transactional
/**
* 使用注解控制事务方法的优点:
* 1.开发团队达成一致约定,明确标注事务方法的编程风格
* 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部
* 3.不是所有的方法都需要事务,如只有一条修改操作、只读操作不要事务控制
*/
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {
if(md5==null||!md5.equals(getMD5(seckillId))){
throw new SeckillException("seckill data rewrite");
}
//执行业务逻辑
Date nowTime=new Date();
try {
int updateCount = seckillDao.reduceNumber(seckillId, nowTime);
if (updateCount <= 0) {
//没有更新到记录,秒杀结束
throw new SeckillCloseException("seckill is closed");
} else {
//记录购买行为
int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
if (insertCount <= 0) {
throw new RepeatKillException("seckill repeated");
} else {
//秒杀成功
SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);
}
}
} 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());
}
}
}
次のように列挙型SeckillStatEnum.javaを作成し、org.seckillリストパケット列挙型の下にパッケージを作成し、読み取ります。
package org.seckill.enums;
public enum SeckillStatEnum {
SUCCESS(1,"秒杀成功"),
END(0,"秒杀结束"),
REPEAT_KILL(-1,"重复秒杀"),
INNER_ERROR(-2,"系统异常"),
DATE_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;
}
}
春管理サービスを使用します
次のように、春パッケージの春-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"
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">
<!--扫描service包下所有使用注解的类型-->
<context:component-scan base-package="cn.codingxiaxw.service"/>
</beans>
そして、やり方アノテーションを使用してサービス実装クラス春IOCは、容器に追加しました:
//@Component @Service @Dao @Controller
@Service
public class SeckillServiceImpl implements SeckillService
使用スプリング宣言型トランザクション構成
宣言型トランザクションを使用します。:. ProxyFactoryBean + XMl.2.txの1つの道早期使用:アドバイス+メリット名前空間AOP、この構成は、永久的な構成です。3.ノート@Transactionalの方法。実際の開発では、それは私たちのビジネスの3分の1が制御され、使用することをお勧めします、利点は、コードの下の注を参照してください。レッツのconfigure宣言型トランザクションは、春-service.xmlにトランザクションの設定を追加します。
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置基于注解的声明式事务
默认使用注解来管理事务行为-->
<tx:annotation-driven transaction-manager="transactionManager"/>
そして、法上のメソッドサービス実装クラスに加え、取引は取引明細のノートが必要です。
//秒杀是否成功,成功:减库存,增加明细;失败:抛出异常,事务回滚
@Transactional
/**
* 使用注解控制事务方法的优点:
* 1.开发团队达成一致约定,明确标注事务方法的编程风格
* 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部
* 3.不是所有的方法都需要事务,如只有一条修改操作、只读操作不要事务控制
*/
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException {}
統合テスト・ロジック・サービスの使用
SeckillService使用インタフェースのIDEAのショートカットはshift+command+T
、迅速JUnitのテストクラスを生成します。情報項目リスト又はリストを得るために適切に達成2つの方法が、上記サービス実装クラス、以下の試験:
package org.seckill.service;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
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;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/spring-*.xml")
public class SeckillServiceTest {
private final Logger logger= LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillService seckillService;
@Test
public void getSeckillList() {
List<Seckill> list=seckillService.getSeckillList();
logger.info("list={}",list);
}
@Test
public void getById() {
long seckillId=1000;
Seckill seckill=seckillService.getById(seckillId);
logger.info("seckill={}",seckill);
}
//完整逻辑代码测试,注意可重复执行
@Test
public void testSeckillLogic() {
long seckillId=1000;
Exposer exposer=seckillService.exportSeckillUrl(seckillId);
if(exposer.isExposed()){
logger.info("exposer={}"+exposer);
long userPhone=13476191896L;
String md5=exposer.getMd5();
try{
SeckillExecution seckillExecution=seckillService.executeSeckill(seckillId,userPhone,md5);
logger.info("result={}",seckillExecution);
}catch (RepeatKillException e){
logger.error(e.getMessage());
}catch (SeckillCloseException e1){
logger.error(e1.getMessage());
}
}else{
//秒杀未开启
logger.warn("exposer={}",exposer);
}
}
}