web-dev-qa-db-ja.com

Spring AMQPv1.4.2-ネットワーク障害時のウサギの再接続の問題

Spring AMQP v1.4.2で次のシナリオをテストしていますが、ネットワークが中断した後、再接続に失敗します。

  1. Rabbit:listener-containerとrabbit:connection-factoryを使用してメッセージを非同期に消費するSpringアプリケーションを起動します(詳細な構成は次のとおりです)。
  2. ログは、アプリケーションがメッセージを正常に受信していることを示しています。
  3. RabbitMQをアプリから見えないようにするには、Rabitサーバーでインバウンドネットワークトラフィックをドロップします:_Sudo iptables -A INPUT -p tcp --destination-port 5672 -j DROP_
  4. 少なくとも3分待ちます(ネットワーク接続がタイムアウトするまで)。
  5. 次の接続を修正します:_Sudo iptables -D INPUT -p tcp --destination-port 5672 -j DROP_
  6. しばらく待つと(1時間以上試しても)、再接続は行われません。
  7. アプリケーションを再起動すると、メッセージの受信が再開されます。これは、ネットワークが正常に戻ったことを意味します。

また、同じシナリオをVMネットワークアダプターがiptablesドロップの代わりに切断し、同じことが起こります。つまり、自動再接続がありません。興味深いことに、iptablesを試してみると [〜#〜]拒否[〜#〜]、DROPの代わりに、期待どおりに機能し、拒否ルールを削除するとすぐにアプリが再起動しますが、拒否はネットワークというよりもサーバー障害のようなものだと思います失敗。

参照ドキュメント によると:

ビジネス例外が原因でMessageListenerが失敗した場合、例外はメッセージリスナーコンテナによって処理され、別のメッセージのリッスンに戻ります。障害の原因が接続の切断(ビジネス例外ではない)である場合は、リスナーのメッセージを収集しているコンシューマーをキャンセルして再起動する必要があります。 SimpleMessageListenerContainerはこれをシームレスに処理し、リスナーが再起動されていることを示すログを残します。実際、コンシューマーを再起動しようとすると無限にループし、消費者が実際に非常に悪い行動をとった場合にのみ、それはあきらめます。副作用の1つは、コンテナーの開始時にブローカーがダウンしている場合、接続が確立されるまでブローカーが試行を続けることです。

これは、切断後約1分で取得するログです。

_    2015-01-16 14:00:42,433 WARN  [SimpleAsyncTaskExecutor-5] org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer Consumer raised exception, processing can restart if the connection factory supports it
com.rabbitmq.client.ShutdownSignalException: connection error
    at com.rabbitmq.client.impl.AMQConnection.startShutdown(AMQConnection.Java:717) ~[amqp-client-3.4.2.jar:na]
    at com.rabbitmq.client.impl.AMQConnection.shutdown(AMQConnection.Java:707) ~[amqp-client-3.4.2.jar:na]
    at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.Java:565) ~[amqp-client-3.4.2.jar:na]
    at Java.lang.Thread.run(Thread.Java:745) [na:1.7.0_55]
Caused by: Java.io.EOFException: null
    at Java.io.DataInputStream.readUnsignedByte(DataInputStream.Java:290) ~[na:1.7.0_55]
    at com.rabbitmq.client.impl.Frame.readFrom(Frame.Java:95) ~[amqp-client-3.4.2.jar:na]
    at com.rabbitmq.client.impl.SocketFrameHandler.readFrame(SocketFrameHandler.Java:139) ~[amqp-client-3.4.2.jar:na]
    at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.Java:534) ~[amqp-client-3.4.2.jar:na]
    ... 1 common frames omitted
_

そして、再接続の数秒後にこのログメッセージが表示されます。

_2015-01-16 14:18:14,551 WARN  [SimpleAsyncTaskExecutor-2] org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer Consumer raised exception, processing can restart if the connection factory supports it. Exception summary: org.springframework.amqp.AmqpConnectException: Java.net.ConnectException: Connection timed out
_

UPDATE:非常に奇妙なことに、org.springframework.amqpパッケージでDEBUGロギングを有効にすると、再接続が正常に行われ、問題を再現できなくなります。

デバッグログを有効にせずに、SpringAMQPコードをデバッグしようとしました。 iptablesドロップが削除された直後に、SimpleMessageListenerContainer.doStop()メソッドが呼び出され、shutdown()が呼び出され、すべてのチャネルがキャンセルされることがわかりました。原因に関連していると思われるdoStop()にブレークポイントを設定すると、このログメッセージも表示されます。

