No available channels 异常
项目配置如下:
spring:
rabbitmq:
host: uck1
port: 5672
username: xxx
password: xxxx
publisher-confirms: true
publisher-returns: true
listener:
direct:
retry:
max-attempts: 3
enabled: true
acknowledge-mode: manual
# 每个队列的消费者数量
consumers-per-queue: 5
prefetch: 10
type: direct
cache:
channel:
size: 500
checkout-timeout: 1000
connection:
mode: channel
#size: 2
再无其他特殊配置,但是部署到线上会报 No available channels 的异常
org.springframework.amqp.AmqpTimeoutException: No available channels
at org.springframework.amqp.rabbit.connection.CachingConnectionFactory.obtainPermits(CachingConnectionFactory.java:521)
at org.springframework.amqp.rabbit.connection.CachingConnectionFactory.getChannel(CachingConnectionFactory.java:487)
at org.springframework.amqp.rabbit.connection.CachingConnectionFactory.access$1600(CachingConnectionFactory.java:101)
at org.springframework.amqp.rabbit.connection.CachingConnectionFactory$ChannelCachingConnectionProxy.createChannel(CachingConnectionFactory.java:1333)
at org.springframework.amqp.rabbit.core.RabbitTemplate.doExecute(RabbitTemplate.java:2079)
at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:2047)
at org.springframework.amqp.rabbit.core.RabbitTemplate.send(RabbitTemplate.java:994)
at org.springframework.amqp.rabbit.core.RabbitTemplate.convertAndSend(RabbitTemplate.java:1060)
at org.springframework.amqp.rabbit.core.RabbitTemplate.convertAndSend(RabbitTemplate.java:1053)
at com.dkd.mq.PushProvider.sendOrdinaryMessage(PushProvider.java:83)
at com.dkd.RabbitmqDelayApplication.lambda$afterSingletonsInstantiated$0(RabbitmqDelayApplication.java:62)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
rabbitmq 无论是生产者还是消费者 都需要和 RabbitMQ Broker 建立连接,这个连接就是一条 TCP 连接,也就是 Connection. 一旦 TCP 连接建立起来,客户端紧接着可以创建一个 AMQP 信道(Channel),每个信道都会被指派一个唯一的 ID . 信道是建立在 Connection 之上的虚拟连接,RabbitMQ 处理的每条 AMQP 指令都是通过信道完成的。
我们完全可以使用 Connection 就能完成信道的工作,为什么还要引入信道呢?
试想这样一个场景,一个应用程序中有很多个线程需要从 RabbitMQ 中消费消息,或者生产消息,那么必然需要建立很多个 Connection,也就是多个 TCP 连接。
然而对于操作系统而言,建立和销毁 TCP 连接是非常昂贵的开销,如果遇到使用高峰,性能瓶颈也随之显现。
RabbitMQ 采用类似 NIO(Non-blocking I/O)的做法,选择 TCP 连接复用,不仅可以减少性能开销,同时也便于管理。
每个线程把持一个信道,所以信道复用了 Connection 的 TCP 连接。同时 RabbitMQ 可以确保每个线程的私密性,就像拥有独立的连接一样。当每个信道的流量不是很大时,复用单一的 Connection 可以在产生性能瓶颈的情况下有效地节省 TCP 连接资源。但是信道本身的流量很大时,这时候多个信道复用一个 Connection 就会产生性能瓶颈,进而使整体的流量被限制了。此时就需要开辟多个 Connection,将这些信道均摊到这些 Connection 中,至于这些相关的调优策略需要根据业务自身的实际情况进行调节。
上面内容转自博客: https://www.cnblogs.com/eleven24/p/10326718.html
由上面可以知道 我们整个应用只使用了一个连接connection,并在这个连接的基础上创建了500个channel,我们的消费者和生产者都共同使用这些channel来传输数据. 如果此时系统性的并发度不高的话,就是说这个时候500个channel没有同时在使用的话,系统是没有问题的,但是如果系统并发度高,需要同时传输大量的数据和消费大量的数据时,同时耗尽了500个channel,并且还需要创建channel 这个时候就是出现了 No available channels
的问题.
解决办法
- 措施1: 生产和消费使用不同的connection 扩大传输通路
rabbitTemplate.setUsePublisherConnection(true);
CachingConnectionFactory 在创建时就创建了两个工厂实例,
当设置这个值为true后,在创建连接时,程序会区分是发送还是消费来创建不同的连接,
这两个连接又分别缓存各自的channel
-
措施2: 增大channel的缓存size
这个在channel的缓存size 较小时是可以的,当channel的缓存size本身就较大时,更明智的办法不是继续增大size.
因为connection 就好比 一条高速公路,channel 就好比一辆辆货车.
货车太多会造成高速公路拥堵,造成数据传输效率降低和增加系统内存消耗. -
措施3: 减小部分消费者的并发度即consumers-per-queue: 5
有些队列的数据量较小,完全可以一个并发即单线程消费搞定,减少channel占用. -
措施4: 增大checkout-timeout
因为达到channel上限后即channel缓存池中channel耗尽,程序在申请channel 他不会立即报错,而是阻塞checkout-timeout时间,在这段时间内有谁及时归还了channel,这个时候也是会正常拿到channel的.