使用メッセージキュー、いくつかの重要な問題、その必要性の注意

メッセージキューは、プロジェクトの作業に使用されている、我々はいくつかの重要な問題に注意を払う必要があります。

  • 注文の問題のメッセージ
  • 複製メッセージ
  • Transactionメッセージ

オーダーメッセージ

順序付けられたメッセージを指すメッセージ消費の順序に従って送信されても​​よいです。たとえば:順序は3つのメッセージ、すなわち、オーダー作成、注文の支払いを持っていた、オーダーが完了しています。とき消費は、消費の順序に従って理にかなっています。複数の注文の間で同時に並行消費です。次の例ではまず見て:

生産者は二つのメッセージがあった場合:M1、M2を、これら二つのメッセージの順序は、何を行うべきであることを保証するには?あなたは、このような脳を考えるかもしれません。

 
 

M1 M2が到着MQSERVER(生産者が正常M1 M2を送信し送信する前に待つ)に先立って、消費されるべき第一の原則を達成するために応じて、M1が最初こうしてメッセージの順序を保証する、M2で消費されることを、この保証します。

このモデルでは、実際の場面では、次のような問題が発生する可能性があり、注文メッセージを保証する唯一の理論的には可能です。

限り、メッセージが別のサーバーに送信されるように、ネットワークの遅延の問題が存在します。上記のように送信がM2 M1加工時間のかかる送信より大きい場合、その後M2は、第一の消費者は、まだメッセージの順序を保証することができないままです。M1とM2は、負荷による、消費者側に到達してもしながら、M2はM1の消費が発生している2明確な消費者側1ではなく、消費者側は、それが事態の前にまだ可能です。

どのようにしてこの問題を解決するには?M1とM2は、同じ消費者に送信され、M1を送信した後、消費者は、M2を送信するために正常な応答後に終了する必要があります。

スマートあなたは別の質問を考えなければならないかもしれません:M1は、最終消費者に送信された場合、消費者側1応答は、M2は、それを送ったり、M1を再送し続けているしないのですか?特定のメッセージは、一般消費者であることを確認するために、M1は、別の消費者端末2への再送を選択し、それを以下に示します。

消費者側1は、サーバの応答しない場合、このようなモデルは、あなたはまだ問題があります慎重にメッセージの厳密な順序付けを保証するであろう、2例があり、一つはM1は、(ネットワーク伝送におけるデータ損失)に達していないで、他の消費者M1と最終消費者は、応答メッセージが送信されていましたが、MQサーバーは受信しません。後者の場合、再送信M1場合、M1は、支出の重複になります。それは我々が言っている2番目の質問、メッセージの重複は、この後に詳しく説明します紹介しています。

バック・メッセージ・シーケンスを見て、本明細書に記載されるように厳密注文メッセージに容易に理解、また、簡単な方法によって処理されます。厳格なメッセージシンプルかつ実行可能な方法を達成するために、総括するには、次のとおりです。

保証は生产者 - MQServer - 消费者1対1の関係であります

このデザインはシンプルですが、それはまたのようないくつかの非常に深刻な問題を持っていますが。

  1. 並列度は、ボトルネック・メッセージ・システム(特定せず)になるであろう
  2. など、より多くの例外処理、:長い問題が発生したとして、それは全体のプロセスフロー妨害の原因となりますよう、消費者の最後に、我々は、目詰まりの問題を解決するために、より多くのエネルギーを費やす必要が。

注文メッセージの問題この観点から、我々は2つの結論を導き出すことができます。

  1. アプリケーションは、実際の障害には関係しません富みます
  2. メッセージキューの障害は障害を意味するものではありません

そのため、運用レベルからのメッセージの順序を保証するために、ちょうどメッセージングシステムに依存しない、そして最終的に我々は、ビューのソースポイントからのメッセージを送信するRocketMQ順序を達成するためにどのように分析します。RocketMQは、すべてのキューのポーリングを介してメッセージが送信されるとキュー(負荷分散ポリシー)を決定します。そのような次の例のように、同じメッセージの順序番号が連続して同一のキューに送信されます。

