RocketMQトランザクションメッセージやプロセスを学ぶピットを掘ります

一、背景

システムアーキテクチャにおけるMQコンポーネントは武器のために不可欠である、あなたは、システム設計レベルのカップリング、並行性の高いシナリオを減らすことができ、現在のマイクロに、クラスタの展開に単一のアプリケーションから、負荷シフトとして機能することができサービスアーキテクチャ、MQは、その優れた性能と高い信頼性と、広く認識されています。
データ、システム圧力が上昇するの増加量と、この現象が現れ始めた:データベースが更新されましたが、メッセージはに送信されたり、メッセージを開始するが、その後、データベースの更新に失敗していない、さまざまなデータ復旧の子供用の靴の研究開発の結果、どの小さな、しかし非常に落ち込んで人々の確率種子生産の問題。実際には、これはデータベーストランザクションとMQメッセージの一貫性があり、単純に、メッセージMQデータベーストランザクションを送信するために平均的なトランザクションデータベースと直接、このようなこれらの問題の両方上記のシナリオとして、バンドルに接続することはできません。

  1. データベーストランザクションがコミットMQメッセージを送信します。
  2. MQメッセージングは​​、データベーストランザクションをコミットする前に開始します。

問題のシナリオ1は、MQメッセージは、シーン2 MQメッセージが送信される問題を経由しませんでした、サーバーがダウンして、データベースのトランザクションはちょうど提出かもしれないですが、データベースのトランザクションは、追加のMQメッセージが送信されていない任意の方法を失敗したコミット、データが得られること下流メッセージが受信された、更新されず、最終的なトランザクションが矛盾現れます。

第二に、トランザクションメッセージが描かれています

当社のマイクロサービスアーキテクチャのショッピングシナリオの例として、どのような公式RocketMQの例を参照して、ユーザAが注文を開始し、操作が完了した後、100ドルを支払う、100ポイント、アカウントサービスおよび会員サービスを得ることができ、二つの別々のマイクロサービスモジュールですこのような状況があるだろう、上記の問題の可能性に応じて、自分のデータベースを持っています:

  • 最初の充電、そのメッセージは、お金だけで終わりバックル可能性がある場合は、ダウンして、メッセージが統合を増加させなかった結果を経由しませんでした。
  • 最初のメッセージ、次に担当した場合、統合が増えるかもしれませんが、お金が差し引かれていなかった、人に100ポイントを与えます。
  • 通常のお金は、メッセージが正常に送信され、控除が、メンバーシップサービスインスタンス消費者情報の問題は、統合の結果は増加しませんでした。
    ショッピングシーンMQ通信ケース

メッセージ送信を行うローカルトランザクションアトミック性の問題を解決する:これは、トランザクションメッセージrocketmqの問題が解決データベーストランザクションMQメッセージのトランザクションの整合性を上昇させます。ここでは境界が理解する必要があり、それは、これ以上の毛、ない漏れを正しく生産側MQを確保するメッセージを送信することです。しかし、送信側が消費ない通常消費(通常お金が差し引か例えば、上記第三の場合、メッセージは、行われていないが、統合問題下流消費を引き起こさない)を有しているとして、この異常シナリオMQメッセージによって範囲内で、この議論には含まれていないことを確認するために、消費者失敗時の再試行メカニズム、。

このシーンは、右のメッセージが送信されることを保証するために、AMQPプロトコル(二次提出)を使用してこのようなActiveMQのよう独自の実装を、持っているためにMQコンポーネントは、一般的に使用され、ここでは学習に焦点を当てRocketMQ。

三、RocketMQ取引メッセージのデザインのアイデア

