import java.util.Properties;
import java.util.function.Consumer; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.ChannelCallback;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter;
import org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.GetResponse; @Service
public class RabbitAdminServices { private static final Logger logger = LoggerFactory.getLogger(RabbitAdminServices.class); @Autowired
AmqpAdmin rabbitAdmin; @Autowired
RabbitTemplate rabbitTemplate; @Autowired
MessageConverter messageConverter; private volatile MessagePropertiesConverter messagePropertiesConverter = new DefaultMessagePropertiesConverter(); public int getCount(String queueName) { Properties properties = rabbitAdmin.getQueueProperties(queueName);
return (Integer)properties.get(RabbitAdmin.QUEUE_MESSAGE_COUNT);
} public <T> void processQueue(String queueName, Integer count, Class<T> clazz, Consumer<T> consumer) { int reprocessCount = getCount(queueName);
int requestCount = reprocessCount;
if(count != null) {
requestCount = count;
}
for(int i = 0; i < reprocessCount && i < requestCount; i++) {
rabbitTemplate.execute(new ChannelCallback<T>() { @Override
public T doInRabbit(Channel channel) throws Exception {
GetResponse response = channel.basicGet(queueName, false);
T result = null;
try {
MessageProperties messageProps = messagePropertiesConverter.toMessageProperties(response.getProps(), response.getEnvelope(), "UTF-8");
if(response.getMessageCount() >= 0) {
messageProps.setMessageCount(response.getMessageCount());
}
Message message = new Message(response.getBody(), messageProps);
result = (T)messageConverter.fromMessage(message);
consumer.accept(result);
channel.basicAck(response.getEnvelope().getDeliveryTag(), false);
}
catch(Exception e) {
channel.basicNack(response.getEnvelope().getDeliveryTag(), false, true);
}
return result;
}
}); }
}
}

实现RabbitMQ的消费者有两种模式,推模式(Push)和拉模式(Pull)。

实现推模式推荐的方式是继承 DefaultConsumer 基类,也可以使用Spring AMQP的 SimpleMessageListenerContainer 。

推模式是最常用的,但是有些情况下推模式并不适用的,比如说:

  • 由于某些限制,消费者在某个条件成立时才能消费消息

  • 需要批量拉取消息进行处理

实现拉模式

RabbitMQ的Channel提供了 basicGet 方法用于拉取消息。

  1.  
    /**
  2.  
    * Retrieve a message from a queue using {@link com.rabbitmq.client.AMQP.Basic.Get}
  3.  
    * @see com.rabbitmq.client.AMQP.Basic.Get
  4.  
    * @see com.rabbitmq.client.AMQP.Basic.GetOk
  5.  
    * @see com.rabbitmq.client.AMQP.Basic.GetEmpty
  6.  
    * @param queue the name of the queue
  7.  
    * @param autoAck true if the server should consider messages
  8.  
    * acknowledged once delivered; false if the server should expect
  9.  
    * explicit acknowledgements
  10.  
    * @return a {@link GetResponse} containing the retrieved message data
  11.  
    * @throws java.io.IOException if an error is encountered
  12.  
    */
  13.  
    GetResponse basicGet(String queue, boolean autoAck) throws IOException;