// RocketMQ通过MessageQueueSelector中实现的算法来确定消息发送到哪一个队列上
// RocketMQ默认提供了两种MessageQueueSelector实现:随机/Hash
// 当然你可以根据业务实现自己的MessageQueueSelector来决定消息按照何种策略发送到消息队列中
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        Integer id = (Integer) arg;
        int index = id % mqs.size();
        return mqs.get(index);
    }
}, orderId);

将来のルーティング情報を取得するには、に基づいてMessageQueueSelectorキューと確かに同じOrderIdでのキューアルゴリズム、アクセスを選択します。

private SendResult send()  {
    // 获取topic路由信息
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    if (topicPublishInfo != null && topicPublishInfo.ok()) {
        MessageQueue mq = null;
        // 根据我们的算法,选择一个发送队列
        // 这里的arg = orderId
        mq = selector.select(topicPublishInfo.getMessageQueueList(), msg, arg);
        if (mq != null) {
            return this.sendKernelImpl(msg, mq, communicationMode, sendCallback, timeout);
        }
    }
}

第二に、重複メッセージ

上記の問題メッセージ・シーケンスを解決するには、メッセージが繰り返される、新たな問題を紹介します。だから、RocketMQメッセージの繰り返しは、問題を解決する方法ですか?それとも「ただ」は解決されていません。

メッセージの根本的な原因が繰り返される:ネットワークが到達不能です。長いネットワークを介してデータの交換などとして、あなたはこの問題を回避することはできません。だから、この問題を解決するには、この問題のバイパスにあります。質問はその後、次のようになります。消費者側は、2つの異なるメッセージを受信した場合、彼らは何をすべきでしょうか?

  1. 消費者側保持サービス論理処理冪等メッセージ
  2. 各メッセージが同じタイムテーブルで再表示されるようにログの成功を確実にするために固有の番号とメッセージ処理を持っていることを確認してください

第1条はよく、ちょうど関係なく、どのように多くの重複メッセージを冪等を維持していない理解し、それが最終的には同じように処理しました。第2条の原則は、新たに到着したメッセージIDがログテーブルにすでにある場合、IDが正常に、メッセージを処理した記録するためにログテーブルを使用することで、それはもはや、このメッセージに対処しません。

第1条ソリューション、消費者側ではなく、実装されるメッセージシステム機能の一部に達成すべきであることは明らかです。第2条は事業終了を達成することができ、メッセージングシステムに実装することができます。通常の状況下で重複メッセージの確率は、実際には非常に小さく、メッセージングシステムによって実装されている場合、それは確かに、重複した質問のビジネスの終わりから、自分のメッセージに対処するので、最高のメッセージングシステムのスループットと影響力の高い利用できるようになり、これがありますその理由は、重複メッセージの問題を解決しないRocketMQ。

RocketMQメッセージがあなたのビジネスは厳密に重複したメッセージである必要はありません場合は、あなたがビジネス側の重いを移動する必要があり、繰り返さないことを保証するものではありません。

第三に、取引メッセージ

トランザクションメッセージをサポートするだけでなく、共通のメッセージ、メッセージシーケンスを、サポートに加えRocketMQ。まず、我々は、トランザクションメッセージとトランザクションメッセージの必要性が何であるかを議論します。100にボブ・スミス転送:私たちは、問題を説明するための例として、シナリオの転送を持っています。

スタンドアロン環境では、トランザクションの実装は、おそらく次のようになります。

転送トランザクションの模式スタンドアロン環境

ある程度のユーザーの成長は、ボブ・スミスと同じサーバ上にもはや口座残高情報とは、上記のプロセスは、このなると:

転送トランザクション概略的なクラスタ環境

あなたは見つけるでしょう。この時間は、また、事業譲渡され、クラスタ環境では、時間がかかり、実際に明らかに受け入れることができないである、指数関数的に成長します。その問題を回避するには?

大きな取引小さなトランザクション+ =非同期

