High concurrent seckill Service layer

Keywords: Spring Java xml less

Design of Service layer

Create the required package

  1. Service package: store the interface and implementation class of service

  2. exception package: store some exceptions of service (repeat seckill, close seckill, etc.)

  3. Dto package: it also stores data. The difference between dto and entity is that entity is the encapsulation of business, and dto is the data transfer between web and service

Interface SeckillService

Design the interface from the user's point of view, not the implementation; the more convenient the user is, the better
1. Method definition granularity 2. Parameter 3. Return type

  1. Query all seconds kill records (to have a list page to show all seconds kill)
List<Seckill> getSeckillList();
  1. Query single seckill record
/**
     * Query single seckill record
     * @param seckillId
     * @return
     */
    Seckill getById(long seckillId);
  1. Output the address of seckill interface
    ① Output the interface address of seckill at the beginning of seckill
    ② Otherwise, the system time and seckill time will be output
    In this way, when seckill doesn't start, no one knows our seckill address. Users can't guess our seckill address in advance, but use browser plug-ins in advance to wait for seckill
//Seckill starts to output seckill interface address
    //Otherwise, output system time and seckill time
    Exposer exportSeckillUrl(long seckillId);

The return value here, Exposer, is our own encapsulated dto object, which has nothing to do with business, just for the convenience of data transmission

package org.seckill.dto;

public class Exposer {
    //Whether to turn on seckill
    private boolean exposed;
    //An encrypted address
    private String md5;
    //If the seckill starts, return the seckill address, which is the id described
    private long seckillId;
    //Do not return the address before seckill, return the start time and end time of seckill
    //System current time
    private long now;
    //Start time of seckill
    private long start;
    //End time of seckill
    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;
    }
}


  1. Execute spike
    This interface should be implemented after obtaining the interface address of seckill. md5 encryption and internal rules are used for comparison to prevent users' url from being tampered
/**
     * Execute spike
     * @param seckillId
     * @param userPhone
     * @param md5: md5 Encryption is compared with internal rules to prevent users' URLs from being tampered with
     */
    SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
            throws SeckillException, RepeatKillException, SeckillCloseException ;

The return value SeckillExecution is also our own encapsulated dto object. If the seckill succeeds, the seckill Id, success Id and success object will be returned. If the seckill fails, the seckill object will not be returned

package org.seckill.dto;

import org.seckill.entity.SuccessKilled;

/**
 * Encapsulate second kill results
 */
public class SeckillExecution {
    private long seckillId;
    //Seckill execution result status
    private int state;
    //Identification of status
    private String stateInfo;
    //Second kill successful object
    private SuccessKilled successKilled;

    public SeckillExecution(long seckillId, int state, String stateInfo, SuccessKilled successKilled) {
        this.seckillId = seckillId;
        this.state = state;
        this.stateInfo = stateInfo;
        this.successKilled = successKilled;
    }

    public SeckillExecution(long seckillId, int state, String stateInfo) {
        this.seckillId = seckillId;
        this.state = state;
        this.stateInfo = stateInfo;
    }

    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;
    }
}

There are two exceptions when executing the seckill. Repeat the seckill exception and close the seckill exception
These are run-time exceptions. Even without try catch, there will be no compilation errors, and spring management transactions can only help us roll back the run-time exceptions
When we design, there should be an exception of all seconds killing related businesses as the parent class, and other exceptions should be subdivided by him
① SeckillException of all seckill related businesses

package org.seckill.exception;

/**
 * All exceptions of seckill related businesses
 */
public class SeckillException extends RuntimeException{
    public SeckillException(String message) {
        super(message);
    }

    public SeckillException(String message, Throwable cause) {
        super(message, cause);
    }
}

② Repeat kill exception

package org.seckill.exception;

/**
 * Repeat seckill exception (exception during operation)
 */
public class RepeatKillException extends SeckillException{
    public RepeatKillException(String message) {
        super(message);
    }

    public RepeatKillException(String message, Throwable cause) {
        super(message, cause);
    }
}

③ SeckillCloseException

package org.seckill.exception;

public class SeckillCloseException extends SeckillException {
    public SeckillCloseException(String message) {
        super(message);
    }

    public SeckillCloseException(String message, Throwable cause) {
        super(message, cause);
    }
}

Complete code

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 {

    List<Seckill> getSeckillList();

    /**
     * Query single seckill record
     * @param seckillId
     * @return
     */
    Seckill getById(long seckillId);

    //Seckill starts to output seckill interface address
    //Otherwise, output system time and seckill time
    Exposer exportSeckillUrl(long seckillId);

    /**
     * Execute spike
     * @param seckillId
     * @param userPhone
     * @param md5: md5 Encryption is compared with internal rules to prevent users' URLs from being tampered with
     */
    SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
            throws SeckillException, RepeatKillException, SeckillCloseException ;


}

Implementation of SeckillServiceImpl

