Java高併發秒殺API之service層實現(二)
阿新 • • 發佈:2018-12-29
二 service層實現
1.內容
站在使用者的角度設計介面
三個方向 :方法粒度,引數,返回型別
2.程式碼
SeckillService
package org.seckill.service;
import java.util.List;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillException;
import org.seckill.exception.seckillCloseException;
/**
* 站在使用者角度設計介面
* 三個方面 方法粒度 引數 返回型別
*/
public interface SeckillService {
/**
*
* 查詢所有秒殺記錄
* @return
*/
List <Seckill> getSeckillList();
/**
*
* 查詢單個秒殺記錄
* @param seckillId
* @return
*/
Seckill getById(long seckillId);
/**
* 輸出秒殺介面地址 否則輸出系統時間和秒殺時間
* @param seckillId
*/
Exposer exportSeckillUrl(long seckillId);
//執行秒殺操作
SeckillExecution excuteSeckill(long seckillId,long userPhone,String md5) throws RepeatKillException,seckillCloseException,SeckillException;
//執行秒殺操作by儲存過程
SeckillExecution excuteSeckillProcedure(long seckillId,long userPhone,String md5) throws RepeatKillException,seckillCloseException,SeckillException;
}
重複秒殺異常 RepeatKillException
package org.seckill.exception;
//重複秒殺異常(執行期異常)
public class RepeatKillException extends RuntimeException{
public RepeatKillException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public RepeatKillException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
}
秒殺關閉異常 seckillCloseException
package org.seckill.exception;
/**
*
* 秒殺關閉異常
* @author LCJA
*
*/
public class seckillCloseException extends RuntimeException{
public seckillCloseException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public seckillCloseException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
}
秒殺相關的所有業務異常 SeckillException
package org.seckill.exception;
/**
*
* 秒殺相關的所有業務異常
* @author LCJA
*
*/
public class SeckillException extends RuntimeException{
public SeckillException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public SeckillException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
}
SeckillServiceImpl 實現類
package org.seckill.service.impl;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.MapUtils;
import org.seckill.dao.SeckillDao;
import org.seckill.dao.SuccessKilledDao;
import org.seckill.dao.cache.RedisDao;
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.SeckillException;
import org.seckill.exception.seckillCloseException;
import org.seckill.service.SeckillService;
import org.slf4j.Logger;
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;
//@component @service @dao @conroller
@Service
public class SeckillServiceImpl implements SeckillService{
private Logger logger=LoggerFactory.getLogger(this.getClass());
// mabaits 使用mapper形式 注入spring容器中
//注入service依賴
@Autowired//@[email protected]
private SeckillDao seckilldao;
@Autowired
private SuccessKilledDao successkilleddao;
@Autowired
private RedisDao redisdao;
//用於混淆md5
private final String slat="abcd";
public List<Seckill> getSeckillList() {
// TODO Auto-generated method stub
return seckilldao.queryAll(0, 4);
}
public Seckill getById(long seckillId) {
// TODO Auto-generated method stub
return seckilldao.queryById(seckillId);
}
public Exposer exportSeckillUrl(long seckillId) {
//優化點 : 快取優化
/**
* get from cache
* if null
* get db
* else
* put cache
* locgin
* 可以這樣做的原因 秒殺單 一般不做修改
* 超時維護
* 一般做叢集
*/
//訪問redis
Seckill seckill = redisdao.getSeckill(seckillId);
if(seckill == null){
seckill=seckilldao.queryById(seckillId);
System.out.println("seckill----"+seckill);
if(seckill== null){
return new Exposer(false, seckillId);
}else{
//放入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=getMD5(seckillId);
return new Exposer(true,md5,seckillId);
}
/**
* 使用註解控制事務有點
* 開發團隊達成一致約定 明確事務方法的程式設計風格
* 保證事務方法的執行時間竟可能短不要穿插其他網路操作
* 不是所有的方法都需要事務 如只有一條修改操作 ,只讀操作不需要控制事務
* 通過異常來告訴宣告式事務 回滾還是commit
*/@Transactional
public SeckillExecution excuteSeckill(long seckillId, long userPhone, String md5)
throws RepeatKillException, seckillCloseException, SeckillException {
try{
if(md5==null||!md5.equals(getMD5(seckillId))){
throw new SeckillException("seckill data rewrite");
}
int insertCount=successkilleddao.insertSuccessKilled(seckillId, userPhone);
if(insertCount<=0){
//重複秒殺
throw new RepeatKillException("seckill repeated");
}else{
//執行秒殺邏輯 減庫存 +記錄購買行為
//減庫存熱點商品競爭
int updateCount = seckilldao.reduceNumber(seckillId, new Date());
if(updateCount <= 0){
//沒有更新到記錄,秒殺結束
throw new seckillCloseException("seckill losed");
}else{
//減庫存成功 記錄購買行為
//秒殺成功
SuccessKilled successKilled=successkilleddao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId,SeckillStatEnum.SUCCESS,successKilled);
}
}
}catch(Exception e){
logger.error(e.getMessage(),e);
throw new SeckillException("seckill inner error:"+e.getMessage());
}
}
private String getMD5(long seckillId){
String base=seckillId+"/"+slat;
String md5=DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
public SeckillExecution excuteSeckillProcedure(long seckillId, long userPhone, String md5)
throws RepeatKillException, seckillCloseException, SeckillException {
if(md5==null||!md5.equals(getMD5(seckillId))){
return new SeckillExecution(seckillId,SeckillStatEnum.DATA_REWRITE);
}
Date killTime = new Date();
Map <String, Object>map =new HashMap<String, Object>();
map.put("seckillId", seckillId);
map.put("phone", userPhone);
map.put("killTime", killTime);
map.put("result", null);
try {
seckilldao.killByProcedure(map);
int result = MapUtils.getInteger(map, "result",-2);
if(result==1){
SuccessKilled 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());
return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
}
}
}
SeckillStatEnum
package org.seckill.enums;
//使用列舉表示常量資料欄位
public enum SeckillStatEnum {
SUCCESS(1,"秒殺成功"),
END(0,"秒殺結束"),
REPEAT_KILL(-1,"重複秒殺"),
INNER_ERROR(-2,"系統異常"),
DATA_REWRITE(-3,"資料篡改")
;
private int state;
private String stateInfo;
private SeckillStatEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
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 static SeckillStatEnum stateOf(int index){
for (SeckillStatEnum state :values()){
if(state.getState() == index){
return state;
}
}
return null;
}
}
3.spring託管service
spring會通過spring工廠建立物件
seckillservice 依賴 SeckillDao和SuccessKillDao,
SeckillDao和SuccessKillDao依賴SqlSessionFactory,
SqlSessionFactory 依賴 資料來源..
原因
物件建立統一管理,
規範生命週期管理,
靈活的依賴注入,
一致獲取物件。
ioc使用場景
xml | 註解 | 配置類 |
---|---|---|
1.bean實現類來自第三方類庫,如DataSource等;2需要名稱空間配置,如:context,aop,mvc等 | 專案中自身開發使用的類,可直接在程式碼中使用註解 如@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:aop="http://www.springframework.org/schema/aop"
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"></context:component-scan>
<!-- 事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入資料庫連線池 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置註解式宣告事務 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
4.spring宣告式事務
丟擲執行期異常時,會回滾,小心try-catch
這個是 tx:advice+aop名稱空間 一次配置永久生效(看起來很美好),本篇沒有采用
<!-- 配置事務事務屬性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="append*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="modify*" propagation="REQUIRED" />
<tx:method name="edit*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="get*" propagation="SUPPORTS" />
<tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
<!-- 配置事務切點,並把切點和事務屬性關聯起來 -->
<aop:config>
<aop:pointcut expression="execution(* com.jinlou.service..*Impl.*(..))" id="txPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
本篇採用的是註解的形式
<!-- 配置註解式宣告事務 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
使用註解控制事務的優點;
1.開發團隊達成一致約定,明確標註事務方法的程式設計風格
2.保證事務執行時儘可能短,不要穿插其他網路操作(rpc(遠端服務呼叫),http請求)
3.不是所有的方法都不是需要事務
5.單元測試
package org.seckill.service;
import java.util.List;
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.service.SeckillService;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
"classpath:spring/spring-dao.xml",
"classpath:spring/spring-service.xml"
})
public class SeckillServiceTest {
private Logger logger=LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillService seckillService;
@Test
public void testGetSeckillList() {
List <Seckill> list=seckillService.getSeckillList();
logger.info("list={}",list);
}
@Test
public void testGetById() {
long id=1003;
Seckill seckill=seckillService.getById(id);
logger.info("seckill={}seckill",seckill);
}
@Test
public void testExportSeckillUrl() {
long id=1000;
Exposer exposer= seckillService.exportSeckillUrl(id);
logger.info("exposer={}",exposer);
}
//exposed=true, md5=8e7add56132475b9141111ed8e961613, seckillId=1000, now=0, start=0, end=0]
@Test
public void testExcuteSeckill() {
long id=1000;
long phone=12345679;
String md5="8e7add56132475b9141111ed8e961613";
SeckillExecution seckillexcution =seckillService.excuteSeckill(id, phone, md5);
logger.info("seckillexcution={}",seckillexcution);
}
@Test
public void executeSeckillProcedure(){
long seckillId =1000;
long phone =1313131313;
Exposer exposer = seckillService.exportSeckillUrl(seckillId);
if(exposer.isExposed()){
String md5 = exposer.getMd5();
SeckillExecution excution = seckillService.excuteSeckillProcedure(seckillId, phone, md5);
logger.info(excution.getStateInfo());
}
}
}
logback.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 預設往控制檯列印 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<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>
另外可以列印sql
<bean id="druid-stat-interceptor"
class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor">
</bean>
<bean id="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"
scope="prototype">
<property name="patterns">
<list>
<value>com.skwx.service.impl.*</value>
</list>
</property>
</bean>
<aop:config>
<aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" />
</aop:config>
需要在pom中新增依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.27</version>
</dependency>