複数の小さなトランザクションに侵入大きなトランザクションは非同期に実行しました。シングルと一致するように最適化されたマシン間で効率的にトランザクションを実行するので、基本的にできます。転送トランザクションは、以下の2つの小さなトランザクションに分けることができます。

小さなトランザクション非同期メッセージング+


図ローカル・トランザクション(ボブ・デビットアカウント)を実行すると、電荷が失敗した場合、あなたがメッセージを送信できない、成功しなければならないメッセージを送信し、成功を充電すると同時に、で、成功または失敗を確保すべきであると同時に、非同期メッセージを送信します。その質問は:それを充電するか、メッセージを送信するのですか?

まず、概略的には実質的に以下のメッセージを、送信する前に状況を見て:

トランザクションメッセージ:最初のメッセージを送信します

問題があります:メッセージが正常に送信されますが、電荷を失敗した場合、消費者側は、メッセージを消費した後、スミス口座にお金を追加します。

次のように最初のメッセージは仕事が、その後、最初にそれを充電し、ラフ図であるされていません。

トランザクションメッセージ - 最初の充電

上記と同様の問題は:電荷が成功した場合は、メッセージを送信すると、失敗し、そこにお金を控除ボブになりますが、スミスは口座にお金を追加しませんでした。

我々のようなこの問題を解決するための方法を、多くの持っている可能性があります送信が失敗した場合に行くには直接ボブ・デビット取引へのメッセージを、例外がスローされ、トランザクションはロールバックされます。このアプローチは、「ただ」解決する必要はありませんの原則と一致しています。

それは説明する必要があります:あなたは物事を管理するために春を使用した場合、彼らは行くためにローカル論理事にメッセージを送ることができ、メッセージを送信する例外をスローに失敗した、春のロールが例外をキャッチするためにこの事の後に戻って、確実にするためにアトミックローカルなものとのメッセージを送ります。

RocketMQサポートトランザクションメッセージ、RocketMQを達成するためにどのように見てみましょう。

トランザクションメッセージを送信実現RocketMQ

RocketMQ第一段階が送信されPrepared消息、メッセージはアドレスにアクセスするために介してメッセージを取得し、メッセージのステータスを変更する最初のステージにローカルなものの実装の第二段階、第三段階のアドレスを取得します。

慎重に、あなたはよくご確認のメッセージが失敗した場合、問題は、どのように行うことがことがありますか?RocketMQは、定期的に物事のメッセージクラスタ内のスキャンのメッセージが、見つかった場合Prepared消息、それはメッセージの送信者に(プロデューサー)確認となり、最終的にはボブのお金がカットされたり、それをカットしていませんでしたか?減少はロールバックするか継続する場合には、確認メッセージを送信しますか?RocketMQは、ロールバックするかどうかを決定または送信者が設定したポリシーに従って、確認メッセージを送信し続けることになります。ローカルトランザクションで送信されたメッセージが成功するか失敗することをこれが保証されます。

その後、我々はRocketMQソースを見て、それはメッセージの事務を処理する方法です。クライアントの一部は(:完全なコードを参照してくださいトランザクションメッセージを送るrocketmq-example建設中com.alibaba.rocketmq.example.transaction.TransactionProducer):

// =============================发送事务消息的一系列准备工作========================================
// 未决事务,MQ服务器回查客户端
// 也就是上文所说的,当RocketMQ发现`Prepared消息`时,会根据这个Listener实现的策略来决断事务
TransactionCheckListener transactionCheckListener = new TransactionCheckListenerImpl();
// 构造事务消息的生产者
TransactionMQProducer producer = new TransactionMQProducer("groupName");
// 设置事务决断处理类
producer.setTransactionCheckListener(transactionCheckListener);
// 本地事务的处理逻辑,相当于示例中检查Bob账户并扣钱的逻辑
TransactionExecuterImpl tranExecuter = new TransactionExecuterImpl();
producer.start()
// 构造MSG,省略构造参数
Message msg = new Message(......);
// 发送消息
SendResult sendResult = producer.sendMessageInTransaction(msg, tranExecuter, null);
producer.shutdown();
次を表示 sendMessageInTransactionのソース法、三段階の合計:送信準備メッセージは、ローカルの業務を行い、確認メッセージが送信されます。

