シナリオについて説明する前に、トランザクションの合法性を確認した後、在庫を差し引き、mqを使用してデータベースを変更し、注文を処理しました。しかし、後で注文を処理するときに問題がある場合、mqによって送信されたメッセージを取り消すことができず、データが変更されているという問題があります。この状況に基づいて、簡単な処理メソッドを用意しました。Springbootトランザクションを使用しているため、トランザクションの送信後に実行されるTransactionSynchronizationManagerインターフェースを提供しています。
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
//异步更新库存
boolean mqResult = itemService.asyncDecreaseStock(itemId , amount);
}
});
もちろん、自分で確認できる書き換え方法はたくさんあります。
注文完了後、在庫を差し引く操作を行いますが、よろしいですか?mqに問題がある場合はどうなりますか?これは、mqを使用するために必要なトランザクションメッセージです。
プロデューサーコードを変更します。
@Component
public class MqProducer {
private TransactionMQProducer transactionMQProducer;
@Value("${mq.nameserver.addr}")
private String nameAddr;
@Value("${mq.topicname}")
private String topicName;
@Autowired
private OrderService orderService;
@PostConstruct
public void init() throws MQClientException {
transactionMQProducer = new TransactionMQProducer("transaction_producer_group");
transactionMQProducer.setNamesrvAddr(nameAddr);
transactionMQProducer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object args) {
//真正要做的事 创建订单
Integer itemId = (Integer) ((Map)args).get("itemId");
Integer promoId = (Integer) ((Map)args).get("promoId");
Integer userId = (Integer) ((Map)args).get("userId");
Integer amount = (Integer) ((Map)args).get("amount");
try {
orderService.createOrder(userId , itemId , promoId , amount);
} catch (BusinessException e) {
e.printStackTrace();
return LocalTransactionState.ROLLBACK_MESSAGE;
}
return LocalTransactionState.COMMIT_MESSAGE;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
return null;
}
});
transactionMQProducer.start();
}
//事务型同步库存扣减消息
public boolean transactionAsyncReduceStock(Integer userId , Integer promoId , Integer itemId , Integer amount){
Map<String , Object> bodyMap = new HashMap<>();
bodyMap.put("itemId" , itemId);
bodyMap.put("amount" , amount);
Map<String , Object> argsMap = new HashMap<>();
argsMap.put("itemId" , itemId);
argsMap.put("amount" , amount);
argsMap.put("userId" , userId);
argsMap.put("promoId" , promoId);
Message message = new Message(topicName , "increas" ,
JSON.toJSON(bodyMap).toString().getBytes(Charset.forName("UTF-8")));
TransactionSendResult transactionSendResult = null;
try {
transactionSendResult = transactionMQProducer.sendMessageInTransaction(message , argsMap);
} catch (MQClientException e) {
e.printStackTrace();
return false;
}
if (transactionSendResult.getLocalTransactionState() == LocalTransactionState.COMMIT_MESSAGE){
return true;
}else{
return false;
}
}
}
コントローラー層は、注文やその他の操作を生成するこのtransactionAsyncReduceStockを直接調整します。
ここで説明する必要がある点がいくつかあります。前のproducer.send操作の場合、このメッセージは3つ、71つに関係なく送信され、送信後にコンシューマに通知できます。トランザクションメッセージの場合、2フェーズコミットの概念があります。transactionMQProducer.sendMessageInTransactionの後、ブローカーはメッセージを受信しますが、その状態は、消費できる状態ではありませんが、準備され、操作は実行されませんこの状態で、彼はexecuteLocalTransactionメソッドをローカルで実行し、送信されたメッセージが実行されるかロールバックされるかを決定するためにCommitまたはrollbackを返します。不明のステータスもあります。たとえば、注文の作成に10秒以上かかる場合、mqを返して返さないでください。このステータスは、ブローカーが定期的にcheckLocalTransactionメソッドを実行して結果をクエリすることを意味します。 unknowを使用して、mqだけでステータスを照会できるようにします。したがって、パイプラインデータが必要です。
次のコードを改善します。
@Component
public class MqProducer {
private TransactionMQProducer transactionMQProducer;
@Value("${mq.nameserver.addr}")
private String nameAddr;
@Value("${mq.topicname}")
private String topicName;
@Autowired
private OrderService orderService;
@Autowired
private StockLogDOMapper stockLogDOMapper;
@PostConstruct
public void init() throws MQClientException {
transactionMQProducer = new TransactionMQProducer("transaction_producer_group");
transactionMQProducer.setNamesrvAddr(nameAddr);
transactionMQProducer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object args) {
//真正要做的事 创建订单
Integer itemId = (Integer) ((Map)args).get("itemId");
Integer promoId = (Integer) ((Map)args).get("promoId");
Integer userId = (Integer) ((Map)args).get("userId");
Integer amount = (Integer) ((Map)args).get("amount");
String stockLogId = (String) ((Map)args).get("stockLogId");
try {
orderService.createOrder(userId , itemId , promoId , amount , stockLogId);
} catch (BusinessException e) {
e.printStackTrace();
StockLogDO stockLogDO = stockLogDOMapper.selectByPrimaryKey(stockLogId);
stockLogDO.setStatus(3);
stockLogDOMapper.updateByPrimaryKeySelective(stockLogDO);
return LocalTransactionState.ROLLBACK_MESSAGE;
}
return LocalTransactionState.COMMIT_MESSAGE;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
//根据是否扣减库存成功来判断要返回commit还是rollback还是unknown
String jsonString = new String(msg.getBody());
Map<String , Object> map = JSON.parseObject(jsonString , Map.class);
String stockLogId = (String) map.get("stockLogId");
StockLogDO stockLogDO = stockLogDOMapper.selectByPrimaryKey(stockLogId);
if (stockLogDO == null){
return LocalTransactionState.UNKNOW;
}
if (stockLogDO.getStatus() == 2){
return LocalTransactionState.COMMIT_MESSAGE;
}else if (stockLogDO.getStatus() == 1){
return LocalTransactionState.UNKNOW;
}else {
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
});
transactionMQProducer.start();
}
//事务型同步库存扣减消息
public boolean transactionAsyncReduceStock(Integer userId , Integer promoId , Integer itemId , Integer amount , String stockLogId){
Map<String , Object> bodyMap = new HashMap<>();
bodyMap.put("itemId" , itemId);
bodyMap.put("amount" , amount);
bodyMap.put("stockLogId" , stockLogId);
Map<String , Object> argsMap = new HashMap<>();
argsMap.put("itemId" , itemId);
argsMap.put("amount" , amount);
argsMap.put("userId" , userId);
argsMap.put("promoId" , promoId);
argsMap.put("stockLogId" , stockLogId);
Message message = new Message(topicName , "increas" ,
JSON.toJSON(bodyMap).toString().getBytes(Charset.forName("UTF-8")));
TransactionSendResult transactionSendResult = null;
try {
transactionSendResult = transactionMQProducer.sendMessageInTransaction(message , argsMap);
} catch (MQClientException e) {
e.printStackTrace();
return false;
}
if (transactionSendResult.getLocalTransactionState() == LocalTransactionState.COMMIT_MESSAGE){
return true;
}else{
return false;
}
}
}
パイプラインをクエリするプロセスをコントローラーレイヤーに追加し、リクエストの前にパイプラインを初期化し、ステータスを初期化し、トランザクションの完了後にパイプラインのステータスを完了に変更し、エラーが発生するとパイプラインのステータスをエラーに変更するので、mq自分でクエリを実行する場合、ステータスに応じて、送信するか、ロールバックするか、待機し続けるかを決定できます。
もちろん、この場合、データベースの最終的な整合性を保証しますが、redisに問題がある場合は、redisにデータを重ね合わせますか、それとも破棄しますか?これはビジネスシナリオに関連しています。次に、たとえば、注文から15分以内に支払いを完了する必要があります。そうしないと無効になります。これはパイプライン時間の計算にも関係しているので、ここでは詳しく説明しません。
スパイクのシナリオもあり、製品が100個しかなく、10,000人がそれを取得する場合、水道水の生成を制御する必要があります。完売の場合もございますが、完売と判断した場合は直接コントローラーに返却いたします。次に、redisを借りて、トランザクションが在庫を削減するたびに0かどうかを判断し、0の場合は完売してredisに入れます。