最終トランザクションの一貫性を確保するために、ことを確認するために、CAP理論、非同期トランザクションメッセージングを通じてRocketMQの方法によります。次のように二相コミットにプロセス理論を描画するために設計された、フローチャートです。
RocetMQ取引メッセージの設計

  1. アプリケーションモジュールを使用すると、事務のメッセージを送信したい場面に遭遇すると、MQにメッセージを送信する準備をします。
  2. メッセージが正常に送信された準備された後、アプリケーションモジュールは、データベース・トランザクション(ローカル・トランザクション)を実行します。
  3. その後、データベーストランザクションの実行結果、およびによると、コミットまたはロールバックMQに戻ります。
  4. 端子は、消費者に送信されている場合はコミット、メッセージをMQ、ロールバック場合は、直接メッセージを準備し、削除。
  5. 応答がない場合に第三工程、またはタイムアウトの結果は、タイミングタスクは、リコールトランザクション状態(デフォルト以上はメッセージを破棄15回まで再試行)の処理結果にステップ4を開始します。
  6. MQ自家消費の成功を確保するためのMQメカニズム。

四、RocketMQ取引メッセージの実装プロセス

RocketMQ 4.5.2バージョンに、例えば、キュ​​ーRMQ_SYS_TRANS_HALF_TOPICを特別なトランザクションメッセージを持って、すべてのメッセージを準備する最初のメッセージがコミット要求を受信したときに、ここに入れ、その後、消費者のために、実際のトピックのキューに詰めメッセージを入れています消費、RMQ_SYS_TRANS_OP_HALF_TOPICメッセージプラグつつ。簡体フローチャートは次のとおりです。
RocketMQ取引メッセージの実装プロセス

上記のプロセスは、私が責任モジュールを分割することを可能にしてください。

  1. 我々は、インポートのjarパッケージに依存するRocketMQクライアントのプロジェクト、展開されているRocketMQブローカーエンドサーバは、ネームサーバはまだ反映されます。
  2. アプリケーション・モジュール・ペア、取引メッセージのための生産の上流端、トランザクション・メッセージに対する消費者の下流端(トランザクションメッセージは通常のメッセージと一致して、消費者側に透明です)。

割込みトランザクションのアプリケーションモジュールは、ネットワークまたは他の理由のため、即時応答をもたらすことができない、RocketMQはUNKNOW処理として、RocketMQ取引メッセージは、救済策を提供する:トランザクションのポーリングメッセージデータベーストランザクションの状態
次のように簡単なフローチャートです。
トランザクション状態実装プロセスをチェックするために戻って定期的なタスクをRocketMQ

第五に、ソースコード解析

以下のフローチャートによれば、一つの分析モジュールによれば、実質的思想を機能とプロセスを説明します。

  1. 環境の準備
    ソースコードを読む前にし、IDE上RocketMQ、独自の検査方法のこの部分をデバッグするソースコードを入手する必要があります。

  2. アプリケーション・モジュール(トランザクションメッセージ生産側)コアのソースコードは、
    インターフェイスTransactionListener、データベーストランザクションとステータス方法のシミュレーション結果を確認するバックトランザクションに実装送信メソッドを実装するリスナクラスを作成します。
/**
 * @program: rocket
 * @description: 调试事务消息示例代码
 * @author: Huang
 * @create: 2019-10-16
 **/
public class SelfTransactionListener implements TransactionListener {
   private AtomicInteger transactionIndex = new AtomicInteger(0);
   private AtomicInteger checkTimes = new AtomicInteger(0);

   private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();
   /**
    * 执行本地事务
    *
    * @param message
    * @param o
    * @return
    */
   @Override
   public LocalTransactionState executeLocalTransaction(Message message, Object o) {
      String msgKey = message.getKeys();
      System.out.println("start execute local transaction " + msgKey);
      LocalTransactionState state;
      if (msgKey.contains("1")) {
         // 第一条消息让他通过
         state = LocalTransactionState.COMMIT_MESSAGE;
      } else if (msgKey.contains("2")) {
         // 第二条消息模拟异常,明确回复回滚操作
         state = LocalTransactionState.ROLLBACK_MESSAGE;
      } else {
         // 第三条消息无响应,让它调用回查事务方法
         state = LocalTransactionState.UNKNOW;
         // 给剩下3条消息,放1,2,3三种状态
         localTrans.put(msgKey, transactionIndex.incrementAndGet());
      }
      System.out.println("executeLocalTransaction:" + message.getKeys() + ",execute state:" + state + ",current time:" + System.currentTimeMillis());
      return state;
   }