endTransactionリクエストの方法はに送信されるbroker(mq server)ステータスメッセージを更新するために、最終的なトランザクション。

  1. するためによるsendResult見つけPrepared消息 、sendResultそれはメッセージのトランザクションIDが含まれています
  2. localTransaction最終状態更新メッセージ

場合はendTransactionこの方法が失敗し、データがに送信されていないbrokerニュースの更新に失敗した事務の結果の状態、broker各トランザクションの状態保存のためのスレッドタイマー(デフォルトは1分)スキャンテーブルファイルをチェックするために、バックがあるだろう、メッセージがすでにコミットされたり、直接ジャンプた場合はロールバックしかし、場合prepared状态意志にProducer開始CheckTransaction要求を、Producer呼び出してDefaultMQProducerImpl.checkTransactionState()処理するためのメソッドをbrokerコールバック要求のタイミングをし、checkTransactionStateトランザクション、最後の呼び出し続行するか、ロールバックするかどうかを決定するために私たちのビジネスの方法を設定することを決定呼び出してendTransactionOneway行うためbroker、最終的なステータス更新メッセージを。

ボブの口座残高が減少している場合次に、例に戻る転送、およびメッセージが正常に送信されてきた、スミスはこのメッセージを終わら消費し始めた、この時間は、消費と消費者問題は残業の考え方の問題であるに対処するために失敗し2回のタイムアウトが存在します消費者側がメッセージを消費し、メッセージの全体のプロセスの可能な重複が表示されますまで、それは解決するために、以前の考え方によると、再試行されました。

消費生活ニュース

だから、基本的には、消費者側のタイムアウトの問題を解決することができますが、どのように消費者が失敗した場合に行うには?人工の解決:アリは、私たちのソリューションが提供されます。私たちは、取引の過程に合わせて、検討することができ、加えてお金がスミスに障害が発生した何らかの理由で、あなたは全体のプロセスをロールバックする必要があります。メッセージシステムがこれを達成するためのプロセスをロールバックする場合は、システムが大幅に複雑さを強化する、それが起こりやすいバグは、バグが失敗の確率よりもはるかに大きくなり、消費者の出現確率を推定しています。情報システムの設計と実装では、我々はそのような発生確率を解決するために、このような高い価格を過ごすために価値があるかどうかを測定する必要があり、多くの時間を必要とし、これがこの問題の現在無しソリューションRocketMQの理由であることは困難な問題を解決するためのもの皆で非常に小さな問題であり、場所を考えます。

四、メッセージを送信するためにどのようにプロデューサー

Producer下に示すように、送信者の負荷分散を達成するために、特定のトピックの方法で、すべてのポーリングキュー、:

RocketMQは、最初のクライアントは、メッセージの送信元を送信し分析します:

// 构造Producer
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
// 初始化Producer,整个应用生命周期内,只需要初始化1次
producer.start();
// 构造Message
Message msg = new Message("TopicTest1",// topic
                        "TagA",// tag:给消息打标签,用于区分一类消息,可为null
                        "OrderID188",// key:自定义Key,可以用于去重,可为null
                        ("Hello MetaQ").getBytes());// body:消息内容
// 发送消息并返回结果
SendResult sendResult = producer.send(msg);
// 清理资源,关闭网络连接,注销自己
producer.shutdown();

アプリケーションライフサイクル全体を通じて、生産者は初期化するには、startメソッドを呼び出す必要があり、初期の主なタスクは以下のとおりです。

  1. あなたが指定しない場合はnamesrvアドレスを、アドレスが自動的になります
  2. スケジュールされたタスクの開始:更新namesrvアドレス、トピックnamsrvからルーティング情報を更新、クリーンアップ、既にブローカーがハングアップし、すべてのブローカーにハートビートを送信...
  3. スタート負荷分散サービス

