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. 什么是 RBAC 权限控制

    RBAC是Role Based Access Control的英文缩写,意思是 基于角色的访问控制. RBAC实际上就是针对产品去挖掘需求时所用到的Who(角色).What(拥有什么资源).How(有 ...

  2. python中队列deque的使用

    队列,堆栈是程序开发中常用的两种数据存储模型.python中队列怎么运用呢?以下内容介绍了队列的使用和队列的函数. from collections import deque q = deque() ...

  3. Nacos2.3.2在ubuntu中的部署

    Nacos2.3.2 在ubuntu下的部署 下载地址 发布历史 | Nacos 官网 https://download.nacos.io/nacos-server/nacos-server-2.3. ...

  4. 中国移动基于 Kubernetes 的物联网边缘计算应用实践

    作者:何毓川,中移物联网,云计算开发高级工程师 EdgeBox简介 中移物联网是中国移动集团在物联网方向的专业研发子公司,在各个垂直行业都有非常丰富和完成的解决方案. 本文通过中移物联网的物联网边缘计 ...

  5. (系列九)使用Vue3+Element Plus创建前端框架(附源码)

    说明 该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发). 该系统文章,我会尽量说的非常详细,做到不管新手.老手都能看懂. 说明:OverallAuth2 ...

  6. JS 通过年份获取月,季度,半年度,年度

    原文请关注公众号 "酒酒酒酒"​,关注公众号 回复  "JS 通过年份获取月,季度,半年度,年度" 可获取源代码 功能描述: 实例化一个函数,给函数内传递不同的 ...

  7. cnblogs内容同步到51cto上的说明(声明)

    51CTO网站上的blog地址为:https://blog.51cto.com/u_15642578 该地址是个人在博客园cnblogs上的同步账号(https://cnblogs.com/xyz), ...

  8. AI游戏外挂:强化学习算法用于棋牌类游戏的最优出牌策略 —— 如何在“斗地主”中使用AI技术获得最高胜率

    相关: https://zh.wikipedia.org/wiki/十三張 去年原打算接的一个小项目,不过后来没有搞下去,这里只记录一下. 这个项目的主要需要完成的一个功能就是图像识别,识别屏幕上的牌 ...

  9. MMdetection 问题报错 mmdet/evaluation/metrics/coco_metric.py data[‘category_id’] = self.cat_ids[label] IndexError: list index out of range

    方案一:有人说 在自己定义的 conifg文件中增加 metainfo = { 'classes': ('class1','class2', 'class2',), 'palette': [ (220 ...

  10. pycharm生成的allure测试报告如何查看本地的index.html文件?

    pycharm生成的allure测试报告应该是通过服务启动查看,但是如果把这个文件保存到本地查看,直接打开页面无内容 可以使用allure-combine工具实现本地正常打开 `from allure ...