   /**
    * 回查本地事务结果
    *
    * @param messageExt
    * @return
    */
   @Override
   public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
      String msgKey = messageExt.getKeys();
      System.out.println("start check local transaction " + msgKey);
      Integer state = localTrans.get(msgKey);
      switch (state) {
         case 1:
            System.out.println("check result unknown 回查次数" + checkTimes.incrementAndGet());
            return LocalTransactionState.UNKNOW;
         case 2:
            System.out.println("check result commit message, 回查次数" + checkTimes.incrementAndGet());
            return LocalTransactionState.COMMIT_MESSAGE;
         case 3:
            System.out.println("check result rollback message, 回查次数" + checkTimes.incrementAndGet());
            return LocalTransactionState.ROLLBACK_MESSAGE;

         default:
            return LocalTransactionState.COMMIT_MESSAGE;
      }
   }
}

実質的なシーンの全てを含むトランザクションメッセージプロデューサのサンプルコード、送信5つのメッセージの総数が、睡眠時間を確保するのに十分な時間に設定されたバックチェックトランザクションインスタンスがまだ実行されている場合、次のように

/**
 * @program: rocket
 * @description: Rocketmq事务消息
 * @author: Huang
 * @create: 2019-10-16
 **/
public class TransactionProducer {

   public static void main(String[] args) {
      try {
         TransactionMQProducer producer = new TransactionMQProducer("transactionMQProducer");
         producer.setNamesrvAddr("10.0.133.29:9876");
         producer.setTransactionListener(new SelfTransactionListener());
         producer.start();
         for (int i = 1; i < 6; i++) {
            Message message = new Message("TransactionTopic", "transactionTest","msg-" + i, ("Hello" + ":" +  i).getBytes());
            try {
               SendResult result = producer.sendMessageInTransaction(message, "Hello" + ":" +  i);
               System.out.printf("Topic:%s send success, misId is:%s%n", message.getTopic(), result.getMsgId());
            } catch (Exception e) {
               e.printStackTrace();
            }
         }
         Thread.sleep(Integer.MAX_VALUE);
         producer.shutdown();
      } catch (MQClientException e) {
         e.printStackTrace();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}
  1. RocketMQクライアント側コード、コードは主に3つの論理セクションに分けることができる:最初のメッセージは、メッセージのために準備するために提供され、サーバはRocketMQを送信
SendResult sendResult = null;
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
try {
    sendResult = this.send(msg);
} catch (Exception e) {
    throw new MQClientException("send message Exception", e);
}

第二段落:メッセージが正常に送信された後、呼び出し元のアプリケーションモジュールのデータベース・トランザクション・メソッドは、トランザクションの結果を取得する(スペースを節約するために、コードが要約さ)

switch (sendResult.getSendStatus()) {
    case SEND_OK: {
        try {
            if (null != localTransactionExecuter) {
                localTransactionState = localTransactionExecuter.executeLocalTransactionBranch(msg, arg);
            } else if (transactionListener != null) {
                log.debug("Used new transaction API");
                localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
            }
            if (null == localTransactionState) {
                localTransactionState = LocalTransactionState.UNKNOW;
            }
        } catch (Throwable e) {
            log.info("executeLocalTransactionBranch exception", e);
            log.info(msg.toString());
            localException = e;
        }
    }
    break;
    case FLUSH_DISK_TIMEOUT:
    case FLUSH_SLAVE_TIMEOUT:
    case SLAVE_NOT_AVAILABLE:
        localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
        break;
    default:
        break;
}

第三段階:トランザクションに送信結果RocketMQトランザクションの終端、および応答結果アプリケーションモジュールに

try {
    this.endTransaction(sendResult, localTransactionState, localException);
} catch (Exception e) {
    log.warn("local transaction execute " + localTransactionState + ", but end broker transaction failed", e);
}
  1. RocketMQブローカ端トランザクションは/ロールバック操作(本明細書部ENDTRANSACTIONを取る)コミット
    org.apache.rocketmq.broker.processor.EndTransactionProcessor:コードエントリを
OperationResult result = new OperationResult();
if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
    result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
    if (result.getResponseCode() == ResponseCode.SUCCESS) {
        RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
        if (res.getCode() == ResponseCode.SUCCESS) {
            // 修改消息的Topic为由RMQ_SYS_TRANS_HALF_TOPIC改为真实Topic
            MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
            msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));
            msgInner.setQueueOffset(requestHeader.getTranStateTableOffset());
            msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset());
            msgInner.setStoreTimestamp(result.getPrepareMessage().getStoreTimestamp());
            // 将消息存储到真实Topic中,供Consumer消费
            RemotingCommand sendResult = sendFinalMessage(msgInner);
            if (sendResult.getCode() == ResponseCode.SUCCESS) {
                // 将消息存储到RMQ_SYS_TRANS_OP_HALF_TOPIC,标记为删除状态,事务消息回查的定时任务中会做处理
                this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
            }
            return sendResult;
        }
        return res;
    }
} else if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) {
    result = this.brokerController.getTransactionalMessageService().rollbackMessage(requestHeader);
    if (result.getResponseCode() == ResponseCode.SUCCESS) {
        RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
        if (res.getCode() == ResponseCode.SUCCESS) {
            this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
        }
        return res;
    }
}
  1. タイミングセクションリコールデータベースのトランザクションのRocketMQブローカータスク
    メソッドエントリ:org.apache.rocketmq.broker.transaction.TransactionalMessageCheckService
