Spring-RabbitMQ 工作队列实践

Springboot 版本: 2.7.0

介绍

工作队列可以将耗时任务分配给多个工作者(或消费者)。其背后的主要思想为避免立即执行资源密集型任务并等待其结果,相反的,我们应该让任务异步执行。
Spring-RabbitMQ 工作队列实践

我们可以将任务封装成消息发送到工作队列,那么在后台运行的工作者就可以获取到消息也就是获取到任务,然后去执行任务。 如果后台有多个工作者,那么这些工作者们将共享任务列表,共同完成这些任务。

代码实现

配置文件:

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    # 消息确认(ACK)
    publisher-confirm-type: CORRELATED #correlated #确认消息已发送到交换机(Exchange)
    publisher-returns: true #确认消息已发送到队列(Queue)
    listener:
      type: simple
      simple:
        default-requeue-rejected: false
        acknowledge-mode: MANUAL

配置类:用于生成交换机和队列等基本Java Bean。

@Slf4j
@Configuration
public class RabbitConfiguration {

    public final static String TOPIC_EXCHANGE = "myExchange";

    public final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(jsonConverter());
        template.setExchange(TOPIC_EXCHANGE);
        template.setConfirmCallback((correlationData, ack, cause) -> {
            if (!ack) {
                log.error("消息:{}发送失败,失败原因为:{}", correlationData.getId(), cause);
            }
        });

        template.setMandatory(true);
        template.setReturnsCallback(returned -> {
            log.error("消息:{}路由失败, 失败原因为:{}", returned.getMessage().toString(), returned.getReplyText());
        });
        return template;
    }

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(TOPIC_EXCHANGE, true, false);
    }


    @Bean
    public Queue queue() {

        return new Queue(QUEUE_NAME, true, false, false);
    }

    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(topicExchange()).with("my.test.*");
    }

    @Bean
    public Jackson2JsonMessageConverter jsonConverter() {
        return new Jackson2JsonMessageConverter();
    }

}

生产者:连续生成10个消息。

@Component
public class Publisher {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send() {
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("my.test.message", new User("kleven", 18, i + 1), new CorrelationData());
        }
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User implements Serializable {
    private static final long serialVersionUID = -5079682733940745661L;

    private String name;
    private Integer age;
    private Integer id;

}

平均分配(轮询模式)

轮询模式:消息会被平均分配给所有的消费者。

@Slf4j
@Component
public class Worker {


    @RabbitListener(queues = "myQueue", messageConverter = "jsonConverter")
    public void worker1(@Payload User user, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws IOException {
        log.info("worker1消费 -> {}", user);
        channel.basicAck(deliveryTag, false);
    }

    @RabbitListener(queues = "myQueue", messageConverter = "jsonConverter")
    public void worker2(@Payload User user, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws InterruptedException, IOException {
        // 假设worker2每次消费需要花费5s
        Thread.sleep(5_000);
        log.info("worker2消费 -> {}", user);
        channel.basicAck(deliveryTag, false);
    }
}

结果:

 worker1消费 -> User(name=kleven, age=18, id=1)
 worker1消费 -> User(name=kleven, age=18, id=3)
 worker1消费 -> User(name=kleven, age=18, id=5)
 worker1消费 -> User(name=kleven, age=18, id=7)
 worker1消费 -> User(name=kleven, age=18, id=9)
 worker2消费 -> User(name=kleven, age=18, id=2)
 worker2消费 -> User(name=kleven, age=18, id=4)
 worker2消费 -> User(name=kleven, age=18, id=6)
 worker2消费 -> User(name=kleven, age=18, id=8)
 worker2消费 -> User(name=kleven, age=18, id=10)

虽然worker2执行缓慢,但依然被分配了一半的任务。这样实际上是拖慢了整体的执行速度,正常的逻辑应该是执行速度快的工作者应该被分配到更多的任务。

非平均分配(能者多劳)

为了让速度快的的工作者分配到更多的任务,可以使用非平均分配。最简单的实现方式是设置prefetch=1(channel.basicQos(1)),即信道中只允许一条未确认的消息,那么消费快的工作者确认消息后马上就可以被分配下一个任务,而效率低的工作者由于信道中的消息还没有确认所以不能被分配任务,这样就实现了最基本的非平均分配,即能者多劳。

Spring-RabbitMQ 工作队列实践

修改后的配置文件:只需将prefetch设置为1,其他代码不变。

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    # 消息确认(ACK)
    publisher-confirm-type: CORRELATED #correlated #确认消息已发送到交换机(Exchange)
    publisher-returns: true #确认消息已发送到队列(Queue)
    listener:
      type: simple
      simple:
        default-requeue-rejected: false
        acknowledge-mode: MANUAL
        prefetch: 1

结果:效率高的worker1被分配了9个任务,效率低的worker仅仅只被分配了1个任务。

worker1消费 -> User(name=kleven, age=18, id=1)
worker1消费 -> User(name=kleven, age=18, id=3)
worker1消费 -> User(name=kleven, age=18, id=4)
worker1消费 -> User(name=kleven, age=18, id=5)
worker1消费 -> User(name=kleven, age=18, id=6)
worker1消费 -> User(name=kleven, age=18, id=7)
worker1消费 -> User(name=kleven, age=18, id=9)
worker1消费 -> User(name=kleven, age=18, id=8)
worker1消费 -> User(name=kleven, age=18, id=10)
worker2消费 -> User(name=kleven, age=18, id=2)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

i余数

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值