basicGet 返回 GetResponse 类。

  1.  
    public class GetResponse {
  2.  
    private final Envelope envelope;
  3.  
    private final BasicProperties props;
  4.  
    private final byte[] body;
  5.  
    private final int messageCount;
  6.  
     
  7.  
    // ...
 

public class GetResponse { private final Envelope envelope; private final BasicProperties props; private final byte[] body; private final int messageCount; // ...

rabbitmq-client版本4.0.3

使用 basicGet 拉取消息需要注意:

  1.  
    basicGet
  2.  
    DefaultConsumer

示例代码:

  1.  
    private void consume(Channel channel) throws IOException, InterruptedException {
  2.  
    while (true) {
  3.  
    if (!isConditionSatisfied()) {
  4.  
    TimeUnit.MILLISECONDS.sleep(1);
  5.  
    continue;
  6.  
    }
  7.  
    GetResponse response = channel.basicGet(CAOSH_TEST_QUEUE, false);
  8.  
    if (response == null) {
  9.  
    TimeUnit.MILLISECONDS.sleep(1);
  10.  
    continue;
  11.  
    }
  12.  
    String data = new String(response.getBody());
  13.  
    logger.info("Get message <= {}", data);
  14.  
    channel.basicAck(response.getEnvelope().getDeliveryTag(), false);
  15.  
    }
  16.  
    }

批量拉取消息

RabbitMQ支持客户端批量拉取消息,客户端可以连续调用 basicGet 方法拉取多条消息,处理完成之后一次性ACK。需要注意:

  1.  
    basicGet
  2.  
    basicAck

示例代码:

  1.  
    String bridgeQueueName = extractorProperties.getBridgeQueueName();
  2.  
    int batchSize = extractorProperties.getBatchSize();
  3.  
    List<GetResponse> responseList = Lists.newArrayListWithCapacity(batchSize);
  4.  
    long tag = 0;
  5.  
    while (responseList.size() < batchSize) {
  6.  
    GetResponse getResponse = channel.basicGet(bridgeQueueName, false);
  7.  
    if (getResponse == null) {
  8.  
    break;
  9.  
    }
  10.  
    responseList.add(getResponse);
  11.  
    tag = getResponse.getEnvelope().getDeliveryTag();
  12.  
    }
  13.  
    if (responseList.isEmpty()) {
  14.  
    TimeUnit.MILLISECONDS.sleep(1);
  15.  
    } else {
  16.  
    logger.info("Get <{}> responses this batch", responseList.size());
  17.  
    // handle messages
  18.  
    channel.basicAck(tag, true);
  19.  
    }

关于QueueingConsumer

QueueingConsumer 在客户端本地使用 BlockingQueue 缓冲消息,其nextDelivery方法也可以用于实现拉模式(其本质上是 BlockingQueue.take ),但是 QueueingConsumer 现在已经标记为Deprecated。

Spring Boot中通过RabbitTemplate主动pull(get)消息的例子的更多相关文章

  1. Spring Boot中一个Servlet主动断开连接的方法

    主动断开连接,从而返回结果给客户端,并且能够继续执行剩余代码. 对于一个HttpServletResponse类型的对象response来说,执行如下代码: response.getWriter(). ...

  2. 记一次spring boot中MongoDB Prematurely reached end of stream的异常解决

    在spring boot项目中使用了mongodb,当一段时间没有操作mongodb,下次操作mongodb时就会出现异常.异常如下: org.springframework.data.mongodb ...

  3. Spring Boot中使用缓存

    Spring Boot中使用缓存 随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一. 原始的使 ...

  4. Spring Boot中使用RabbitMQ

    很久没有写Spring Boot的内容了,正好最近在写Spring Cloud Bus的内容,因为内容会有一些相关性,所以先补一篇关于AMQP的整合. Message Broker与AMQP简介 Me ...

  5. spring boot中使用servlet、listener和filter

    spring boot中支持使用java Web三大组件(servlet.listener和filter),但是坑比较多,主要是spring boot内嵌tomcat和独立tomcat服务器有一些细节 ...

  6. Spring AOP动态代理实现,解决Spring Boot中无法正常启用JDK动态代理的问题

    Spring AOP底层的动态代理实现有两种方式:一种是JDK动态代理,另一种是CGLib动态代理. JDK动态代理 JDK 1.3版本以后提供了动态代理,允许开发者在运行期创建接口的代理实例,而且只 ...

  7. Spring Boot中快速操作Mongodb

    Spring Boot中快速操作Mongodb 在Spring Boot中集成Mongodb非常简单,只需要加入Mongodb的Starter包即可,代码如下: <dependency> ...

  8. 在 Spring Boot 中使用 HikariCP 连接池

    上次帮小王解决了如何在 Spring Boot 中使用 JDBC 连接 MySQL 后,我就一直在等,等他问我第三个问题,比如说如何在 Spring Boot 中使用 HikariCP 连接池.但我等 ...

  9. spring boot(三):Spring Boot中Redis的使用

    spring boot对常用的数据库支持外,对nosql 数据库也进行了封装自动化. redis介绍 Redis是目前业界使用最广泛的内存数据存储.相比memcached,Redis支持更丰富的数据结 ...

  10. Spring Boot中的事务管理

    原文  http://blog.didispace.com/springboottransactional/ 什么是事务? 我们在开发企业应用时,对于业务人员的一个操作实际是对数据读写的多步操作的结合 ...

随机推荐

  1. 38. data为什么是一个函数

    vue中的data为什么是返回对象的函数,而不是直接使用对象形式 : 我们复用组件的时候,要求每一份data数据之间是独立的,不能互相影响,如果写成对象的形式所有的组件使用一份data数据 ,如果使用 ...

  2. 排查sshfs挂载失败的问题

    排查sshfs挂载失败的问题 写代码在Linux上运行,但是熟悉的IDE(比如VS code)在自己的电脑上,可以使用sshfs把linux上的目录挂载到本地,再用VScode打开即可,可以使用下面的 ...

  3. kotlin协程——>基础、取消与超时

    Kotlin使用挂起函数为异步操作,使用kotlinx.coroutines中的launch.async 1. 第⼀个协程程序 import kotlinx.coroutines.* fun main ...

  4. 《你不知道的JavaScript》读书笔记(一):JS是如何查找变量的

    这本书之前囫囵地看了一遍,确实点明了很多以前不清不楚的点,但是仅仅看一遍是没什么用的,最近面试遇到不少原理相关的题感觉答得不理想,回头看下其实以前都理解过,但是没有记下来,正好结合实际的问题来再学习一 ...

  5. OpenFunction v0.8.0 发布:通过 Dapr Proxy 加速函数启动

    相较于其他函数计算项目,OpenFunction 有很多独特的功能,其中之一便是通过 Dapr 与各种后端服务(BaaS)无缝集成.目前 OpenFunction 已经支持了 Dapr 的 pub/s ...

  6. CSS动画(登录页面)

    1.整体效果 https://mmbiz.qpic.cn/sz_mmbiz_gif/EGZdlrTDJa6Pfop3IiakrZOtiaiaKniaxeH2Gs407DmkXPsMo8AFKdsYib ...

  7. chrony服务器

    1.安装 yum install chrony -y 2.配置文件 主配置文件:/etc/chrony.conf server ntp.aliyun.com iburst         ---指定阿 ...

  8. 基于Java+SpringBoot+Mysql实现的古诗词平台功能设计与实现四

    一.前言介绍: 1.1 项目摘要 随着信息技术的迅猛发展和数字化时代的到来,传统文化与现代科技的融合已成为一种趋势.古诗词作为中华民族的文化瑰宝,具有深厚的历史底蕴和独特的艺术魅力.然而,在现代社会中 ...

  9. 1.Kubernetes简介

    Kubernetes简介 来源 bilibili尚硅谷K8S视频:https://www.bilibili.com/video/BV1GT4y1A756 中文官网:https://kubernetes ...

  10. Modbus调试、Modbus Slave、ModScan、Modbus Ploll、串口调试

    记录一下昨天调试Modbus调试. 上位机往下位机发送modbus指令.发送过去之后没有反应.后来才调试出来原来是下位机错一个位. 调试过程:用modScan 往modbus slave 发送modb ...