RocketMQと他のメッセージミドルウェアの最大の違いは、トランザクションメッセージをサポートしていることです。これは、分散トランザクションにおけるメッセージベースの結果整合性スキームでもあります。
1.トランザクションメッセージとは何ですか?
トランザクションメッセージ:トランザクション特性を持つメッセージ。つまり、プロデューサーがブローカーに送信された後、メッセージをロールバックまたは送信できます(コンシューマーはコミット後にのみ表示されます)。
2.トランザクションメッセージの用途は何ですか?
RocketMQの公式例:ユーザーAが注文を開始し、100元を支払い、操作の完了後に100ポイントを獲得します。アカウントサービスとメンバーシップサービスは、上記の問題に応じて、それぞれ独自のデータベースを持つ2つの独立したマイクロサービスモジュールです。これらの状況があります:
-
最初にお金を差し引いてからメッセージを送信すると、お金が差し引かれたばかりで、マシンがダウンし、メッセージが送信されず、結果としてポイントが増加しなかった可能性があります。
-
最初にメッセージを送信してからお金を差し引くと、ポイントが増える可能性がありますが、お金は差し引かれず、100ポイントが無料で他の人に与えられます。
// 先扣款,再加积分伪代码:
@Transational
pay() {
mysql.payMoney() // 数据库中添加用户信息
reduceRepo() // 数据库中减少库存
}
-------------------------------------// 若先发消息再加积分,那在这行宕机怎么办?
producer.send(msg) // 发送100积分
==>したがって、上記の方法は実行不可能です。最初にメッセージ(プラスポイント)をブローカーに送信できますが、メッセージをコンシューマーの非表示状態に設定します
- ローカルトランザクション(控除)が正常に処理されたら、コンシューマーに表示させます
- ローカルトランザクション(控除)が失敗した場合、現在のメッセージはロールバックされます
ここで問題が発生している可能性があります。プロデューサーのローカルトランザクションが成功した後、ブローカーへのトランザクション確認メッセージの送信に失敗します。どうすればよいですか?
今回は、消費者がニュースを正常に消費できないことを意味します。したがって、RocketMQはメッセージレビューメカニズムを提供します。トランザクションメッセージが常に中間状態にある場合、ブローカーは再試行を開始して、ブローカーでのトランザクションの処理ステータスを照会します。トランザクションが成功したことが判明すると、現在のメッセージが表示されるように設定されます
全体的なモデル図は次のとおりです。
上記の例から、トランザクションメッセージは一般的にローカルトランザクションの前に使用されることがわかります。これはネストされたトランザクションとして理解することもでき、メッセージの送信は外部トランザクションであり、ローカルトランザクションはメモリトランザクションです。
3. Javaはトランザクションメッセージを使用しますか?
上記の例では、特定のコードを使用して実装する方法を見てみましょう。
TransactionProducer
public class TransactionProducer {
public static void main(String[] args) throws Exception {
// 这里用的是事务Producer(TransactionMQProducer)
TransactionMQProducer transactionProducer=new TransactionMQProducer("tx_producer_group");
transactionProducer.setNamesrvAddr("43.105.136.120:9876");
// 自定义线程池,用于异步执行事务操作
transactionProducer.setExecutorService(Executors.newFixedThreadPool(10); );
// 核心!!添加事务消息监听
transactionProducer.setTransactionListener(new TransactionListenerLocal());
transactionProducer.start();
for(int i=0;i<20;i++) {
String orderId= UUID.randomUUID().toString();
String body="{'operation':'doOrder','orderId':'"+orderId+"'}";
// 构建消息
Message message = new Message("pay_tx_topic", "TagA",orderId, body.getBytes(RemotingHelper.DEFAULT_CHARSET));
// 发送消息, 注:是发送事务消息
transactionProducer.sendMessageInTransaction(message, orderId+"&"+i);
Thread.sleep(1000); // 1秒一次
}
}
}
TransactionListenerLocal(トランザクションメッセージコア)
// 本地事务监听,实现TransactionListener接口
public class TransactionListenerLocal implements TransactionListener {
private static final Map<String,Boolean> results=new ConcurrentHashMap<>();
// 执行本地事务
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
System.out.println(":执行本地事务:"+arg.toString());
String orderId=arg.toString();
// 模拟数据入库操作(成功/失败)
boolean rs=saveOrder(orderId);
return rs? LocalTransactionState.COMMIT_MESSAGE:LocalTransactionState.UNKNOW;
// 这个返回状态表示告诉broker这个事务消息是否被确认,允许给到consumer进行消费
// LocalTransactionState.ROLLBACK_MESSAGE 回滚
// LocalTransactionState.UNKNOW 未知
}
// 提供事务执行状态的回查方法,提供给broker回调
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
String orderId=msg.getKeys();
System.out.println("执行事务执行状态的回查,orderId:"+orderId);
boolean rs=Boolean.TRUE.equals(results.get(orderId));
System.out.println("回调:"+rs);
return rs?LocalTransactionState.COMMIT_MESSAGE:
LocalTransactionState.ROLLBACK_MESSAGE;
}
private boolean saveOrder(String orderId){
//如果订单取模等于0,表示成功,否则表示失败
boolean success=Math.abs(Objects.hash(orderId))%2==0;
results.put(orderId,success);
return success;
}
}
TransactionConsumer
public class TransactionConsumer {
public static void main(String[] args) throws MQClientException, IOException {
DefaultMQPushConsumer defaultMQPushConsumer=new
DefaultMQPushConsumer("tx_consumer_group");
defaultMQPushConsumer.setNamesrvAddr("43.105.136.120:9876");
defaultMQPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_O FFSET);
defaultMQPushConsumer.subscribe("pay_tx_topic","*");
defaultMQPushConsumer.registerMessageListener((MessageListenerConcurrently)
(msgs, context) -> {
msgs.stream().forEach(messageExt -> {
try {
String orderId=messageExt.getKeys();
// 拿到消息
String body=new String(messageExt.getBody(),
RemotingHelper.DEFAULT_CHARSET);
// 扣减库存
System.out.println("收到消息:"+body+",开始扣减库存:"+orderId);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
});
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
defaultMQPushConsumer.start();
System.in.read();
}
}
4.トランザクションメッセージの3つの状態?
上記のTransactionListenerLocalクラスでは、オーバーライドされた両方のメソッドがLocalTransactionStateを返す必要があることがわかりました。これは、この既存のトランザクションメッセージの処理方法をブローカーに指示することを意味します。
-
ROLLBACK_MESSAGE:トランザクションをロールバックします
executeLocalTransactionメソッドがROLLBACK_MESSAGEを返す場合、トランザクションが直接ロールバックされることを意味します
-
COMMIT_MESSAGE:トランザクションをコミットします
-
UNKNOW:ブローカーは、成功または失敗するまで、プロデューサーメッセージのステータスを定期的にチェックします。
UNKNOWが返されると、ブローカーは一定期間後にcheckLocalTransactionをチェックバックし、checkLocalTransactionの返されるステータスに従ってトランザクション操作(ロールバックまたはコミット)を実行します。
例のように、ROLLBACK_MESSAGEが返されると、コンシューマーはメッセージを受信せず、チェックバック関数を呼び出しません。COMMIT_MESSAGEが返されると、トランザクションがコミットされ、コンシューマーがメッセージを受信します。UNKNOWを返すと、チェックが行われます。 -一定期間後にback関数が呼び出され、ステータスに応じて送信ステータスまたはロールバックステータスを返します。送信ステータスを返すメッセージはコンシューマーによって消費されるため、この時点でコンシューマーはメッセージの一部を消費できます。 。