秒殺系統(tǒng)設(shè)計

功能需求:設(shè)計一個秒殺系統(tǒng)

初始方案

商品表設(shè)計:熱銷商品提供給用戶秒殺,有初始庫存。

@Entity

public class SecKillGoods implements Serializable{

? ? @Id

? ? private String id;

? ? /**

? ? * 剩余庫存

? ? */

? ? private Integer remainNum;

? ? /**

? ? * 秒殺商品名稱

? ? */

? ? private String goodsName;

}

秒殺訂單表設(shè)計:記錄秒殺成功的訂單情況

@Entity

public class SecKillOrder implements Serializable {

? ? @Id

? ? @GenericGenerator(name = "PKUUID", strategy = "uuid2")

? ? @GeneratedValue(generator = "PKUUID")

? ? @Column(length = 36)

? ? private String id;

? ? //用戶名稱

? ? private String consumer;

? ? //秒殺產(chǎn)品編號

? ? private String goodsId;

? ? //購買數(shù)量

? ? private Integer num;

}

Dao設(shè)計:主要就是一個減少庫存方法,其他CRUD使用JPA自帶的方法

public interface SecKillGoodsDao extends JpaRepository<SecKillGoods,String>{

? ? @Query("update SecKillGoods g set g.remainNum = g.remainNum - ?2 where g.id=?1")

? ? @Modifying(clearAutomatically = true)

? ? @Transactional

? ? int reduceStock(String id,Integer remainNum);

}

數(shù)據(jù)初始化以及提供保存訂單的操作:

@Service

public class SecKillService {

? ? @Autowired

? ? SecKillGoodsDao secKillGoodsDao;

? ? @Autowired

? ? SecKillOrderDao secKillOrderDao;

? ? /**

? ? * 程序啟動時:

? ? * 初始化秒殺商品,清空訂單數(shù)據(jù)

? ? */

? ? @PostConstruct

? ? public void initSecKillEntity(){

? ? ? ? secKillGoodsDao.deleteAll();

? ? ? ? secKillOrderDao.deleteAll();

? ? ? ? SecKillGoods secKillGoods = new SecKillGoods();

? ? ? ? secKillGoods.setId("123456");

? ? ? ? secKillGoods.setGoodsName("秒殺產(chǎn)品");

? ? ? ? secKillGoods.setRemainNum(10);

? ? ? ? secKillGoodsDao.save(secKillGoods);

? ? }

? ? /**

? ? * 購買成功,保存訂單

? ? * @param consumer

? ? * @param goodsId

? ? * @param num

? ? */

? ? public void generateOrder(String consumer, String goodsId, Integer num) {

? ? ? ? secKillOrderDao.save(new SecKillOrder(consumer,goodsId,num));

? ? }

}

下面就是controller層的設(shè)計

@Controller

public class SecKillController {

? ? @Autowired

? ? SecKillGoodsDao secKillGoodsDao;

? ? @Autowired

? ? SecKillService secKillService;

? ? /**

? ? * 普通寫法

? ? * @param consumer

? ? * @param goodsId

? ? * @return

? ? */

? ? @RequestMapping("/seckill.html")

? ? @ResponseBody

? ? public String SecKill(String consumer,String goodsId,Integer num) throws InterruptedException {

? ? ? ? //查找出用戶要買的商品

? ? ? ? SecKillGoods goods = secKillGoodsDao.findOne(goodsId);

? ? ? ? //如果有這么多庫存

? ? ? ? if(goods.getRemainNum()>=num){

? ? ? ? ? ? //模擬網(wǎng)絡(luò)延時

? ? ? ? ? ? Thread.sleep(1000);

? ? ? ? ? ? //先減去庫存

? ? ? ? ? ? secKillGoodsDao.reduceStock(num);

? ? ? ? ? ? //保存訂單

? ? ? ? ? ? secKillService.generateOrder(consumer,goodsId,num);

? ? ? ? ? ? return "購買成功";

? ? ? ? }

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

? ? }

}

上面是全部的基礎(chǔ)準備,下面使用一個單元測試方法,模擬高并發(fā)下,很多人來購買同一個熱門商品的情況。

@Controller

public class SecKillSimulationOpController {

? ? final String takeOrderUrl = "http://127.0.0.1:8080/seckill.html";

? ? /**

? ? * 模擬并發(fā)下單

? ? */

? ? @RequestMapping("/simulationCocurrentTakeOrder")

? ? @ResponseBody

? ? public String simulationCocurrentTakeOrder() {

? ? ? ? //httpClient工廠

? ? ? ? final SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();

? ? ? ? //開50個線程模擬并發(fā)秒殺下單

? ? ? ? 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(takeOrderUrl + "?consumer=consumer" + consumerName + "&goodsId=123456&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;//獲得頁面內(nèi)容或返回內(nèi)容

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? System.out.println(consumerName+":"+result);

? ? ? ? ? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }).start();

? ? ? ? }

? ? ? ? return "simulationCocurrentTakeOrder";

? ? }

}

訪問localhost:8080/simulationCocurrentTakeOrder,就可以測試了

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

實際情況:訂單表記錄


商品表記錄


下面分析一下為啥會出現(xiàn)超庫存的情況:

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

@RequestMapping("/seckill.html")

@ResponseBody

public synchronized String SecKill

改進方案

根據(jù)多線程編程的規(guī)范,提倡對共享資源加鎖,在最有可能出現(xiàn)并發(fā)爭搶的情況下加同步塊的思想。應(yīng)該同一時刻只有一個線程去減少庫存。但是這里給出一個最好的方案,就是利用Oracle,MySQL的行級鎖–同一時間只有一個線程能夠操作同一行記錄,對SecKillGoodsDao進行改造:

public interface SecKillGoodsDao extends JpaRepository<SecKillGoods,String>{

? ? @Query("update SecKillGoods g set g.remainNum = g.remainNum - ?2 where g.id=?1 and g.remainNum>0")

? ? @Modifying(clearAutomatically = true)

? ? @Transactional

? ? int reduceStock(String id,Integer remainNum);

}

僅僅是加了一個and,卻造成了很大的改變,返回int值代表的是影響的行數(shù),對應(yīng)到controller做出相應(yīng)的判斷。

@RequestMapping("/seckill.html")

? ? @ResponseBody

? ? public String SecKill(String consumer,String goodsId,Integer num) throws InterruptedException {

? ? ? ? //查找出用戶要買的商品

? ? ? ? SecKillGoods goods = secKillGoodsDao.findOne(goodsId);

? ? ? ? //如果有這么多庫存

? ? ? ? if(goods.getRemainNum()>=num){

? ? ? ? ? ? //模擬網(wǎng)絡(luò)延時

? ? ? ? ? ? Thread.sleep(1000);

? ? ? ? ? ? if(goods.getRemainNum()>0) {

? ? ? ? ? ? ? ? //先減去庫存

? ? ? ? ? ? ? ? int i = secKillGoodsDao.reduceStock(goodsId, num);

? ? ? ? ? ? ? ? if(i!=0) {

? ? ? ? ? ? ? ? ? ? //保存訂單

? ? ? ? ? ? ? ? ? ? secKillService.generateOrder(consumer, goodsId, num);

? ? ? ? ? ? ? ? ? ? return "購買成功";

? ? ? ? ? ? ? ? }else{

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

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }else {

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

? ? ? ? ? ? }

? ? ? ? }

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

? ? }

在看看運行情況


訂單表:


在高并發(fā)問題下的秒殺情況,即使存在網(wǎng)絡(luò)延時,也得到了保障。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容