前言
Rabbitmq拉模式是什么?为什么有推模式还要拉模式?怎么在spring中实现批量拉消息消费呢?本文针对这些问题来动手实现一个Rabbitmq拉模式,消费者主动请求服务端接口来批量消费消息。
Rabbitmq的消费模式有两种:
- 推模式
- 拉模式
在spring中,加入封装好的amqp:
xml
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
只需要在消费者方法上加上@RabbitListener(queues = XXXX)就能实现消费者监听订阅的主题;比如如下,监听我们订阅的队列,当有消息的时候,消费者执行发送邮件方法
java
@RabbitListener(queues = RabbitMQConfig.QUEUE_NAME)
public void receiveMessage(String message) {
log.info("开始发送邮件信息:" + message);
mailService.sendMail(message);
}
探究原理
什么是拉模式?
先了解一下推模式:消息中间件主动将消息推送给消费者;可以结合观察者模式来理解,前提是消费者订阅(注册)了需要的主题,当生产者在该主题内生产了新的消息,那么服务端就会主动推送新的消息到消费者端,这样就实现了消息队列的推模式。由于推模式是信息到达RabbitMQ后,就会立即被投递给匹配的消费者,所以实时性非常好,消费者能及时得到最新的消息。
拉模式:消费者主动从消息中间件拉取消息,相较于推模式的区别,其实就跟pull和push一样,是消费者主动去服务端拉取消息消费,是主动的;由于拉模式需要消费者手动去RabbitMQ中拉取消息,所以实时性较差;消费者难以获取实时消息,具体什么时候能拿到新消息完全取决于消费者什么时候去拉取消息。
为什么要拉模式?
既然拉模式的的实时性不好,为什么还要用呢?因为在某些场景下,由于某些限制,消费者只有在某些条件成立下才能去消息中间件中批量获取消息;而且相较于推模式关注于消息的实时性,拉模式更加关注消费者的消费能力,所以是因为场景不同,我们需要拉模式。
怎么实现批量拉消息消费呢?
引入spring实现的amqp包,其中RabbitTemplate提供了获取单条消息的方法,我们可以自己实现批量消费。
动手实现
配置
java
@Configuration
public class RabbitConfig {
public static final String TOPIC_EXCHANGE_NAME = "topic.exchange";
public static final String TOPIC_QUEUE_A = "topic.queue.a";
public static final String TOPIC_QUEUE_B = "topic.queue.b";
@Resource
private RabbitAdmin rabbitAdmin;
@Bean
public RabbitTemplate rabbitTemplate() {
return rabbitAdmin.getRabbitTemplate();
}
public ConnectionFactory getConnectFactory() {
com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
connectionFactory.setHost("XXXX");
connectionFactory.setPort(xXXX);
connectionFactory.setUsername("XXXX");
connectionFactory.setPassword("XXXX");
connectionFactory.setVirtualHost("XXXX");
return new CachingConnectionFactory(connectionFactory);
}
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
public RabbitAdmin rabbitAdmin() {
RabbitAdmin rabbitAdmin = new RabbitAdmin(getConnectFactory());
// 只有设置为 true,spring 才会加载 RabbitAdmin 这个类
rabbitAdmin.setAutoStartup(true);
rabbitAdmin.declareExchange(rabbitTopicExchange());
rabbitAdmin.declareQueue(topicQueue(TOPIC_QUEUE_A));
rabbitAdmin.declareQueue(topicQueue(TOPIC_QUEUE_B));
return rabbitAdmin;
}
public TopicExchange rabbitTopicExchange() {
return new TopicExchange(TOPIC_EXCHANGE_NAME, true, false);
}
public Queue topicQueue(String queueName) {
return new Queue(queueName, true, false, false);
}
@Bean
public Binding bindTopicExchangeToMail() {
return BindingBuilder.bind(topicQueue(TOPIC_QUEUE_A))
.to(rabbitTopicExchange())
.with("XXXX");
}
@Bean
public Binding bindTopicExchangeToSms() {
return BindingBuilder.bind(topicQueue(TOPIC_QUEUE_B))
.to(rabbitTopicExchange())
.with("XXXX");
}
}
实现类
RabbitMQService
java
public interface RabbitMQService {
String consumeOneMessage(String queueName);
List<String> consumeBatchMessage(String queueName, int batchSize);
}
RabbitMQServiceImpl
java
@Service
public class RabbitMQServiceImpl implements RabbitMQService {
@Resource
private RabbitTemplate rabbitTemplate;
// 消费单条消息
@Override
public String consumeOneMessage(String queueName) {
Object o = rabbitTemplate.receiveAndConvert(queueName);
return (String) o;
}
// 批量消费消息
@Override
public List<String> consumeBatchMessage(String queueName, int batchSize) {
List<String> result = new ArrayList<>();
for (int i = 0; i < batchSize; i ++) {
String message = (String) rabbitTemplate.receiveAndConvert(queueName);
if (message == null) {
break;
// 取200条消息,大概25ms continue;
}
result.add(message);
}
return result;
}
}
接口
java
@RestController
@RequestMapping("/rabbitmq")
public class RabbitMQController {
@Resource
private RabbitMQService rabbitMQService;
@GetMapping("/batchConsume")
public List<String> consumeBatch(@RequestParam(name = "queueName") String queueName, @RequestParam(name = "batchSize") int batchSize) {
return rabbitMQService.consumeBatchMessage(queueName, batchSize);
}
@GetMapping("/singleConsume")
public String consumeSingle(@RequestParam(name = "queueName") String queueName) {
return rabbitMQService.consumeOneMessage(queueName);
}
问题
-
定时任务不生效的问题;当我们的配置文件实现了BeanPostProcessor后,定时任务不生效?
-
RabbitTemplate实现获取单条消息的原理呢?ack机制是什么?
-
推磨式下怎么保证消息不丢失呢?