@Override
protected void onWaitEnd() {
    long timeout = brokerController.getBrokerConfig().getTransactionTimeOut();
    // 超过15次的回查事务状态失败后,默认是丢弃此消息
    int checkMax = brokerController.getBrokerConfig().getTransactionCheckMax();
    long begin = System.currentTimeMillis();
    log.info("Begin to check prepare message, begin time:{}", begin);
    this.brokerController.getTransactionalMessageService().check(timeout, checkMax, this.brokerController.getTransactionalMessageCheckListener());
    log.info("End to check prepare message, consumed time:{}", System.currentTimeMillis() - begin);
}

バックチェックの入り口のトランザクションを呼び出します:

// 此段代码为TransactionalMessageServiceImpl类中的check方法
List<MessageExt> opMsg = pullResult.getMsgFoundList();
boolean isNeedCheck = (opMsg == null && valueOfCurrentMinusBorn > checkImmunityTime)
    || (opMsg != null && (opMsg.get(opMsg.size() - 1).getBornTimestamp() - startTime > transactionTimeout))
    || (valueOfCurrentMinusBorn <= -1
);

if (isNeedCheck) {
    if (!putBackHalfMsgQueue(msgExt, i)) {
        continue;
    }
    // 调用AbstractTransactionalMessageCheckListener的
    listener.resolveHalfMsg(msgExt);
} else {
    pullResult = fillOpRemoveMap(removeMap, opQueue, pullResult.getNextBeginOffset(), halfOffset, doneOpOffset);
    log.info("The miss offset:{} in messageQueue:{} need to get more opMsg, result is:{}", i,
        messageQueue, pullResult);
    continue;
}