初期化の後、メッセージの送信を開始する次のように、メインコード送信されたメッセージです。

private SendResult sendDefaultImpl(Message msg,......) {
    // 检查Producer的状态是否是RUNNING
    this.makeSureStateOK();
    // 检查msg是否合法:是否为null、topic,body是否为空、body是否超长
    Validators.checkMessage(msg, this.defaultMQProducer);
    // 获取topic路由信息
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    // 从路由信息中选择一个消息队列
    MessageQueue mq = topicPublishInfo.selectOneMessageQueue(lastBrokerName);
    // 将消息发送到该队列上去
    sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, timeout);
}

コードの二つの方法は、注意が必要ですtryToFindTopicPublishInfoし、selectOneMessageQueue初期のプロデューサーで述べたように、ローカルキャッシュに情報や更新情報をルーティングするためのスケジュールされたタスクを開始します、tryToFindTopicPublishInfo応答が受信されない場合、それは自身の行く、キャッシュからのルーティング情報を取得するために最初に話題になりnamesrv、ルーティング情報を。selectOneMessageQueue負荷分散の目的を達成するためにポーリング方式、戻り待ち行列、。

プロデューサーは、メッセージの送信に失敗した場合は、自動的に、再試行戦略を再試行します。

  1. 再試行<retryTimesWhenSendFailed(設定可能)
  2. 処理された合計(含むリトライn回の処理)<(メッセージを送信するときにパラメータが渡さ)sendMsgTimeout
  3. 上記の2つの条件を満足すると同時に、プロデューサーは別のキューにメッセージを送信することを選択します

===============

取引メッセージについて、転載する他のソリューションがあります別の記事は

 

、お金を追加するために、2つの異なるDBに分布する2つのアカウント、または内部に二つの異なるサブシステム、お金を控除するA、B:分散トランザクションといえば、古典的な「口座移転」問題について話しますアトミック性を確保するためにどのように?

一般的なアイデアはミドルウェア「結果整合性」メッセージングによって実装されています:お金を控除する制度を、その後、時計仕掛けのメッセージは、より多くのお金のためのミドルウェア、Bシステムにこのメッセージを受け取りました。

しかし、内部の問題があります:Aは、メッセージを送信した後、最初の更新DBのですか?これは、最初の更新DBの後にメッセージを送るのでしょうか?

、という最初の更新DBの成功を仮定すると、ネットワーク障害へのメッセージを送信し、再送信してくださいどのように、失敗? 
最初の成功は、メッセージ、更新DBの失敗を送信すると仮定すると。メッセージが送信されてきた、と彼らは撤退することはできません、どのように?

だから、ここの結論です:ちょうど送ったメッセージや更新DBこれら2つの操作が関係なく、誰がすべきの、アトミックではありません、誰が問題となっています。

そして、どのようにこの問題を解決するには?

間違ったプログラム0

一部の人々は、障害がメッセージ、更新DB自動ロールバックを送信する場合、私は、トランザクションに内側にこのネットワーク呼び出しと更新DB「メッセージを送る」ことができ、と思うことがあります。これはまだ2つのアトミック動作を保証するものではありませんか?

プログラムは、実際には、右のようだ間違っている、2つの理由があります。

(1)2、一般的なネットワークの問題:メッセージを送信するために失敗し、送信者が知っているメッセージングミドルウェアは、実際にメッセージは表示されませんされていないのですか?またはメッセージは単に失敗する応答時間を返し、受信されましたか?

メッセージを受信した場合は、送信者が考える、ロールバック操作の更新デシベルのを受信しません。Aは、追加のお金のBアカウントを控除されていないアカウントのお金になります。

(2)長いトランザクションDBをもたらすため、ネットワークの遅延であってもよいDBトランザクションのネットワーク呼。深刻なは、全体のDBをブロックします。これは非常に危険です。

上記の分析に基づいて、我々は、このプログラムが実際に間違っていることを知っています!

スキーム1 - 自分のビジネスを達成する側

