使用工作队列方案

时间:2016-12-14 17:15:53

标签: java multithreading rabbitmq spring-amqp

基于此示例设置我的架构:

RabbitMQ - Work Queues

设定:

  • 工人一次收到一条消息
  • 每个工作人员下载文档,需要几秒钟
  • 一旦工作人员成功下载了文档,它就会确认消息
  • 如果工作人员无法下载文档,则无法重新排队(最多三次重试)

我正在调查我的实施中导致速度减慢的瓶颈。因为我使用noAck重新排队失败的工人。为了在我的工作线程中均匀地启用它,我已将预取设置为1.查看此问题:RabbitMQ work queue is blocking consumers - 他们看到了我在下面的屏幕截图中看到的内容:

acks / second

channels

为了确保一次只为工作人员分配一条消息,我需要将预取设置为1,但是其他人说这会导致工作人员按顺序而不是并行工作。

在通道级别,Running实际意味着什么?我看到队列和连接正常运行,但各个通道(每个线程一个)都是空闲的。

编辑#1:关于将连接池传递给RabbitMQ连接的说明看起来很有希望。 https://www.rabbitmq.com/api-guide.html#consumer-thread-pool我正在使用Spring AMQP,但我认为可以使用类似的方法:

     /**
     * Configure a large thread pool for concurrent channels on the physical Connection
     */
    @Bean
    public org.springframework.amqp.rabbit.connection.CachingConnectionFactory rabbitConnectionFactory() {
        logger.info("Configuring connection factory");
        CachingConnectionFactory cf = new CachingConnectionFactory();
        cf.setAddresses(this.rabbitMQProperties.getAddresses());
        cf.setUsername(this.rabbitMQProperties.getUsername());
        cf.setPassword(this.rabbitMQProperties.getPassword());
        cf.setVirtualHost(this.rabbitMQProperties.getVirtualHost());
        //configure a large thread pool for the connection thread
        int threads = 30;
        logger.info(String.format("Configuring thread pool with %d threads", threads));
        ExecutorService connectionPool = Executors.newFixedThreadPool(threads);
        cf.setExecutor(connectionPool);
        logger.info(String.format("MQ cache mode: %s", cf.getCacheMode().toString()));
        logger.info(String.format("MQ connection cache: %d", cf.getConnectionCacheSize()));
        logger.info(String.format("MQ channel cache: %d", cf.getChannelCacheSize()));
        return cf;
    }

    @Bean
    AmqpTemplate rabbitTemplate(org.springframework.amqp.rabbit.connection.CachingConnectionFactory connectionFactory){
        AmqpTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

1 个答案:

答案 0 :(得分:1)

在Spring AMQP中,单个物理TCP / IP连接的默认线程池默认为5个线程:

Spring AMQP

  

此外,在编写本文时,rabbitmq-client库默认为每个连接(5个线程)创建一个固定的线程池。使用大量连接时,应考虑在CachingConnectionFactory上设置自定义执行程序。然后,所有连接将使用相同的执行程序,并且可以共享其线程。执行程序的线程池应该是无限制的,或者为预期的利用率设置适当的(通常,每个连接至少有一个线程)。如果在每个连接上创建了多个通道,那么池大小将影响并发性,因此变量(或简单缓存)线程池执行器将是最合适的。

我能够通过更改分配给RabbitMQ连接池的线程数来复制它:

/**
     * Expand the number of concurrent threads for a single RabbitMQ connection
     * http://docs.spring.io/spring-amqp/reference/htmlsingle/
     * Also, at the time of writing, the rabbitmq-client library creates a fixed thread pool for each connection (5 threads) by default. 
     * When using a large number of connections, you should consider setting a custom executor on the CachingConnectionFactory.
     */
    @Bean(name="channelPool")
    @Scope("singleton")
    MigrationPool rabbitConnectionPool(){
        int channels = 50;
        logger.info(String.format("Configuring connection pool with %d threads", channels));
        return new MigrationPool(channels, channels, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
    }

    /**
     * Configure a large thread pool for concurrent channels on the physical Connection
     */
    @Bean
    public org.springframework.amqp.rabbit.connection.CachingConnectionFactory rabbitConnectionFactory(@Qualifier("channelPool") MigrationPool connectionPool) {
        logger.info("Configuring connection factory");
        CachingConnectionFactory cf = new CachingConnectionFactory();
        cf.setAddresses(this.rabbitMQProperties.getAddresses());
        cf.setUsername(this.rabbitMQProperties.getUsername());
        cf.setPassword(this.rabbitMQProperties.getPassword());
        cf.setVirtualHost(this.rabbitMQProperties.getVirtualHost());
        cf.setExecutor(connectionPool);
        logger.info(String.format("MQ cache mode: %s", cf.getCacheMode().toString()));
        logger.info(String.format("MQ connection cache: %d", cf.getConnectionCacheSize()));
        logger.info(String.format("MQ channel cache: %d", cf.getChannelCacheSize()));
        logger.info(String.format("MQ thread pool: %d threads", connectionPool.getMaximumPoolSize()));
        return cf;
    }

在上面的代码中,我有每个连接的线程数,镜像虚拟通道的数量,即每个虚拟RabbitMQ通道的一个真实物理线程,因为每个通道引用一个工作线程,每个处理一个消息。这导致通道不再在默认的5个连接上阻塞,而是充分利用扩展的线程数:

channels no longer blocking

操纵RabbitMQ连接可用的线程数将显示阻塞的通道。例如,设置为10个线程并打开50个通道。