// 此方法在AbstractTransactionalMessageCheckListener类中
public void resolveHalfMsg(final MessageExt msgExt) {
    executorService.execute(new Runnable() {
        @Override
        public void run() {
            try {
                sendCheckMessage(msgExt);
            } catch (Exception e) {
                LOGGER.error("Send check message error!", e);
            }
        }
    });
}

// 此方法在AbstractTransactionalMessageCheckListener类中
public void sendCheckMessage(MessageExt msgExt) throws Exception {
    CheckTransactionStateRequestHeader checkTransactionStateRequestHeader = new CheckTransactionStateRequestHeader();
    checkTransactionStateRequestHeader.setCommitLogOffset(msgExt.getCommitLogOffset());
    checkTransactionStateRequestHeader.setOffsetMsgId(msgExt.getMsgId());
    checkTransactionStateRequestHeader.setMsgId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
    checkTransactionStateRequestHeader.setTransactionId(checkTransactionStateRequestHeader.getMsgId());
    checkTransactionStateRequestHeader.setTranStateTableOffset(msgExt.getQueueOffset());
    msgExt.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC));
    msgExt.setQueueId(Integer.parseInt(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_QUEUE_ID)));
    msgExt.setStoreSize(0);
    String groupId = msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP);
    Channel channel = brokerController.getProducerManager().getAvaliableChannel(groupId);
    if (channel != null) {
        // 通过Netty发送请求到RocketMQ Client端,执行checkTransactionState方法
        brokerController.getBroker2Client().checkProducerTransactionState(groupId, channel, checkTransactionStateRequestHeader, msgExt);
    } else {
        LOGGER.warn("Check transaction failed, channel is null. groupId={}", groupId);
    }
}

RocketMQクライアントサーバ要求、リコールリコールデータベース・トランザクション・メソッドとトランザクションの結果を受け取った後、再びRocketMQブローカーエンドに提出する
方法エントリー:クラスメソッドをorg.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl

try {
    if (transactionCheckListener != null) {
        localTransactionState = transactionCheckListener.checkLocalTransactionState(message);
    } else if (transactionListener != null) {
        log.debug("Used new check API in transaction message");
        localTransactionState = transactionListener.checkLocalTransaction(message);
    } else {
        log.warn("CheckTransactionState, pick transactionListener by group[{}] failed", group);
    }
} catch (Throwable e) {
    log.error("Broker call checkTransactionState, but checkLocalTransactionState exception", e);
    exception = e;
}

this.processTransactionState(
    localTransactionState,
    group,
    exception);

第六に、補足質問

公式サイト言及したトランザクションメッセージはレイテンシーメッセージングおよびバルクメッセージをサポートしていません、私は、アプリケーションモジュールの検出をログに記録し、安いが、それが取引メッセージDelayTimeLevel、RMQ_SYS_TRANS_HALF_TOPICから削除することはできません失われたこのメッセージの結果を設定し、メッセージを延期しようとした手繰り返し業務を戻って確認しようとする試み、すぐに2000件の以上のレコードがクエリのニュースのリストにRMQ_SYS_TRANS_HALF_TOPICコンソールインタフェース、およびその理由で?

私たちは、次のように分析されているコード・レベルに戻ります。

1.トランザクションがコミット(またはデータベース・トランザクションが完了した後にバックチェック)後DelayTimeLevel、データを設定した後、メッセージが、干渉のDelayTimeLevelに、ターゲットトピックに書き込まれREAL_TOPICがRMQ_SYS_TRANS_HALF_TOPICなりながら、ターゲットトピック、SCHEDULE_TOPIC_XXXXなり、実トピックは、このセッションでは失われています。