The implementation is placed under the impl package under service

  1. The method of returning seckill object is very simple. You can call the method of dao layer between them
@Override
    public List<Seckill> getSeckillList() {
        //Only four seconds to kill
        return seckillDao.queryAll(0,4);
    }

    @Override
    public Seckill getById(long seckillId) {
        return seckillDao.queryById(seckillId);
    }
  1. Implementation of exportSeckillUrl interface
@Override
    public Exposer exportSeckillUrl(long seckillId) {
        Seckill seckill = seckillDao.queryById(seckillId);
        if(seckill == null){//Less than a second kill
            return new Exposer(false,seckillId);
        }
        Date startTime = seckill.getStartTime();//Start time of seckill
        Date endTime = seckill.getEndTime();//End time
        Date nowTime = new Date();//System current time
        //Judge whether it's time to kill
        if(nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()){
            //Be not in
            return new Exposer(false,seckillId,nowTime.getTime(),startTime.getTime(), endTime.getTime());
        }
        //Spike start
        String md5 = getMD5(seckillId);//Encryption, converting specific strings, irreversible
        return new Exposer(true,md5,seckillId);
    }

In order to reuse the executeckill method, a method is extracted to generate MD5
Declaring an MD5 salt value is used to confuse MD5, because if only skillId is encrypted, it is easy to guess. It is safer to generate MD5 by using specific splicing of salt value and skillId

private final String slat="sdaqei012ee[qsdaq231w";
   private String getMD5(long seckillId){
        //md5 salt value is used to confuse md5, because if only skillId is encrypted, it is easy to get out
        //Specific splicing using salt value and skillId is safer
        String base = seckillId+"/"+slat;
        //Using spring's tool class to generate md5
        String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
        return md5;
    }
  1. Implementation of executeSeckill interface
    This is the execution of seckill logic
    ① The first step is to judge the correctness of md5
if(md5 == null || !md5.equals(getMD5(seckillId))){
	throw new SeckillException("seckill data rewrite");
}

② The next step is to execute the seckill logic: reduce inventory + record purchase record
Go to update seckill first. If the record is not updated (the product is not available or is not in the seckill time), the seckill will end and an exception will be thrown

int updateCount = seckillDao.reduceNumber(seckillId,nowTime);
if(updateCount <= 0){//If the record is not updated, the seckill will end
	throw new SeckillCloseException("seckill is closed");
}
           

③ Update the purchase record after seckill succeeds

int insertCount = successKilledDao.insertSuccessKilled(seckillId,userPhone);
                //userPhone and seckillId as joint primary keys to avoid repeated second killing
if(insertCount<=0){
	throw new RepeatKillException("seckill repeated");
}else {//Spike kill
	SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
	return new SeckillExecution(seckillId,1,"Spike kill",successKilled);
}