_2015-01-20 15:28:44,200 ERROR [pool-1-thread-16] org.springframework.amqp.rabbit.connection.CachingConnectionFactory Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=405, reply-text=RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'e4288669-2422-40e6-a2ee-b99542509273' in vhost '/', class-id=50, method-id=10)
2015-01-20 15:28:44,243 WARN  [SimpleAsyncTaskExecutor-3] org.springframework.amqp.rabbit.listener.BlockingQueueConsumer Failed to declare queue:e4288669-2422-40e6-a2ee-b99542509273
2015-01-20 15:28:44,243 WARN  [SimpleAsyncTaskExecutor-3] org.springframework.amqp.rabbit.listener.BlockingQueueConsumer Queue declaration failed; retries left=0
org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[e4288669-2422-40e6-a2ee-b99542509273]
    at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.Java:486) ~[spring-rabbit-1.4.2.RELEASE.jar:na]
    at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.Java:401) ~[spring-rabbit-1.4.2.RELEASE.jar:na]
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.Java:1022) [spring-rabbit-1.4.2.RELEASE.jar:na]
    at Java.lang.Thread.run(Thread.Java:745) [na:1.7.0_55]
2015-01-20 15:28:49,245 ERROR [pool-1-thread-16] org.springframework.amqp.rabbit.connection.CachingConnectionFactory Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=405, reply-text=RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'e4288669-2422-40e6-a2ee-b99542509273' in vhost '/', class-id=50, method-id=10)
2015-01-20 15:28:49,283 WARN  [SimpleAsyncTaskExecutor-3] org.springframework.amqp.rabbit.listener.BlockingQueueConsumer Failed to declare queue:e4288669-2422-40e6-a2ee-b99542509273
2015-01-20 15:28:49,300 ERROR [SimpleAsyncTaskExecutor-3] org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer Consumer received fatal exception on startup
org.springframework.amqp.rabbit.listener.QueuesNotAvailableException: Cannot prepare queue for listener. Either the queue doesn't exist or the broker will not allow us to use it.
    at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.Java:429) ~[spring-rabbit-1.4.2.RELEASE.jar:na]
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.Java:1022) ~[spring-rabbit-1.4.2.RELEASE.jar:na]
    at Java.lang.Thread.run(Thread.Java:745) [na:1.7.0_55]
Caused by: org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[e4288669-2422-40e6-a2ee-b99542509273]
    at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.Java:486) ~[spring-rabbit-1.4.2.RELEASE.jar:na]
    at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.Java:401) ~[spring-rabbit-1.4.2.RELEASE.jar:na]
    ... 2 common frames omitted
2015-01-20 15:28:49,301 ERROR [SimpleAsyncTaskExecutor-3] org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer Stopping container from aborted consumer
_

UPDATE 2:回答で示唆されているように、_requested-heartbeat_を30秒に設定した後、再接続はほとんどの場合機能し、再定義に成功しましたファンアウト交換にバインドされた排他的な一時キューですが、それでも時々再接続に失敗します。

まれに、テスト中にRabbitMQ管理コンソールを監視し、(古い接続がタイムアウトによって削除された後)新しい接続が確立されたが、再接続後に排他的一時キューが再定義されなかったことを確認しました。また、クライアントはメッセージを受信して​​いませんでした。発生頻度が少ないため、問題を確実に再現することは現在非常に困難です。以下に完全な構成を提供しました。これには、キュー宣言が含まれています。

UPDATE 3:排他的一時キューを名前付き自動削除キューに置き換えた後でも、同じ動作がときどき発生します。つまり、名前付きキューの自動削除は再接続後に再定義されず、アプリケーションが再起動されるまでメッセージは受信されません。

誰かがこれについて私を助けてくれるなら、私は本当にたくさん感謝します。

これが私が頼っている春のAMQP構成です:

_<!-- Create a temporary exclusive queue to subscribe to the control exchange -->
<rabbit:queue id="control-queue"/>

<!-- Bind the temporary queue to the control exchange -->
<rabbit:fanout-exchange name="control">
    <rabbit:bindings>
        <rabbit:binding queue="control-queue"/>
    </rabbit:bindings>
</rabbit:fanout-exchange>

<!-- Subscribe to the temporary queue -->
<rabbit:listener-container connection-factory="connection-factory"
                           acknowledge="none"
                           concurrency="1"
                           prefetch="1">
    <rabbit:listener queues="control-queue" ref="controlQueueConsumer"/>

