マスタへのエントリからのRabbitMQ(二)

1.どのように成功したメッセージ配信が百パーセントを保証しますか?

生産の送出端部の信頼性とは何ですか?

  • 送信されたメッセージの成功を確認してください
  • MQノードが正常に保護を受けます
  • MQの送信者は、ノード(ブローカー)承認を受け、
  • メッセージのための補償機構を改善

あなたがメッセージ配信百パーセントの成功を保証したい場合は、最初の3つのステップが必ずしも保証されません。いくつかの時間または確認応答を返すときにメッセージを配信することにより、ネットワークグリッチに、失敗した、または生産面でニュースを配信することを、MQはまた、MQを受けている可能性など生産面など、いくつかの極端な場合には、生産につながったことを受けていません答えは、この時点では、メッセージ配信の成功または失敗を知らないので、そのような場合のために、我々はいくつかの補償機構を行う必要があります。

 

1.1オプション1:落下のメッセージは、メッセージの状態がマーキング

 

  1. その後、メッセージを送信するために、このような私たちの注文フォームなどのデータの保存、すべての業務データ・ウェアハウスの最初の、注文情報、およびメッセージを生成し、メッセージもストレージに置くことができ、このメッセージは、メッセージの状態が含まれている必要があり作成した(CREATE_DATE属性)、および0などの初期兆候を設定し、それに送信され、メッセージが正常に作成されていることを示し
  2. まず、最初のステップメッセージの成功を確実にするためには、すべての例外なしに、保存され、その後、生産側と、メッセージを送信します。あなたは、迅速な故障メカニズムに失敗した場合
  3. 結果MQメッセージは、応答受信(confirm)生産終了までを
  4. 端子を製造Confirm Listener、監視する非同期Broker、返送応答メッセージが正常に配信されているかどうかを決定するために、成功した場合、メッセージは、データベースを照会するために、ステータスメッセージを更新し、成功した配信を示すメッセージが
     
    OK第2工程を想定し、第一とき3エコー応答、ネットワークが突然このニュース答えを確認する受信しない、それがこのニュースのステータスが0となっているあるんリスナーの生産面で、その結果、フラッシュが登場

  5. この時点で、我々は、そのようなメッセージとして、ルールを設定する必要があり、それを抽出する必要があるメッセージのステータス場合、蓄積時間内のしきい値タイムアウトを設定し、5分または0。私たちは、定期的なタスクを配布され、ここで使用し、DBを取得するタイミングは、ゼロからのメッセージに5分以上とステータスメッセージを作成しました。
  6. ニュースの再配達の外にクロールする(Retry Send)、つまり、彼は第二段階から下るし続けるようになりました
  7. もちろん、いくつかのメッセージは、設定されていないroutingKeyとして、ブローカーにルーティングすることはできませんいくつかの実用的な問題の原因である可能性があります、対応するキューが誤って削除され、正常に配信することができない、まだ数回再試行した場合でも、このメッセージは、それが再試行の回数で行われる必要があります配信数が3倍よりも大きい場合にはそのような制限のような制限は、3回、メッセージの状態は、最終的な配信障害メッセージことを示す、2に更新されます。

 

それに対する補償を行うにはどのようにこのような状況のビューでは、メッセージの究極の失敗を照会する補償システムを持って、その後、失敗の理由を与えることができ、もちろん、これらを手動で操作する必要があるかもしれません。

非常に同時シナリオで最初の配信の信頼性、適合性?

最初のシナリオでは、並行性の高いシナリオでは、二回明らかにデータベースのパフォーマンスのボトルネックを、データベースの永続操作を行う必要があります。実際には、それだけでストレージにおける当社のコアビジネスのリンクにする必要があり、メッセージがストレージに置く必要がなく、私たちはメッセージの配信を遅らせないことができ、第2の確認コールバック検査を行います。

もちろん、この方式は必ずしも百パーセント成功した配信を保証するものではありませんが、基本的には、メッセージの約99.9%がOKであることを保証することができ、いくつかの特に極端な場合にのみ、手動でそれを行うには補償することができる、または使用スケジュールされたタスクを行うことができます。

 

1.2オプションII:メッセージの遅延配信は、コールバックをチェックし、第2の送達確認を行います

 