ミドルウェアは、「取引メッセージ」機能を提供していない例えば、あなたはカフカを使用しているメッセージングと仮定します。どのようにしてこの問題を解決するには?

:解決策は以下の通りである。 
これらの二つの操作、トランザクションDBプロデューサメッセージテーブル端を調製し、インサートメッセージ更新DB(1)。

(2)デーモン、テーブルのミドルウェアメッセージングのメッセージ転送メッセージの安定したストリームを調製しました。失敗し、再試行、再送信を続けます。それは順序が乱れることはないだろう、繰り返しメッセージを許可しますが、メッセージは失われません。

重いテーブルを作成する文章の(3)消費者エンド。処理されたメッセージ、判定テーブルの内部に再記録。ビジネスのと上のようにパワーを実現。しかし、ここでまた、原子質問が含まれます。メッセージは、これら2つの操作の重いテーブル原子を言い渡されている消費者+挿入メッセージを確実にするためですか?

消費者の成功が、重いテーブルを宣告挿入するにはどのように行う、失敗しましたか?この上で、カフカのソースコード解析シリーズで、最初のものは、正確に質問たら、議論がなされています。

上記の3つのステップによって、我々はアトミック更新デシベルの送信ネットワークメッセージの基本的な問題と、本明細書にこれらの2つの操作を解決しました。

DBのメッセージテーブルを設計する必要はなく、常にローカルニュースをスキャンし、バックグラウンドタスクが必要になります。しかし、この解決策の欠点はこれです。メッセージトラフィックの負荷側に接続された付加的な処理とビジネスロジックを引き起こします。

プログラム2 - RocketMQトランザクションメッセージ

この問題を解決するために、カップリングおよびビジネスずに、RocketMQは「取引メッセージ」の概念がある前方に置きます。

具体的には、2つの段階にメッセージを送信している:位相及び検証フェーズを準備します。

:具体的には、2つのステップ上に、3つのステップに分解される 
(1)を調製したメッセージ送信 
(2)更新DB 
更新DB成否結果、確認メッセージまたは取り消し準備(3)。

いくつかは、最後のステップは、実行する方法を失敗し、最初の2つのステップの実装を成功さを求めることができますか?ここRocketMQに関連しているキーポイント:RocketMQは、定期的にメッセージが送信され、最後にこれを確認するために、送信者に尋ねる、すべてのスキャンのメッセージを準備します(デフォルトは1分ですか)?このエントリまたはメッセージをキャンセル?

それはcheckListenerの定義である、RocketMQは、上記のプログラムを達成するために、リスナーをコールバックします。

概要:オプション2とオプション1の比較、RocketMQ最大の変化、実際には、「メッセージテーブルをスキャンし、」この事、ビジネスの側面をさせますが、メッセージングミドルウェアを行うには助けません。

メッセージテーブルに関しては、それはまだ保存されていません。メッセージングミドルウェアは、送信者に依頼するので、彼らは成功したものかどうか、物事の実行状態を記録するために、「ローカルニュースシートの変装フォーム」を必要とします。

人間の介入

一部の人々は関係なく、オプション1、オプション2のまたは送信メッセージが正常にキューに終止符を打つ、言わなければならないかもしれないが、消費者の最終消費者の障害がどのように行うには?

消費者は、再試行、失敗した、また、どのように行うには失敗してきましたか?それは自動的にプロセス全体をロールバックすることではないでしょうか?

答えは手動の介入です。エンジニアリングの実践の観点から、このプロセス全体の自動ロールバック・コストは複雑達成するだけでなく、新たな問題を紹介するだけでなく、膨大です。例えば、自動ロールバックは失敗し、どのように対処しますか?

例は非常に低い確率にこれが対応し、非常に複雑な自動化されたシステムのロールバック、より信頼性の高い、かつ容易に実現するよりも、手動処理を取ります。

 

公開された72元の記事 ウォン称賛7 ビュー10000 +

おすすめ

転載: blog.csdn.net/qq_39399966/article/details/103382111