秒杀系统模拟高并发

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层的设计:

扫描二维码关注公众号,回复: 4206545 查看本文章
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 ,自己又操作了一般,明白了一些东西,实例最能让人去理解了。

猜你喜欢

转载自blog.csdn.net/zhaolinxuan1/article/details/84429525