④ For inventory reduction and purchase record recording, a transaction should be formed, either successful or failed

 @Override
    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");
        }
        //Execute seckill logic: reduce inventory + record purchase record
        Date nowTime = new Date();
        try {
            //Reduce stock
            int updateCount = seckillDao.reduceNumber(seckillId,nowTime);
            if(updateCount <= 0){//If the record is not updated, the seckill will end
                throw new SeckillCloseException("seckill is closed");
            }else {//Record purchase records
                int insertCount = successKilledDao.insertSuccessKilled(seckillId,userPhone);
                //userPhone and seckillId as joint primary keys to avoid repeated second killing
                if(insertCount<=0){
                    throw new RepeatKillException("seckill repeated");
                }else {//Spike kill
                    SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
                    return new SeckillExecution(seckillId,1,"Spike kill",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());
        }
    }

⑤ The logic implementation is completed, but it is not friendly enough. In other words, the new SeckillExecution(seckillId,1, "seckill successfully", successKilled); the 1 and "seckill successfully" in it should be constants. We use enumeration to record, which is easier to organize
The enumeration classes are as follows

package org.seckill.enums;

public enum SeckillStaEnum {
    SUCCESS(1,"Spike kill"),
    END(0,"End of spike"),
    REPEAT_KILL(-1,"Repeat spike"),
    INNER_ERROR(-2,"System exception"),
    DATA_REWRITE(-3,"Data tampering");

    private int state;
    private String stateInf;

    SeckillStaEnum(int state, String stateInf) {
        this.state = state;
        this.stateInf = stateInf;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public String getStateInf() {
        return stateInf;
    }

    public void setStateInf(String stateInf) {
        this.stateInf = stateInf;
    }

    public static SeckillStaEnum stateOf(int index){
        for (SeckillStaEnum state : values()) {
            if(state.getState() == index){
                return state;
            }
        }
        return null;
    }
}

Change the construction method of SeckillExecution

 public SeckillExecution(long seckillId, SeckillStaEnum seckillStaEnum, SuccessKilled successKilled) {
        this.seckillId = seckillId;
        this.state = seckillStaEnum.getState();
        this.stateInfo = seckillStaEnum.getStateInf();
        this.successKilled = successKilled;
    }

    public SeckillExecution(long seckillId, SeckillStaEnum seckillStaEnum) {
        this.seckillId = seckillId;
        this.state = seckillStaEnum.getState();
        this.stateInfo = seckillStaEnum.getStateInf();
    }

The complete code after the change is as follows

@Override
    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");
        }
        //Execute seckill logic: reduce inventory + record purchase record
        Date nowTime = new Date();
        try {
            //Reduce stock
            int updateCount = seckillDao.reduceNumber(seckillId,nowTime);
            if(updateCount <= 0){//If the record is not updated, the seckill will end
                throw new SeckillCloseException("seckill is closed");
            }else {//Record purchase records
                int insertCount = successKilledDao.insertSuccessKilled(seckillId,userPhone);
                //userPhone and seckillId as joint primary keys to avoid repeated second killing
                if(insertCount<=0){
                    throw new RepeatKillException("seckill repeated");
                }else {//Spike kill
                    SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
                    return new SeckillExecution(seckillId, SeckillStaEnum.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());
        }
    }

Using Spring to host Service dependencies



We can access any instance in the factory through a consistent access interface

Configure 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"
       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">
    <!--scanning service All types of annotations used under the package-->
    <context:component-scan base-package="org.seckill.service"></context:component-scan>
</beans>

Spring declarative transaction


We don't have to worry about when to start a transaction, when to end a transaction, and when to roll back. Instead, we leave it to spring management

Nesting of transaction methods

When a new transaction is added, if there is a previous transaction, the original transaction logic will be added directly. If there is no previous transaction, a new transaction will be created

When to roll back
Rollback occurs when a runtime exception is thrown

To configure

<?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">
    <!--scanning service All types of annotations used under the package-->
    <context:component-scan base-package="org.seckill.service"></context:component-scan>

    <!--Configure transaction manager-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource">
        </property>
    </bean>

    <!--Configure annotation based declaration transactions-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

Final service implementation class

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.SeckillStaEnum;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import javax.xml.crypto.Data;
import java.util.Date;
import java.util.List;

@Service
public class SeckillServiceImpl implements SeckillService {
    //Log object
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    //The implementation class is in the container and injected into the dao layer
    @Autowired
    private SeckillDao seckillDao;
    @Autowired
    private SuccessKilledDao successKilledDao;

    //md5 salt value is used to confuse md5, because if only skillId is encrypted, it is easy to get out
    //Specific splicing using salt value and skillId is safer
    private final String slat="sdaqei012ee[qsdaq231w";

    @Override
    public List<Seckill> getSeckillList() {
        //Only four seconds to kill
        return seckillDao.queryAll(0,4);
    }

    @Override
    public Seckill getById(long seckillId) {
        return seckillDao.queryById(seckillId);
    }

    @Override
    public Exposer exportSeckillUrl(long seckillId) {
        Seckill seckill = seckillDao.queryById(seckillId);
        if(seckill == null){//Less than a second kill
            return new Exposer(false,seckillId);
        }
        Date startTime = seckill.getStartTime();//Start time of seckill
        Date endTime = seckill.getEndTime();//End time
        Date nowTime = new Date();//System current time
        //Judge whether it's time to kill
        if(nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()){
            //Be not in
            return new Exposer(false,seckillId,nowTime.getTime(),startTime.getTime(), endTime.getTime());
        }
        //Spike start
        String md5 = getMD5(seckillId);//Encryption, converting specific strings, irreversible
        return new Exposer(true,md5,seckillId);
    }

    @Override
    @Transactional
    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");
        }
        //Execute seckill logic: reduce inventory + record purchase record
        Date nowTime = new Date();
        try {
            //Reduce stock
            int updateCount = seckillDao.reduceNumber(seckillId,nowTime);
            if(updateCount <= 0){//If the record is not updated, the seckill will end
                throw new SeckillCloseException("seckill is closed");
            }else {//Record purchase records
                int insertCount = successKilledDao.insertSuccessKilled(seckillId,userPhone);
                //userPhone and seckillId as joint primary keys to avoid repeated second killing
                if(insertCount<=0){
                    throw new RepeatKillException("seckill repeated");
                }else {//Spike kill
                    SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
                    return new SeckillExecution(seckillId, SeckillStaEnum.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());
        }
    }

    private String getMD5(long seckillId){
        //md5 salt value is used to confuse md5, because if only skillId is encrypted, it is easy to get out
        //Specific splicing using salt value and skillId is safer
        String base = seckillId+"/"+slat;
        //Using spring's tool class to generate md5
        String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
        return md5;
    }
}

Published 26 original articles, won praise 0, visited 280
Private letter follow

Posted by dalecosp on Tue, 14 Jan 2020 21:09:40 -0800