秒殺系統模擬高併發
阿新 • • 發佈:2018-11-24
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 ,自己又操作了一般,明白了一些東西,例項最能讓人去理解了。