Upstream Service上流のサービスは、生産面でDownstream serviceは、サービスの下流端が消費されCallback service、コールバックサービスです。

 

  1. [最初のストレージサービスメッセージ、およびメッセージの生産終了を送信します
  2. 再びメッセージ送信側生産続くメッセージ、送信した後(Second Send Delay Check)、すなわち、遅延メッセージ配信検査を、例えば5分後の送達のためのように、遅延時間を設定することが必要。
  3. 消費者側が指定したキューで待機するように、メッセージを処理するために受信されます。
  4. プロセスが完了した後、送信confirmメッセージを、応答が返送されるが、これはACKに正常な応答ではなく、MQに配信されたメッセージを再生成します。
  5. 上記のCallback service別のサービスである、実際には、それは、MQにより送信された下りのサービスを監視することで、メッセージ最初のシナリオ、保存DBの役割を果たしているconfirm場合は、メッセージをCallback service受け取っconfirmたメッセージが永続ストレージを行うメッセージが、その後、メッセージはおよそですDBに永続化。
  6. 伝送遅延への5分MQメッセージの後、およびCallback service遅延の後または任意の処理を行う必要がない場合は何も消費や故障がない場合、があるかどうかを確認するために、受信したメッセージを確認するメッセージDBに対応するメッセージキューを聞きます、その後、Callback serviceあなたは、私はこのメッセージを見つけることができませんでしたチェックを遅らせるためにそれを教えて、上流のRPCサービスへの通信を開始する必要がある、あなたは、生産側が再クエリサービスメッセージになり、その後、メッセージを送信した後、情報を受信、再送信する必要があります。

目標は、並行性の高いシナリオでDBのストレージで最も懸念を行うには小さいメッセージは100パーセントの成功を配信されていないが、我々は、並行性のような大規模な量に抵抗するのは難しい保証性能を、確認する必要があります。データベース操作が補償非同期でも保存することを可能な限り保存することが可能です。

 

実際には、コールバックサービスのない主な流れが存在しない、それはサービスの報酬に属し、コア全体のリンクは生産終了ストレージビジネスニュースで、MQ、キュー・モニター・消費者側、消費者のニュースにメッセージを送信します。他のステップは、補償機構です。

2番目のオプションは、インターネットの巨人は、より古典的かつ主流のソリューションです。それほど高くない性能要件場合でも、最初のプログラムは、より単純であることを

 

2.冪等

2.1どのようなセックスように力であると?

開始した同じ操作のユーザまたは複数の要求のための要求の結果が同じであるだけです。

ここでは、例を与えるために、データベースの楽観的ロック機構から学ぶことができます。

  • まず、テーブルのバージョンのバージョンフィールドを追加します

  • 更新操作する前に、データベースクエリ、このバージョンに移動します

  • その後など、条件としてバージョンへの更新ステートメントを実行します。

    UPDATEは、設定されたカウント=カウント-1、VERSION = VERSION + 1 VERSION = 1 T_REPS

  • あなたは、この条件は施行しません、更新を実行する場合は、このフォームでデータを更新するために他の人がありますが、それは操作を実行しないであろう、と冪等にこの楽観的ロック機構を保護するために。

 

2.2メッセージ終了等べき保証

消費者問題を繰り返します。

生産面に戻りますが、生産面につながるため、ネットワーク停止にACKながら、消費者のニュースの消費量を完了すると、確認メッセージ、メッセージを再送信します記事や消費者支出を受けなかったが、実際に消費者が正常に消費しました消費問題を繰り返しているニュース記事、。

 

唯一ID +指紋コード機構2.2.1

ユニークID:などの商品IDなど、ビジネスのユニークなプライマリキーテーブル、

:指紋コードの各通常動作コード、各操作で生成されたフィンガープリントコードを区別するために、+サービスIDは、(トラフィックがシーンに応じて)タイムスタンプまたはフラグとすることができます

 

  • ID +指紋コードのユニークなメカニズム、主キーは再利用のためのデータベース
  • T_ORDER FROM SELECT COUNT(1)ここで、ID =一意のフィンガープリントコードIDとIS_CONSUM =
  • 利点:シンプル
  • 短所:パフォーマンスのボトルネックのデータベース書き込みは、高い同時実行の下にあります。
  • 溶液:サブテーブルIDに基づいて、ライブラリを分割するルーティング・アルゴリズム

全体的なアイデアは、我々はメッセージに基づいてグローバルに一意なIDを生成する必要があり、その後も指紋のコードを追加する必要があり、すべての最初のです。それは持っていない指紋コードは、システムによって生成されるが、スプライシングへのビジネスルールのいくつかの外部または内部のルールは、その目的は、この操作は絶対にユニークで保護することです。

データベース主キー値としてスプライスされたID +指紋コードは、重複排除を行うことができます。つまり、消費する前に、彼らはの制御を持っていないのに代わってそこに費やされてきた場合、それは、挿入操作を現在行っていないというメッセージデータベースのクエリ指紋識別コードに行くのメッセージです。

 

2.2.2原子性を達成するためのRedisを使って

ここでは唯一の問題MQ冪等リピート消費を解決するために、原子のRedisと述べました

注意:MQの冪等の問題が適切に受信されていないACKの生産終了にあり、それは、ネットワークの停止で、その結果、ネットワークジッタかもしれ

 

私の計画:

消費者IDの消費者側はID、生産メッセージング、メッセージの送信か否かを取らない場合Redisのビットマップに対応する位置から、生産の各端部のためのMQ生産データ、Redisのビットマップに入れ始めるMQ。

しかし、誰かが消費者側場合、生産側Redisのコマンドは、場合、それには可能性が浮上しているものの、リピート消費が異常Redisのコマンドの実行が極めて低い登場し、実行する方法を失敗しますが、と言うかもしれませんか?

OK、我々はRedisのコマンドでは、処理のためのこの非常に特別なメッセージ上のライブラリオフメッセージ、毎日タイマーを、失敗することができます。

 

3.確認メカニズム

3.1どのように理解するには?

  • ブローカがメッセージを受信した場合、確認メッセージは、メッセージの配信後にプロデューサーを意味し、それは私たちに返信プロデューサーを与えます
  • プロデューサがメッセージをブローカに正常に送信されているかどうかを決定するために、応答を受信し、この方法は、メッセージ配信の信頼性であります

コア保護

 

フローチャート機構を確認

生産メッセージ、エコー応答、生産受信側ブローカは、ブローカにメッセージを送信Confirm Listenerスヌープ応答に、当然のことながら、この動作は非同期で、生産側は、制御できないメッセージを送信するように内部モニタブローカーは、私たちの応答に耳を傾けることです。

 

3.2どのように達成するために?

  • 最初のステップは、チャネル上の確認モードを開きます。channel.confirmSelect()
  • 2.チャネルをリッスンして追加しますaddConfirmListener、リスニングの成功と失敗の結果を返し、特定の結果に応じてメッセージを再送信、その後の処理、などログ!
public class Producer {
    public static void main(String[] args) throws Exception {
        
        //创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setHandshakeTimeout(20000);

        
        //获取Connection
        Connection connection = connectionFactory.newConnection();
        
        //通过connection创建一个新的Channel
        Channel channel = connection.createChannel();
        
        //指定我们的消息投递模式
        channel.confirmSelect();
        
        String exchangeName = "test_confirm_exchange";
        String routingkey = "confirm.save";
        
        //发送一条信息
        String msg = "Hello RabbitMQ Send confirm message!";
        channel.basicPublish(exchangeName, routingkey, null, msg.getBytes());
        
        //添加一个确认监听
        channel.addConfirmListener(new ConfirmListener() {
            
            @Override
            public void handleNack(long deliveryTag, boolean multiple)
                    throws IOException {
                System.out.println("-------no ack!---------");
            }
            
            @Override
            public void handleAck(long deliveryTag, boolean multiple)
                    throws IOException {
                System.out.println("--------ack!----------");
            }
        });
    }
}

 

public class Consumer {
    public static void main(String[] args) throws Exception{
        //创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setHandshakeTimeout(20000);

        
        //获取Connection
        Connection connection = connectionFactory.newConnection();
        
        //通过connection创建一个新的Channel
        Channel channel = connection.createChannel();
        
        String exchangeName = "test_confirm_exchange";
        String routingkey = "confirm.#";
        String queueName = "test_confirm_queue"; 
        
        //声明交换机和队列 然后进行绑定和 设置 最后制定路由key
        channel.exchangeDeclare(exchangeName, "topic",true);
        channel.queueDeclare(queueName, true, false, false, null);
        
        channel.queueBind(queueName, exchangeName, routingkey);
        
        //创建消费者
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
        channel.basicConsume(queueName, true,queueingConsumer); 
        
        while(true){
            Delivery delivery = queueingConsumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("消费端:" + msg);
        }
    }
}

 

取扱説明書

ます。http:// IP:まず、アクセス制御ユニットを消費者側を開始15672、検査し、ExchangeキューがOKに設定され、その後、生産面を開始し、メッセージが消費者の最終消費者、生産面であるだけでなく、ACK応答の成功を監視すること。

 

4.復帰機構

4.1どのように理解するには?

  • Return Listener いくつかの非ルーティング可能のメッセージを処理!
  • 私たちのニュースのプロデューサー、そして行くために特定のキューにメッセージを配信するためには、Exchange Routingkeyを指定することで、その後、私たちは、キュー消費者を聞く処理動作を消費!
  • 私たちはこのメッセージまで聞きする必要がない場合は、私たちがメッセージを送信する時間を持っている場合は、いくつかのケースでは、現在の為替が存在しないか、指定されたルートキーのルートよりも少ないしない、この時間は、使用する必要がありますReturn Listener

 

4.2どのように達成するために?

  1. 戻りリスナーを追加します。addReturnListener生産終了は、例えば、いくつかの後処理を行うために、これらの到達不能メッセージをリスンメッセージログを記録し、または適時に記録を追跡するために、それを再設定するような表情を可能です
  2. 发送消息时,设置Mandatory:如果为true,则监听器会接收到路由不可达的消息,然后进行后续处理,如果为false,那么broker端自动删除该消息!

 

public class ReturnProducer {
     public static void main(String[] args) throws Exception {
            //1 创建ConnectionFactory
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.244.11");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
            connectionFactory.setHandshakeTimeout(20000);
            //2 获取Connection
            Connection connection = connectionFactory.newConnection();
            //3 通过Connection创建一个新的Channel
            Channel channel = connection.createChannel();
            
            String exchange = "test_return_exchange";
            //String routingKey = "return.save";
            String routingKeyError = "abc.save";
            
            String msg = "Hello RabbitMQ Return Message";
            //添加return监听
            channel.addReturnListener(new ReturnListener() {
                @Override
                public void handleReturn(int replyCode, String replyText, String exchange,
                        String routingKey, BasicProperties properties, byte[] body) throws IOException {
                    //replyCode:响应码    replyText:响应信息
                    System.err.println("---------handle  return----------");
                    System.err.println("replyCode: " + replyCode);
                    System.err.println("replyText: " + replyText);
                    System.err.println("exchange: " + exchange);
                    System.err.println("routingKey: " + routingKey);
                    //System.err.println("properties: " + properties);
                    System.err.println("body: " + new String(body));
                }

                
            });
            //5 发送一条消息,第三个参数mandatory:必须设置为true
            channel.basicPublish(exchange, routingKeyError, true, null, msg.getBytes());
        }
}

 

public class ReturnConsumer {
    
    public static void main(String[] args) throws Exception {
        //1 创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setHandshakeTimeout(20000);
        //2 获取Connection
        Connection connection = connectionFactory.newConnection();
        //3 通过Connection创建一个新的Channel
        Channel channel = connection.createChannel();
        
        String exchangeName = "test_return_exchange";
        String routingKey = "return.#";
        String queueName = "test_return_queue";
        //4 声明交换机和队列,然后进行绑定设置路由Key
        channel.exchangeDeclare(exchangeName, "topic", true, false, null);
        channel.queueDeclare(queueName, true, false, false, null);
        channel.queueBind(queueName, exchangeName, routingKey);
        
        //5 创建消费者 
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
        channel.basicConsume(queueName, true, queueingConsumer);
        
        while(true){
            Delivery delivery = queueingConsumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.err.println("消费者: " + msg);
        }
    }
}

 

运行说明

先启动消费端,访问管控台:http://ip:15672,检查Exchange和Queue是否设置OK,然后启动生产端。
由于生产端设置的是一个错误的路由key,所以消费端没有任何打印,而生产端打印了如下内容

如果我们将 Mandatory 属性设置为false,对于不可达的消息会被Broker直接删除,那么生产端就不会进行任何打印了。如果我们的路由key设置为正确的,那么消费端能够正确消费,生产端也不会进行任何打印。

おすすめ

転載: www.cnblogs.com/dwlovelife/p/10991371.html