簡體   English   中英

Spring AMQP v1.4.2 - 網絡故障時兔子重新連接問題

[英]Spring AMQP v1.4.2 - Rabbit reconnection issue on network failure

我正在測試Spring AMQP v1.4.2中的以下場景,並且在網絡中斷后無法重新連接:

  1. 啟動spring應用程序,使用rabbit:listener-container和rabbit:connection-factory(詳細配置如下)異步使用消息。
  2. 日志顯示應用程序正在成功接收消息。
  3. 通過刪除sudo iptables -A INPUT -p tcp --destination-port 5672 -j DROP服務器上的入站網絡流量,使RabbitMQ對應用程序不可見: 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. 等待一段時間(甚至嘗試了一個多小時)並且沒有重新連接。
  7. 重新啟動應用程序,它再次開始接收消息,這意味着網絡恢復正常。

我還測試了與VM網絡適配器斷開而不是iptables drop相同的情況,同樣的事情發生,即沒有自動重新連接。 有趣的是,當我嘗試iptables REJECT而不是DROP時,它按預期工作,一旦我刪除拒絕規則,應用程序就會重新啟動,但我認為拒絕更像是服務器故障而不是網絡故障。

根據參考文件

如果MessageListener因業務異常而失敗,則異常由消息偵聽器容器處理,然后返回偵聽另一條消息。 如果失敗是由連接斷開(不是業務異常)引起的,則必須取消並重新啟動正在為偵聽器收集消息的使用者。 SimpleMessageListenerContainer無縫地處理它,並留下一個日志來說明正在重新啟動監聽器。 事實上,它無休止地循環嘗試重新啟動消費者,並且只有當消費者表現得非常糟糕時才會放棄。 一個副作用是,如果代理在容器啟動時關閉,它將繼續嘗試直到可以建立連接。

這是斷開連接后大約一分鍾的日志:

    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

更新:非常奇怪的是,當我在org.springframework.amqp包上啟用DEBUG登錄時,重新連接成功發生,我再也無法重現這個問題了!

在沒有啟用調試日志記錄的情況下,我嘗試調試spring AMQP代碼。 我觀察到刪除iptables drop后不久,調用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

更新2:在將requested-heartbeat設置為30秒之后,如答案所示,重新連接大部分時間都有效,並成功重新定義了綁定到扇出交換的獨占臨時隊列,但它仍然無法偶爾重新連接。

在失敗的極少數情況下,我在測試期間監視RabbitMQ管理控制台並觀察到建立了新連接(在超時刪除舊連接之后)但重新連接后未重新定義獨占臨時隊列。 客戶端也沒有收到任何消息。 現在很難可靠地重現這個問題,因為它不常發生。 我已經提供了下面的完整配置,現在包含隊列聲明。

更新3:即使用自動刪除命名隊列替換獨占臨時隊列,偶爾也會出現相同的行為; 即重新連接后未重新定義自動刪除命名隊列,並且在重新啟動應用程序之前不會收到任何消息。

如果有人可以幫助我,我會非常感激。

這是我依賴的Spring 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>

我剛剛按照描述運行你的測試(使用iptables丟棄數據包的linux上的兔子)。

重新建立連接時沒有日志(也許我們應該)。

我建議你打開調試日志以查看重新連接。

編輯:

來自rabbitmq文檔:

exclusive獨占隊列只能由當前連接訪問,並在該連接關閉時刪除。 不允許通過其他連接被動聲明獨占隊列。

從你的例外:

reply-code = 405,reply-text = RESOURCE_LOCKED - 無法獲得對vhost'/',class-id = 50的鎖定隊列'e4288669-2422-40e6-a2ee-b99542509273'的獨占訪問權限,方法 -

所以問題是經紀人仍然認為存在其他連接。

  1. 不要使用獨占隊列(無論如何,您將丟失具有此類隊列的消息)。 要么,
  2. 設置低的requestedHeartbeat以便代理更快地檢測丟失的連接。

我們也在生產環境中面臨這個問題,可能是因為Rabbit節點在不同的ESX機架上作為虛擬機運行等。我們發現的解決方法是讓我們的客戶端應用程序不斷嘗試重新連接,如果它從集群斷開連接。 以下是我們應用的設置並且有效:

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

當聲明隊列因致命錯誤(代理不可用等)失敗時,此屬性會更改Spring AMQP的全局行為。 默認情況下,容器僅嘗試3次(請參閱顯示“retries left = 0”的日志消息)。

參考: http//docs.spring.io/spring-amqp/reference/htmlsingle/#containerAttributes

此外,我們添加了recovery-interval,以便容器從非致命錯誤中恢復。 但是,當全局行為也要重試致命錯誤(如缺少隊列)時,也會使用相同的配置。

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

setRequestedHeartBeat設置為ConnectionFactory並將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;
  }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM