9.トランザクションパフォーマンスの最適化-トランザクションメッセージ

シナリオについて説明する前に、トランザクションの合法性を確認した後、在庫を差し引き、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に入れます。

97件の元の記事を公開 28 件を獲得・1 万回以上の閲覧

おすすめ

転載: blog.csdn.net/haozi_rou/article/details/105429774