RabbitMQ配置
引入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
application.yml配置
yaml
spring:
rabbitmq:
host: 42.194.132.44
port: 5672
username: guest
password: guest
RabbitmqConfig配置
java
package com.angel.ocean.rabbitmq.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class RabbitmqConfig implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* 单条消费
*/
@Bean(name = "singleContainerFactory")
public SimpleRabbitListenerContainerFactory singleContainerFactory() {
SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
containerFactory.setConnectionFactory(applicationContext.getBean(ConnectionFactory.class));
containerFactory.setMaxConcurrentConsumers(1);
containerFactory.setConcurrentConsumers(1);
containerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
containerFactory.setConsumerBatchEnabled(false);
containerFactory.setBatchListener(false);
containerFactory.setAutoStartup(false);
containerFactory.setPrefetchCount(100);
return containerFactory;
}
/**
* 批量消费
*/
@Bean(name = "batchContainerFactory")
public SimpleRabbitListenerContainerFactory batchContainerFactory() {
SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
containerFactory.setConnectionFactory(applicationContext.getBean(ConnectionFactory.class));
containerFactory.setMaxConcurrentConsumers(1);
containerFactory.setConcurrentConsumers(1);
containerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
containerFactory.setBatchListener(true);
containerFactory.setConsumerBatchEnabled(true);
containerFactory.setBatchSize(50);
containerFactory.setAutoStartup(false);
containerFactory.setPrefetchCount(100);
return containerFactory;
}
}
RabbitMQ 队列声明、启动消费者设置
java
package com.angel.ocean.rabbitmq.runner;
import com.angel.ocean.rabbitmq.util.RabbitmqUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Slf4j
@Component
public class RabbitmqQueueRunner implements CommandLineRunner {
@Resource
private AmqpAdmin amqpAdmin;
@Resource
private RabbitListenerEndpointRegistry endpointRegistry;
@Override
public void run(String... args) throws Exception {
log.info("动态创建MQ配置信息...");
RabbitmqUtil.declareRabbitmqQueue(amqpAdmin, "ocean");
// 启动消费者
endpointRegistry.start();
}
}
RabbitmqUtil 工具类
java
package com.angel.ocean.rabbitmq.util;
import org.springframework.amqp.core.*;
public class RabbitmqUtil {
/**
* 交换机
*/
public static Exchange getExchange(String key) {
String name = key + "_exchange";
return new DirectExchange(name, true, false);
}
/**
* 队列
*/
public static Queue getQueue(String key) {
String name = key + "_queue";
return new Queue(name, true, false, false);
}
/**
* 绑定
*/
public static Binding getBinding(String key) {
String routingKey = key + "_routing_key";
return new Binding(getQueue(key).getName(), Binding.DestinationType.QUEUE,
getExchange(key).getName(), routingKey, null);
}
/**
* 创建RabbitMQ队列
*/
public static void declareRabbitmqQueue(AmqpAdmin amqpAdmin, String key) {
// 获取队列
Queue queue = RabbitmqUtil.getQueue(key);
// 获取交换机
Exchange exchange = RabbitmqUtil.getExchange(key);
// 绑定关系
Binding binding = RabbitmqUtil.getBinding(key);
// 创建队列
amqpAdmin.declareQueue(queue);
amqpAdmin.declareExchange(exchange);
amqpAdmin.declareBinding(binding);
}
}
RabbitMQ 使用验证
生产者
java
package com.angel.ocean.rabbitmq;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSON;
import com.angel.ocean.rabbitmq.domain.UserInfo;
import com.angel.ocean.rabbitmq.service.RabbitmqSenderService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.Date;
@Slf4j
@SpringBootTest
class ApplicationTest {
@Resource
private RabbitmqSenderService rabbitmqSenderService;
@Test
void test() {
for (int i = 0; i < 500; i++) {
UserInfo userInfo = new UserInfo(i, "Name" + i, RandomUtil.randomString(32), new Date());
rabbitmqSenderService.send(JSON.toJSONString(userInfo));
}
}
}
java
package com.angel.ocean.rabbitmq.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class RabbitmqSenderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(String message) {
rabbitTemplate.convertAndSend("ocean_exchange", "ocean_routing_key", message);
}
}
消费者
java
package com.angel.ocean.rabbitmq.listener;
import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
@Slf4j
@Component
public class RabbitmqListener {
/**
* 单条消费
*/
@RabbitListener(queues = "ocean_queue", containerFactory = "singleContainerFactory")
public void receive(Message message, Channel channel) throws IOException {
try {
log.info("Received data: {}", new String(message.getBody()));
// 消费完成后,手动ACK
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
int sleepTime = RandomUtil.randomInt(2);
Thread.sleep(sleepTime);
} catch (IOException e) {
// 处理消费失败的情况
log.error("RabbitmqListener.receive() IOException, {}", e.getMessage(), e);
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
} catch (InterruptedException e) {
log.error("RabbitmqListener.receive() InterruptedException, {}", e.getMessage(), e);
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
/**
* 批量消费
*/
@RabbitListener(queues = "ocean_queue", containerFactory = "batchContainerFactory")
public void batchReceive(List<Message> messages, Channel channel) throws IOException {
log.info("batchReceive, size:{}", messages.size());
// 消费完成后,手动ACK
for(Message message: messages) {
try {
// TODO 消费消息的逻辑
log.info("batchReceive data: {}", new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
// 处理消费失败的情况
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
}
RabbitMQ 使用注意事项
- 为了保证数据一致性,设置RabbitMQ的消息确认机制ack为手动确认
java
containerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
或者
- 如果要保证消息顺序消费,不能设置并发消费
java
containerFactory.setConcurrentConsumers(1);
或者
如果设置成并发消费,消费结果如下图,可以看出消息消费出现了乱序。
- 批量消费设置,可以根据实际业务设置批次大小
1)批量消费设置
java
/**
* 批量消费
*/
@Bean(name = "batchContainerFactory")
public SimpleRabbitListenerContainerFactory batchContainerFactory() {
SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
containerFactory.setConnectionFactory(applicationContext.getBean(ConnectionFactory.class));
containerFactory.setMaxConcurrentConsumers(1);
containerFactory.setConcurrentConsumers(1);
containerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
containerFactory.setBatchListener(true);
containerFactory.setConsumerBatchEnabled(true);
containerFactory.setBatchSize(50);
containerFactory.setAutoStartup(false);
containerFactory.setPrefetchCount(100);
return containerFactory;
}
2)@RabbitListener配置,使用containerFactory = "batchContainerFactory(见RabbitmqConfig)
java
@RabbitListener(queues = "ocean_queue", containerFactory = "batchContainerFactory")
public void batchReceive(List<Message> messages, Channel channel) throws IOException {
log.info("batchReceive, size:{}", messages.size());
// 消费完成后,手动ACK
for(Message message: messages) {
try {
// TODO 消费消息的逻辑
log.info("batchReceive data: {}", new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
// 处理消费失败的情况
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
验证结果如下图:
4. @RabbitListener先创建队列,再消费配置
如果使用默认配置,即设置containerFactory.setAutoStartup(true),而且队列又不存在,就会报如下错误:
java
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'ocean_queue' in vhost '/', class-id=50, method-id=10)
1)设置消费者监听自动启动为false
2)服务启动后,启动消费者监听