// RocketMQ Broker端接受事务提交后的处理
org.apache.rocketmq.broker.processor.EndTransactionProcessor类
OperationResult result = new OperationResult();
if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
    // 这里调用CommitLog的putMessage方法
    result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
    if (result.getResponseCode() == ResponseCode.SUCCESS) {
        RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
        if (res.getCode() == ResponseCode.SUCCESS) {
            // 修改消息的Topic为由RMQ_SYS_TRANS_HALF_TOPIC改为真实Topic
            MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
            msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));
            msgInner.setQueueOffset(requestHeader.getTranStateTableOffset());
            msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset());
            msgInner.setStoreTimestamp(result.getPrepareMessage().getStoreTimestamp());
            // 将消息存储到真实Topic中,此时Topic已经变成SCHEDULE_TOPIC_XXXX
            RemotingCommand sendResult = sendFinalMessage(msgInner);
            if (sendResult.getCode() == ResponseCode.SUCCESS) {
                // 将消息存储到RMQ_SYS_TRANS_OP_HALF_TOPIC,标记为删除状态,事务消息回查的定时任务中会做处理
                this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
            }
            return sendResult;
        }
        return res;
    }
}

// 此段代码在org.apache.rocketmq.store.CommitLog类的putMessage方法中
// 由于DelayTimeLevel的干扰,目标Topic将变成SCHEDULE_TOPIC_XXXX
final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());
if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE
    || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
    // Delay Delivery
    if (msg.getDelayTimeLevel() > 0) {
        if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
            msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());
        }

        topic = ScheduleMessageService.SCHEDULE_TOPIC;
        queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());

        // Backup real topic, queueId
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));
        msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));

        msg.setTopic(topic);
        msg.setQueueId(queueId);
    }
}

たとえば、印刷ログは、次のように:

2019-10-17 14\:41\:05 INFO EndTransactionThread_4 - Transaction op message write successfully. messageId=0A00851D00002A9F0000000000000E09, queueId=0 
msgExt:MessageExt [queueId=0, storeSize=335, queueOffset=5, sysFlag=8, bornTimestamp=1571293959305, bornHost=/10.0.133.29:54634, storeTimestamp=1571294460555, 
storeHost=/10.0.133.29:10911, msgId=0A00851D00002A9F0000000000000E09, commitLogOffset=3593, bodyCRC=1849408413, reconsumeTimes=0, preparedTransactionOffset=0, 
toString()=Message{topic='SCHEDULE_TOPIC_XXXX', flag=0, properties={REAL_TOPIC=RMQ_SYS_TRANS_HALF_TOPIC, TRANSACTION_CHECK_TIMES=3, KEYS=msg-test-3, 
TRAN_MSG=true, UNIQ_KEY=0A00851D422C18B4AAC25584B0880000, WAIT=false, DELAY=1, PGROUP=transactionMQProducer, TAGS=transactionTest, REAL_QID=0}, 
body=[72, 101, 108, 108, 111, 84, 105, 109, 101, 58, 51], transactionId='null'}]

2.遅延メッセージがタスクは、私はちょうど戻って再びニュースRMQ_SYS_TRANS_HALF_TOPICに、スケジュールされたタスクを1秒の遅延を設定するトリガさタイムアウトされ、この時だけRMQ_SYS_TRANS_HALF_TOPICニュースは、RMQ_SYS_TRANS_OP_HALF_TOPICキューは、このメッセージは、次のコードではないことに注意してください:

// 此段代码在org.apache.rocketmq.store.schedule.ScheduleMessageService类executeOnTimeup方法内
try {
    // 消息重新回到RMQ_SYS_TRANS_HALF_TOPIC队列中
    MessageExtBrokerInner msgInner = this.messageTimeup(msgExt);
    PutMessageResult putMessageResult =
        ScheduleMessageService.this.writeMessageStore
            .putMessage(msgInner);

    if (putMessageResult != null
        && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) {
        continue;
    } else {
        log.error(
            "ScheduleMessageService, a message time up, but reput it failed, topic: {} msgId {}",
            msgExt.getTopic(), msgExt.getMsgId());
        ScheduleMessageService.this.timer.schedule(
            new DeliverDelayedMessageTimerTask(this.delayLevel,
                nextOffset), DELAY_FOR_A_PERIOD);
        ScheduleMessageService.this.updateOffset(this.delayLevel,
            nextOffset);
        return;
    }
} catch (Exception e) {
    log.error(
        "ScheduleMessageService, messageTimeup execute error, drop it. msgExt="
            + msgExt + ", nextOffset=" + nextOffset + ",offsetPy="
            + offsetPy + ",sizePy=" + sizePy, e);
}

