說罷秒殺網關相關的核心要點,咱們接着聊聊秒殺收單相關的核心要點與代碼實現。數據庫
本文重點說明如下幾點:緩存
首先對業務場景進行概述。bash
完整的業務流可參考實戰分佈式之電商高併發秒殺場景總覽併發
秒殺收單核心業務邏輯以下:app
接着講解下如何經過消息隊列進行異步收單。異步
關於如何對消息進行封裝,能夠參考 實戰分佈式之電商高併發秒殺網關核心要點及代碼實現 , 這裏再也不贅述。分佈式
咱們須要定義一個進行下單操做的消費者客戶端,並對秒殺收單消息進行訂閱。ide
@PostConstruct
public void init() {
defaultMQPushConsumer = new DefaultMQPushConsumer(MessageProtocolConst.SECKILL_CHARGE_ORDER_TOPIC.getConsumerGroup());
defaultMQPushConsumer.setNamesrvAddr(nameSrvAddr);
// 從頭開始消費
defaultMQPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 消費模式:集羣模式
defaultMQPushConsumer.setMessageModel(MessageModel.CLUSTERING);
// 註冊監聽器
defaultMQPushConsumer.registerMessageListener(messageListener);
// 訂閱全部消息
try {
defaultMQPushConsumer.subscribe(MessageProtocolConst.SECKILL_CHARGE_ORDER_TOPIC.getTopic(), "*");
// 啓動消費者
defaultMQPushConsumer.start();
} catch (MQClientException e) {
LOGGER.error("[秒殺下單消費者]--SecKillChargeOrderConsumer加載異常!e={}", LogExceptionWapper.getStackTrace(e));
throw new RuntimeException("[秒殺下單消費者]--SecKillChargeOrderConsumer加載異常!", e);
}
LOGGER.info("[秒殺下單消費者]--SecKillChargeOrderConsumer加載完成!");
}複製代碼
接着須要實現秒殺收單核心的邏輯,也就是實現咱們本身的MessageListenerConcurrently。高併發
@Component
public class SecKillChargeOrderListenerImpl implements MessageListenerConcurrently {
/**
* 秒殺核心消費邏輯
* @param msgs
* @param context
* @return
*/
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {複製代碼
定義一個類實現接口MessageListenerConcurrently,經過@Component標註爲一個SpringBean。ui
這就是秒殺客戶端的主要骨架代碼。
咱們雖然在秒殺網關已經對庫存進行了校驗,但那是不可靠的。
緣由在於秒殺網關的庫存是在啓動時預熱的,後續扣減是基於緩存進行的。核心目的在於減小對數據庫的壓力。
當下單消費者進行真實下單的時候就須要對庫存進行校驗,此時數據庫的壓力已經很小了,由於在網關的前置校驗已經對流量進行了大幅度的削弱。
咱們看一下真實庫存校驗邏輯
// 庫存校驗
String prodId = chargeOrderMsgProtocol.getProdId();
SecKillProductDobj productDobj = secKillProductService.querySecKillProductByProdId(prodId);
// 取庫存校驗
int currentProdStock = productDobj.getProdStock();
if (currentProdStock <= 0) {
LOGGER.info("[decreaseProdStock]當前商品已售罄,消息消費成功!prodId={},currStock={}", prodId, currentProdStock);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}複製代碼
此處先對庫存進行了查詢並校驗是否已經小於等於0,若是等於0說明已經售罄,則再也不進行消費。
實際下單操做與實際庫存扣減處於同一個本地事務中,核心代碼邏輯以下:
// 減庫存
if (!secKillProductService.decreaseProdStock(prodId)) {
LOGGER.info("[insertSecKillOrder]orderId={},prodId={},下單前減庫存失敗,下單失敗!", orderId, prodId);
// TODO 此處可給用戶發送通知,告知秒殺下單失敗,緣由:商品已售罄
return false;
}
// 設置產品名稱
SecKillProductDobj productInfo = secKillProductService.querySecKillProductByProdId(prodId);
orderInfoDO.setProdName(productInfo.getProdName());
try {
insertCount = secKillOrderMapper.insertSecKillOrder(orderInfoDO);
} catch (Exception e) {
LOGGER.error("[insertSecKillOrder]orderId={},秒殺訂單入庫[異常],事務回滾,e={}", orderId, LogExceptionWapper.getStackTrace(e));
String message =
String.format("[insertSecKillOrder]orderId=%s,秒殺訂單入庫[異常],事務回滾", orderId);
throw new RuntimeException(message);
}
if (insertCount != 1) {
LOGGER.error("[insertSecKillOrder]orderId={},秒殺訂單入庫[失敗],事務回滾,e={}", orderId);
String message =
String.format("[insertSecKillOrder]orderId=%s,秒殺訂單入庫[失敗],事務回滾", orderId);
throw new RuntimeException(message);
}
return true;複製代碼
咱們首先進行減庫存操做,若是減庫存失敗,則事務回滾。
若是扣減成功則進行下單操做,下單操做失敗則事務回滾,同時對庫存進行回滾。此處經過Spring的聲明式事務對事務進行處理。使用默認事務傳播級別 Propagation.REQUIRED 便可。
本文主要對高併發秒殺場景下的收單部分的重點邏輯進行了講解,對庫存真實扣減、真實下單部分的邏輯和注意點以代碼的形式作了較爲直觀的展示。
到此,實戰分佈式之電商高併發秒殺場景的系列就暫時告一段落。
實際上,在收單以後還有支付、物流相關的操做,它們都可以經過使用RocketMQ之類的消息引擎進行異步化處理。思路和秒殺下單主邏輯很類似,礙於篇幅就暫時不作講解了,感興趣的同窗能夠參考以前的思路本身實現,聰明的你必定可以觸類旁通。更多的實戰相關的總結與分享