</rabbit:listener-container>

<rabbit:connection-factory id="connection-factory"
                           username="${rabbit.username}"
                           password="${rabbit.password}"
                           Host="${rabbit.Host}"
                           virtual-Host="${rabbit.virtualhost}"
                           publisher-confirms="true" 
                           channel-cache-size="100"
                           requested-heartbeat="30" />

<rabbit:admin id="admin" connection-factory="connection-factory"/>

<rabbit:queue id="qu0-id" name="qu0">
    <rabbit:queue-arguments>
        <entry key="x-dead-letter-exchange" value="dead-letter"/>
    </rabbit:queue-arguments>
</rabbit:queue>

<rabbit:topic-exchange id="default-exchange" name="default-ex" declared-by="admin">
    <rabbit:bindings>
        <rabbit:binding queue="qu0" pattern="p.0"/>
    </rabbit:bindings>
</rabbit:topic-exchange>

<rabbit:listener-container connection-factory="connection-factory"
                           acknowledge="manual"
                           concurrency="4"
                           prefetch="30">
    <rabbit:listener queues="qu0" ref="queueConsumerComponent"/>
</rabbit:listener-container>
_
13
Amir Moghimi

説明したようにテストを実行しました(Linuxでiptablesを使用してパケットをドロップするウサギ)。

接続が再確立されたときにログはありません(おそらく必要です)。

再接続を確認するには、デバッグログをオンにすることをお勧めします。

編集:

Rabbitmqドキュメントから:

排他的排他的キューは、現在の接続によってのみアクセスでき、その接続が閉じると削除されます。他の接続による排他キューのパッシブ宣言は許可されていません。

あなたの例外から:

reply-code = 405、reply-text = RESOURCE_LOCKED-vhost '/'、class-id = 50、method-のロックされたキュー 'e4288669-2422-40e6-a2ee-b99542509273'への排他的アクセスを取得できません

したがって、問題は、ブローカーがまだ他の接続が存在すると考えていることです。

  1. 排他キューを使用しないでください(とにかくそのようなキューを持つメッセージは失われます)。または、
  2. ブローカーが失われた接続をより速く検出できるように、低いrequestedHeartbeatを設定します。
5
Gary Russell

実稼働環境でもこの問題に直面しています。これは、Rabbitノードが異なるESXラックなどでVMとして実行されていることが原因である可能性があります。見つかった回避策は、クライアントアプリがクラスターから切断された場合に、継続的に再接続を試行することでした。 。以下は、適用した設定と機能した設定です。

<util:properties id="spring.amqp.global.properties">
  <prop key="smlc.missing.queues.fatal">false</prop>
</util:properties>

この属性は、致命的なエラー(ブローカーが利用できないなど)でキューの宣言が失敗した場合のSpringAMQPのグローバルな動作を変更します。デフォルトでは、コンテナは3回だけ試行します(「retriesleft = 0」を示すログメッセージを参照)。

参照: http://docs.spring.io/spring-amqp/reference/htmlsingle/#containerAttributes

さらに、コンテナが致命的でないエラーから回復するように、recovery-intervalを追加しました。ただし、グローバルな動作が致命的なエラー(キューの欠落など)を再試行する場合にも、同じ構成が使用されます。

<rabbit:listener-container recovery-interval="15000" connection-factory="consumerConnectionFactory">
....
</rabbit:listener-container>
3
Shreerang

setRequestedHeartBeatConnectionFactoryに設定し、setMissingQueuesFatal(false)SimpleMessageListenerContainerに設定して、無期限に接続を再試行します。デフォルトでは、SimpleMessageListenerContainer setMissingQueuesFatalはtrueに設定されており、3回の再試行のみが実行されます。

  @Bean
  public ConnectionFactory connectionFactory() {
    final CachingConnectionFactory connectionFactory = new CachingConnectionFactory(getHost(), getPort());
    connectionFactory.setUsername(getUsername());
    connectionFactory.setPassword(getPassword());
    connectionFactory.setVirtualHost(getVirtualHost());
    connectionFactory.setRequestedHeartBeat(30);
    return connectionFactory;
  }

  @Bean
  public SimpleMessageListenerContainer listenerContainerCopernicusErrorQueue() {
    final SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory());
    container.setQueueNames(myQueue().getName());
    container.setMessageListener(messageListenerAdapterQueue());
    container.setDefaultRequeueRejected(false);
    container.setMissingQueuesFatal(false);
    return container;
  }
1
L. G.