3.取引メッセージタイミングタスクはRMQ_SYS_TRANS_HALF_TOPICのニュースをチェックし、開始しますが、何のメッセージは、順序を保証するためにメッセージを書きませんRMQ_SYS_TRANS_OP_HALF_TOPIC、およびRMQ_SYS_TRANS_OP_HALF_TOPICにこのメッセージを再作成し、トリガートランザクション操作をバックチェック。リコール同じコールトランザクション・エントリ上記のサンプルコード:

// 此段代码为TransactionalMessageServiceImpl类中的check方法
List<MessageExt> opMsg = pullResult.getMsgFoundList();
boolean isNeedCheck = (opMsg == null && valueOfCurrentMinusBorn > checkImmunityTime)
    || (opMsg != null && (opMsg.get(opMsg.size() - 1).getBornTimestamp() - startTime > transactionTimeout))
    || (valueOfCurrentMinusBorn <= -1
);

if (isNeedCheck) {
    if (!putBackHalfMsgQueue(msgExt, i)) {
        continue;
    }
    listener.resolveHalfMsg(msgExt);
} else {
    pullResult = fillOpRemoveMap(removeMap, opQueue, pullResult.getNextBeginOffset(), halfOffset, doneOpOffset);
    log.info("The miss offset:{} in messageQueue:{} need to get more opMsg, result is:{}", i,
        messageQueue, pullResult);
    continue;
}

これは前にこのメッセージを15回を破棄しようとする試み(試行のデフォルトの最大数は15である)まで、その価格は少し大きいです、無限ループを構成しています。この問題の最適化のために、RocketMQ PRコミュニティに提出された、新しいバージョンがリリースされ、取引メッセージは、この問題が再び起こらないだろう、DelayTimeLevelを保護します。

新しいバージョンがリリースされる前に、当社のソリューション:

  1. R&Dプロセス明確に禁止されたトランザクションメッセージはDelayTimeLevelを設定します。
    (私が最初に行ったように)、結局、リスクのある新しい子供の靴を感じ、こののいくつかの機能が追加される場合があります振戦の特に意識していないです。
  2. RocketMQクライアントは、トランザクション・メッセージは、次の例の入口に設けられた送信方法を提供することができませんrocketmqスプリングブート・スターターとして、単純なパッケージを作ります。
/**
 * 事务消息发送
 * 不支持延迟发送和批量发送
 */
public void sendMessageInTransaction(String topic, String tag, Object message, String requestId) throws Exception {
   TransactionMQProducer producer = annotationScan.getProducer(topic + "_" + tag);
   producer.sendMessageInTransaction(MessageBuilder.of(topic, tag, message, requestId).build(), message);
}

それは結局、元からDelayTimeLevel設定したパラメータに終止符を打つ、理想的でなければなりません。

VII結論

Benpian簡潔には、著者のアートワークから学ぶ、その後、コードの簡単な説明を選択するか、独自の掘るピットプロセスこのRocketMQで取引メッセージのシーンと責任の制限、基本的なデザインのアイデアやプロセスに対処しました、任意の正しくないか徹底的なガイド場所がある記事では、メッセージを残してください、ありがとうございました。

Javaの並行性の高いフォーカス、分散アーキテクチャ、より多くの呉服共有技術と経験は、公共の数に注意してください:Javaのアーキテクチャコミュニティ
Javaのアーキテクチャコミュニティ

おすすめ

転載: www.cnblogs.com/huangying2124/p/11702761.html