1. 程式人生 > >秒殺系統模擬高併發

秒殺系統模擬高併發

1.初始方案

(1)表設計

商品表設計:熱銷商品提供給使用者秒殺,有初始庫存。

import java.io.Serializable;

/**
 * t_seckillgoods
 * @author 
 */

public class TSeckillgoods implements Serializable {
    /**
     * id
     */
    private Integer id;

    /**
     * 商品庫存
     */
    private Integer remainnum;

    /**
     * 商品名
     */
    private String goodsname;

    private static final long serialVersionUID = 1L;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getRemainnum() {
        return remainnum;
    }

    public void setRemainnum(Integer remainnum) {
        this.remainnum = remainnum;
    }

    public String getGoodsname() {
        return goodsname;
    }

    public void setGoodsname(String goodsname) {
        this.goodsname = goodsname;
    }
}

 

秒殺訂單表設計:記錄秒殺成功的訂單情況:

 



import java.io.Serializable;

/**
 * t_seckillOrder
 * @author 
 */
public class TSeckillorder implements Serializable {
    private Integer id;

    private String consumer;

    private Integer goodsid;

    private Integer num;

    private static final long serialVersionUID = 1L;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getConsumer() {
        return consumer;
    }

    public void setConsumer(String consumer) {
        this.consumer = consumer;
    }

    public Integer getGoodsid() {
        return goodsid;
    }

    public void setGoodsid(Integer goodsid) {
        this.goodsid = goodsid;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }
}

(2)Dao設計: 

商品dao,庫存減去購買數量

public interface TSeckillgoodsDao {
    

   @Update("UPDATE  t_seckillgoods g set g.remainnum=g.remainnum - #{num} where g.id=1")
    @Transactional
    int reduceStock(Integer num);
}

 訂單dao,增加訂單資訊,原始mapper.


import com.javasvip.model.vo.TSeckillorder;

public interface TSeckillorderDao {
    int deleteByPrimaryKey(Integer id);

    int insert(TSeckillorder record);

    int insertSelective(TSeckillorder record);

    TSeckillorder selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(TSeckillorder record);

    int updateByPrimaryKey(TSeckillorder record);
}

(3)Controller層的設計:

import com.javasvip.dao.TSeckillgoodsDao;
import com.javasvip.model.vo.TSeckillgoods;
import com.javasvip.service.impl.SecKillServerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SecKillController {

    @Autowired
    private TSeckillgoodsDao tSeckillgoodsDao;

    @Autowired
    private SecKillServerImpl secKillServer;

    @RequestMapping("/seckill.html")
    @ResponseBody
    public String seckill(String consumer,int goodsId,Integer num) throws Exception{
        TSeckillgoods tSeckillgoods=tSeckillgoodsDao.selectByPrimaryKey(goodsId);
        if (tSeckillgoods.getRemainnum()>num){
            Thread.sleep(1000);
            //減去庫存
            tSeckillgoodsDao.reduceStock(num);
            //儲存訂單
            secKillServer.saveOrder(consumer,goodsId,num);
            return "購買成功";

        }
        return "購買失敗,庫存不足";
    }
}

(4)server設計:

import com.javasvip.dao.TSeckillgoodsDao;
import com.javasvip.dao.TSeckillorderDao;
import com.javasvip.model.vo.TSeckillorder;
import com.javasvip.service.ISecKillServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SecKillServerImpl implements ISecKillServer {
    @Autowired
    private TSeckillorderDao tSeckillorderDao;
    @Autowired
    private TSeckillgoodsDao tSeckillgoodsDao;
    @Override
    public void saveOrder(String consumer, int goodsId, Integer num) {
        TSeckillorder tSeckillorder=new TSeckillorder();
        tSeckillorder.setConsumer(consumer);
        tSeckillorder.setGoodsid(goodsId);
        tSeckillorder.setNum(num);
        tSeckillorderDao.insert(tSeckillorder);
    }
}

(5)測試方法,模擬高併發下,很多人來購買同一個熱門商品的情況:(url注意路徑和埠號)




import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.List;
@Controller
public class TestController {
    private static final Logger logger = LoggerFactory.getLogger(TestController.class);
 
    final String url="http://127.0.0.1/seckill.html";
    @RequestMapping("/sumibtmitOrder")
    @ResponseBody
    public String sumibtmitOrder(){
        final SimpleClientHttpRequestFactory httpRequestFactory=new SimpleClientHttpRequestFactory();
        for(int i=0;i<50;i++){
            final String consumerName="consumer"+i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    ClientHttpRequest request=null;
                    try{
                        URI uri=new URI(url+"?consumer=consumer"+consumerName+"&goodsId=1&num=1");
                        request=httpRequestFactory.createRequest(uri,HttpMethod.POST);
                        InputStream body=request.execute().getBody();
                        BufferedReader br=new BufferedReader(new InputStreamReader(body));
                        String line="";
                        String result="";
                        while((line=br.readLine())!=null){
                            result+=line;
                        }
                        System.out.println(consumerName+":"+result);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        return "sumibtmitOrder";
    }
}

 

訪問localhost:port/sumibtmitOrder,就可以測試了

預期情況:因為我們只對秒殺商品(123456)初始化了10件,理想情況當然是庫存減少到0,訂單表也只有10條記錄。

(6)測試結果:

商品表:

訂單表: 

 

(7)原因分析:

因為多個請求訪問,僅僅是使用dao查詢了一次資料庫有沒有庫存,但是比較惡劣的情況是很多人都查到了有庫存,這個時候因為程式處理的延遲,沒有及時的減少庫存,那就出現了髒讀。如何在設計上避免呢?最笨的方法是對SecKillController的seckill方法做同步,每次只有一個人能下單。但是太影響效能了,下單變成了同步操作。 

 

來源:www.cnkirito.moe ,自己又操作了一般,明白了一些東西,例項最能讓人去理解了。