1.トランザクションメッセージの発信元
1.ケース
公式ショッピングケースの引用:
Xiao Mingが100元のアイテムを購入すると、アカウントから100元が差し引かれ、ダウンストリームポイントシステムがXiaoMingのアカウントに100ポイントを追加するようにする必要があります。アカウントシステムとポイントシステムは2つの独立したシステムであり、1つは100元削減し、もう1つは100ポイント増やす必要があります。以下に示すように:
2.問題
- アカウントサービスの控除は成功し、通知ポイントシステムも成功しましたが、ポイントが増えると失敗し、データに一貫性がありませんでした。
- アカウントサービスの控除は成功しましたが、通知ポイントシステムが失敗したため、ポイントは増加せず、データに一貫性がありません。
3.スキーム
最初の問題に対するRocketMQの解決策は、消費が失敗した場合、自動的に再試行することです。数回の再試行後に消費が失敗した場合、この状況は、デッドレターキューに入れてから手動で確認するなど、手動で解決する必要があります。原因とそれに対処します。
2番目の問題に対するRocketMQの解決策は、お金の控除に成功したが、mqへのメッセージの書き込みに失敗した場合、RocketMQはメッセージをロールバックします。この時点で、控除操作をロールバックすることもできます。
第二に、トランザクションメッセージの原則
1.原理図
2.詳細なプロセス
- 1.プロデューサーはハーフメッセージ(ハーフメッセージ)をブローカーに送信します。
なぜハーフメッセージと呼ばれるのかわかりにくいので文句を言いたいのですが、実はこれは準備メッセージ、送信済みメッセージです。
- ハーフメッセージが正常に送信された後、ローカルトランザクションが実行されます。
- ローカルトランザクションが正常に実行された場合はcommitを返し、実行が失敗した場合はロールバックを返します。(これは、トランザクションメッセージのコミットまたはロールバックのコールバックメソッドで開発者自身が決定します)
- プロデューサーは前のステップのコミットまたはロールバックをブローカーに送信します。ここには2つの状況があります。
1.ブローカーがコミット/ロールバックメッセージを受信した場合:
- コミットが受信された場合、ブローカーはトランザクション全体が正常であり、実行が成功したと見なします。次に、消費のためにコンシューマー側にメッセージを送信します。
- ロールバックが受信された場合、ブローカーはローカルトランザクションの実行が失敗したと見なし、ブローカーはハーフメッセージを削除してコンシューマーに送信しません。
2.ブローカーがメッセージを受信しない場合(ローカルトランザクションの実行が突然ダウンした場合、同等のローカルトランザクションの実行結果は不明を返し、ブローカーが確認メッセージを受信しなかったかのように扱われます)。
- ブローカーは定期的にローカルトランザクションの実行結果を確認します。確認結果がローカルトランザクションが実行された場合はコミットを返し、そうでない場合はロールバックを返します。
- プロデューサーのバックチェックの結果はブローカーに送信されます。ブローカーがコミットを受信した場合、ブローカーはトランザクション全体が正常に実行されたと見なします。ロールバックの場合、ブローカーはローカルトランザクションが失敗したと見なし、ブローカーはハーフメッセージを削除してコンシューマーに送信しません。ブローカーがバックチェックの結果を受け取らない場合(または不明な場合)、ブローカーは定期的にバックチェックを繰り返して、最終的なトランザクション結果が見つかるようにします。時間間隔と繰り返されるバックチェックの数の両方を構成できます。
3、トランザクションメッセージ実現プロセス
1.実装プロセス
簡単に言うと、トランザクションメッセージはコールバック関数を持つリスナーです。コールバック関数では、アカウントに-100元を与えてから、ポイントのmqにメッセージを送信するなどのビジネスロジック操作を実行します。これでアカウント-100が成功した場合、およびメッセージがmqに正常に送信された場合、メッセージステータスはコミットに設定されます。この時点で、ブローカーはハーフメッセージを実際のトピックに送信します。最初は、セミメッセージキューに送信されましたが、実際のトピックキューには存在しませんでした。コミットを確認して初めて転送されます。
2.是正計画
中断またはその他のネットワーク上の理由でトランザクションがすぐに応答しない場合、RocketMQはそれをUNKNOWとして扱います。RocketMQトランザクションメッセージも解決策を提供します。トランザクションメッセージのトランザクションステータスを定期的に照会します。これもコールバック関数です。補正はその中で行うことができます。補正ロジックの開発者はそれを自分で作成します。成功すると、コミットに戻り、完了します。
4、コード例
1.コード
package com.chentongwei.mq.rocketmq;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.Date;
/**
* Description:
*
* @author TongWei.Chen 2020-06-21 11:32:58
*/
public class ProducerTransaction2 {
public static void main(String[] args) throws Exception {
TransactionMQProducer producer = new TransactionMQProducer("my-transaction-producer");
producer.setNamesrvAddr("124.57.180.156:9876");
// 回调
producer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object arg) {
LocalTransactionState state = null;
//msg-4返回COMMIT_MESSAGE
if(message.getKeys().equals("msg-1")){
state = LocalTransactionState.COMMIT_MESSAGE;
}
//msg-5返回ROLLBACK_MESSAGE
else if(message.getKeys().equals("msg-2")){
state = LocalTransactionState.ROLLBACK_MESSAGE;
}else{
//这里返回unknown的目的是模拟执行本地事务突然宕机的情况(或者本地执行成功发送确认消息失败的场景)
state = LocalTransactionState.UNKNOW;
}
System.out.println(message.getKeys() + ",state:" + state);
return state;
}
/**
* 事务消息的回查方法
*/
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
if (null != messageExt.getKeys()) {
switch (messageExt.getKeys()) {
case "msg-3":
System.out.println("msg-3 unknow");
return LocalTransactionState.UNKNOW;
case "msg-4":
System.out.println("msg-4 COMMIT_MESSAGE");
return LocalTransactionState.COMMIT_MESSAGE;
case "msg-5":
//查询到本地事务执行失败,需要回滚消息。
System.out.println("msg-5 ROLLBACK_MESSAGE");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
return LocalTransactionState.COMMIT_MESSAGE;
}
});
producer.start();
//模拟发送5条消息
for (int i = 1; i < 6; i++) {
try {
Message msg = new Message("transactionTopic", null, "msg-" + i, ("测试,这是事务消息! " + i).getBytes());
producer.sendMessageInTransaction(msg, null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.結果
msg-1,state:COMMIT_MESSAGE
msg-2,state:ROLLBACK_MESSAGE
msg-3,state:UNKNOW
msg-4,state:UNKNOW
msg-5,state:UNKNOW
msg-3 unknow
msg-3 unknow
msg-5 ROLLBACK_MESSAGE
msg-4 COMMIT_MESSAGE
msg-3 unknow
msg-3 unknow
msg-3 unknow
msg-3 unknow
3.コントロールコンソール
4.結果分析
- msg-1とmsg-4のみが正常に送信されました。msg-1が最初に成功し、msg-4はバックチェック後にのみ成功したため、msg-4はmsg-1よりも進んでいます。時系列の逆順で提供されます。
- 最初に、5つのメッセージに対応する5つの結果を出力します
msg-1、state:COMMIT_MESSAGE
msg-2、state:ROLLBACK_MESSAGE
msg-3、state:UNKNOW
msg-4、state:UNKNOW
msg-5、state:UNKNOW
- 次にレビューを入力すると、msg-3はまだ不明であり、msg-5はロールバックされ、msg-4はトランザクションを送信しました。したがって、この時点でmsg-4はコントロールコンソールに表示されます。
- しばらくして、もう一度msg-3をチェックしたところ、まだわからないことがわかったので、チェックを続けました。
時間間隔とバックチェックの数は構成可能です。デフォルトでは、チェックバックが15回失敗すると、メッセージは破棄されます。
5、質問
質問:Springトランザクションと通常の分散トランザクションは良くありませんか?Rocketmqの業務は不要ですか?
MQはデカップリングに使用されます。以前は、分散トランザクションはアカウントシステムとポイントシステムを直接操作していました。ただし、その2つは強い結合の存在であり、途中にmqを挿入すると、アカウントシステムの運用後にアカウントシステムがmqにメッセージを送信します。成功すると送信され、送信が失敗した場合は送信がロールバックされます。そして、分散トランザクションにRocketMQを使用している人はかなりたくさんいます。