[英]Spring amqp consumer not re-connecting to queue after network failure
[英]Spring AMQP v1.4.2 - Rabbit reconnection issue on network failure
我正在測試Spring AMQP v1.4.2中的以下場景,並且在網絡中斷后無法重新連接:
sudo iptables -A INPUT -p tcp --destination-port 5672 -j DROP
服務器上的入站網絡流量,使RabbitMQ對應用程序不可見: sudo iptables -A INPUT -p tcp --destination-port 5672 -j DROP
sudo iptables -D INPUT -p tcp --destination-port 5672 -j DROP
我還測試了與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'的獨占訪問權限,方法 -
所以問題是經紀人仍然認為存在其他連接。
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.