使用rabbitmq手动确认消息的,定时获取队列消息实现
描述问题
最近项目中因为有些数据,需要推送到第三方系统中,因为数据会一直增加,并且需要与第三方系统做相关交互。
相关业务
本着不影响线上运行效率的思想,我们将增加的消息放入rabbitmq,使用另一个应用获取消费,因为数据只是推送,并且业务的数据有15分钟左右的更新策略,对实时性不是很高所以我们需要一个定时任务来主动链接rabbit去消费,然后将数据以网络方式传送
相关分析
网络上大致出现了相关的解决办法,但由于实现相关数据丢失及处理、性能和效率等相关基础业务的工作量,望而却步。。。。。。
还好spring有相关的 org.springframework.amqp 工具包,简化的大量麻烦>_> 让我们开始吧
了解rabbit的相关几个概念
了解了这几个概念的时候你可能已经关注到了我们今天的主题SimpleMessageListenerContainer
我们使用SimpleMessageListenerContainer容器设置消费队列监听,然后设置具体的监听Listener进行消息消费具体逻辑的编写,通过SimpleRabbitListenerContainerFactory我们可以完成相关SimpleMessageListenerContainer容器的管理,
但对于使用此容器批量消费的方式,官方并没有相关说明,网络上你可能只找到这篇SimpleMessageListenerContainer批量消息处理对于问题描述是很清晰,但是回答只是说的比较简单
下面我们就对这个问题的答案来个coding
解决办法
首先我们因为需要失败重试,使用spring的RepublishMessageRecoverer可以解决这个问题,这显然有一个缺点,即将在整个重试期间占用线程。所以我们使用了死信队列
相关配置
- @Bean
- ObjectMapper objectMapper() {
- ObjectMapper objectMapper = new ObjectMapper();
- DateFormat dateFormat = objectMapper.getDateFormat();
- JavaTimeModule javaTimeModule = new JavaTimeModule();
- SimpleModule module = new SimpleModule();
- module.addSerializer(new ToStringSerializer(Long.TYPE));
- module.addSerializer(new ToStringSerializer(Long.class));
- module.addSerializer(new ToStringSerializer(BigInteger.class));
- javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
- javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
- javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
- objectMapper.registerModule(module);
- objectMapper.registerModule(javaTimeModule);
- objectMapper.setConfig(objectMapper.getDeserializationConfig().with(new ObjectMapperDateFormatExtend(dateFormat)));//反序列化扩展日期格式支持
- objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
- objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
- return objectMapper;
- }
- @Bean
- RabbitAdmin admin (ConnectionFactory aConnectionFactory) {
- return new RabbitAdmin(aConnectionFactory);
- }
- @Bean
- MessageConverter jacksonAmqpMessageConverter( ) {
- return new Jackson2JsonMessageConverter(objectMapper());
- }
- @Bean
- Queue bcwPushControlQueue (RabbitAdmin rabbitAdmin) {
- Queue queue = new Queue(Queues.QUEUE_BCW_PUSH);
- rabbitAdmin.declareQueue(queue);
- return queue;
- }
- @Bean
- Queue bcwPayControlQueue (RabbitAdmin rabbitAdmin) {
- Queue queue = new Queue(Queues.QUEUE_BCW_PAY);
- rabbitAdmin.declareQueue(queue);
- return queue;
- }
- @Bean
- Queue bcwPullControlQueue (RabbitAdmin rabbitAdmin) {
- Queue queue = new Queue(Queues.QUEUE_BCW_PULL);
- rabbitAdmin.declareQueue(queue);
- return queue;
- }
- /**
- * 声明一个交换机
- * @return
- */
- @Bean
- TopicExchange controlExchange () {
- return new TopicExchange(Exchanges.ExangeTOPIC);
- }
- /**
- * 延时重试队列
- */
- @Bean
- public Queue bcwPayControlRetryQueue() {
- Map<String, Object> arguments = new HashMap<>();
- arguments.put("x-message-ttl", 10 * 1000);
- arguments.put("x-dead-letter-exchange", Exchanges.ExangeTOPIC);
- // 如果设置死信会以路由键some-routing-key转发到some.exchange.name,如果没设默认为消息发送到本队列时用的routing key
- arguments.put("x-dead-letter-routing-key", "queue_bcw.push");
- return new Queue("queue_bcw@pay@retry", true, false, false, arguments);
- }
- /**
- * 延时重试队列
- */
- @Bean
- public Queue bcwPushControlRetryQueue() {
- Map<String, Object> arguments = new HashMap<>();
- arguments.put("x-message-ttl", 10 * 1000);
- arguments.put("x-dead-letter-exchange", Exchanges.ExangeTOPIC);
- // 如果设置死信会以路由键some-routing-key转发到some.exchange.name,如果没设默认为消息发送到本队列时用的routing key
- arguments.put("x-dead-letter-routing-key", "queue_bcw.push");
- return new Queue("queue_bcw@push@retry", true, false, false, arguments);
- }
- /**
- * 延时重试队列
- */
- @Bean
- public Queue bcwPullControlRetryQueue() {
- Map<String, Object> arguments = new HashMap<>();
- arguments.put("x-message-ttl", 10 * 1000);
- arguments.put("x-dead-letter-exchange", Exchanges.ExangeTOPIC);
- // 如果设置死信会以路由键some-routing-key转发到some.exchange.name,如果没设默认为消息发送到本队列时用的routing key
- // arguments.put("x-dead-letter-routing-key", "queue_bcw");
- return new Queue("queue_bcw@pull@retry", true, false, false, arguments);
- }
- @Bean
- public Binding bcwPayControlRetryBinding() {
- return BindingBuilder.bind(bcwPushControlRetryQueue()).to(controlExchange()).with("queue_bcw.pay.retry");
- }
- @Bean
- public Binding bcwPushControlRetryBinding() {
- return BindingBuilder.bind(bcwPushControlRetryQueue()).to(controlExchange()).with("queue_bcw.push.retry");
- }
- @Bean
- public Binding bcwPullControlRetryBinding() {
- return BindingBuilder.bind(bcwPushControlRetryQueue()).to(controlExchange()).with("queue_bcw.pull.retry");
- }
- /**
- * 队列绑定并关联到RoutingKey
- *
- * @param queueMessages 队列名称
- * @param exchange 交换机
- * @return 绑定
- */
- @Bean
- Binding bcwPushBindingQueue(@Qualifier("bcwPushControlQueue") Queue queueMessages,@Qualifier("controlExchange") TopicExchange exchange) {
- return BindingBuilder.bind(queueMessages).to(exchange).with("queue_bcw.push");
- }
- /**
- * 队列绑定并关联到RoutingKey
- *
- * @param queueMessages 队列名称
- * @param exchange 交换机
- * @return 绑定
- */
- @Bean
- Binding bcwPayBindingQueue(@Qualifier("bcwPayControlQueue") Queue queueMessages, @Qualifier("controlExchange") TopicExchange exchange) {
- return BindingBuilder.bind(queueMessages).to(exchange).with("queue_bcw.pay");
- }
- /**
- * 队列绑定并关联到RoutingKey
- *
- * @param queueMessages 队列名称
- * @param exchange 交换机
- * @return 绑定
- */
- @Bean
- Binding bcwPullBindingQueue(@Qualifier("bcwPullControlQueue") Queue queueMessages,@Qualifier("controlExchange") TopicExchange exchange) {
- return BindingBuilder.bind(queueMessages).to(exchange).with("queue_bcw.pull");
- }
- @Bean
- @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
- public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
- SimpleRabbitListenerContainerFactoryConfigurer configurer,
- ConnectionFactory connectionFactory) {
- SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
- configurer.configure(factory, connectionFactory);
- factory.setMessageConverter(jacksonAmqpMessageConverter());
- return factory;
- }
下面就是我们的主题,定时任务使用的是org.springframework.scheduling
- /**
- * 手动确认消息的,定时获取队列消息实现
- */
- public abstract class QuartzSimpleMessageListenerContainer extends SimpleMessageListenerContainer {
- protected final Logger logger = LoggerFactory.getLogger(getClass());
- private List<Message> body = new LinkedList<>();
- public long start_time;
- private Channel channel;
- @Autowired
- private ObjectMapper objectMapper;
- @Autowired
- private RabbitTemplate rabbitTemplate;
- public QuartzSimpleMessageListenerContainer() {
- // 手动确认
- this.setAcknowledgeMode(AcknowledgeMode.MANUAL);
- this.setMessageListener((ChannelAwareMessageListener) (message,channel) -> {
- long current_time = System.currentTimeMillis();
- int time = (int) ((current_time - start_time)/1000);
- logger.info("====接收到{}队列的消息=====",message.getMessageProperties().getConsumerQueue());
- Long retryCount = getRetryCount(message.getMessageProperties());
- if (retryCount > 3) {
- logger.info("====此消息失败超过三次{}从队列的消息删除=====",message.getMessageProperties().getConsumerQueue());
- try {
- channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- return;
- }
- this.body.add(message);
- /**
- * 判断数组数据是否满了,判断此监听器时间是否大于执行时间
- * 如果在最后延时时间段内没有业务消息,此监听器会一直开着
- */
- if(body.size()>=3 || time>60){
- this.channel = channel;
- callback();
- }
- });
- }
- private void callback(){
- // channel = getChannel(getTransactionalResourceHolder());
- if(body.size()>0 && channel !=null && channel.isOpen()){
- try {
- callbackWork();
- }catch (Exception e){
- logger.error("推送数据出错:{}",e.getMessage());
- body.stream().forEach(message -> {
- Long retryCount = getRetryCount(message.getMessageProperties());
- if (retryCount <= 3) {
- logger.info("将消息置入延时重试队列,重试次数:" + retryCount);
- rabbitTemplate.convertAndSend(Exchanges.ExangeTOPIC, message.getMessageProperties().getReceivedRoutingKey()+".retry", message);
- }
- });
- } finally{
- logger.info("flsher too data");
- body.stream().forEach(message -> {
- //手动acknowledge
- try {
- channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
- } catch (IOException e) {
- logger.error("手动确认消息失败!");
- e.printStackTrace();
- }
- });
- body.clear();
- this.stop();
- }
- }
- }
- abstract void callbackWork() throws Exception;
- /**
- * 获取消息失败次数
- * @param properties
- * @return
- */
- private long getRetryCount(MessageProperties properties){
- long retryCount = 0L;
- Map<String,Object> header = properties.getHeaders();
- if(header != null && header.containsKey("x-death")){
- List<Map<String,Object>> deaths = (List<Map<String,Object>>)header.get("x-death");
- if(deaths.size()>0){
- Map<String,Object> death = deaths.get(0);
- retryCount = (Long)death.get("count");
- }
- }
- return retryCount;
- }
- @Override
- @Scheduled(cron = "0 0/2 * * * ? ")
- public void start() {
- logger.info("start push data scheduled!");
- //初始化数据,将未处理的调用stop方法,返还至rabbit
- body.clear();
- super.stop();
- start_time = System.currentTimeMillis();
- super.start();
- logger.info("end push data scheduled!");
- }
- public List<WDNJPullOrder> getBody() {
- List<WDNJPullOrder> collect = body.stream().map(data -> {
- byte[] body = data.getBody();
- WDNJPullOrder readValue = null;
- try {
- readValue = objectMapper.readValue(body, new TypeReference<WDNJPullOrder>() {
- });
- } catch (IOException e) {
- logger.error("处理数据出错{}",e.getMessage());
- }
- return readValue;
- }
- ).collect(Collectors.toList());
- return collect;
- }
- }
后续
当然定时任务的启动,你可以写到相关rabbit容器实现的里面,但是这里并不是很需要,所以对于这个的小改动,同学你可以自己实现
- @Scheduled(cron = "0 0/2 * * * ? ")
- public void start()
使用rabbitmq手动确认消息的,定时获取队列消息实现的更多相关文章
- RabbitMq手动确认时的重试机制
本文转载自RabbitMq手动确认时的重试机制 消息手动确认模式的几点说明 监听的方法内部必须使用channel进行消息确认,包括消费成功或消费失败 如果不手动确认,也不抛出异常,消息不会自动重新推送 ...
- RabbitMQ通过http API获取队列消息数量等信息
参考 RabbitMQ提供了HTTP API手册,发现其中有获取队列情况的API.(本地的API手册地址为:http://localhost:15672/api) 所有API调用都需要做权限验证,需在 ...
- RabbitMQ发布订阅实战-实现延时重试队列
RabbitMQ是一款使用Erlang开发的开源消息队列.本文假设读者对RabbitMQ是什么已经有了基本的了解,如果你还不知道它是什么以及可以用来做什么,建议先从官网的 RabbitMQ Tutor ...
- Windows消息理解(系统消息队列,进程消息队列,非队列消息)
// ====================Windows消息分类==========================在Windows中,消息分为以下三类:标准消息——除WM_COMMAND之外,所 ...
- RabbitMQ使用 prefetch_count优化队列的消费,使用死信队列和延迟队列实现消息的定时重试,golang版本
RabbitMQ 的优化 channel prefetch Count 死信队列 什么是死信队列 使用场景 代码实现 延迟队列 什么是延迟队列 使用场景 实现延迟队列的方式 Queue TTL Mes ...
- (六)RabbitMQ消息队列-消息任务分发与消息ACK确认机制(PHP版)
原文:(六)RabbitMQ消息队列-消息任务分发与消息ACK确认机制(PHP版) 在前面一章介绍了在PHP中如何使用RabbitMQ,至此入门的的部分就完成了,我们内心中一定还有很多疑问:如果多个消 ...
- 消息队列手动确认Ack
以RabbitMQ为例,默认情况下 RabbitMQ 是自动ACK机制,就意味着 MQ 会在消息发送完毕后,自动帮我们去ACK,然后删除消息的信息.这样依赖就存在这样一个问题:如果消费者处理消息需要较 ...
- RabbitMQ 消息确认机制以及lazy queue+ disk消息持久化
一:Basic的一些属性,一些方法 1. 消费端的确认 自动确认: message出队列的时候就自动确认[broke] basicget... 手工确认: message出队列之后,要应用程序自己去确 ...
- RabbitMQ获取队列的消息数目
使用RabbitMQ,业务需求,想要知道队列中还有多少待消费待数据. 方式一: @Value("${spring.rabbitmq.host}") private String h ...
随机推荐
- Python--day25--抽象类
什么是抽象类: 抽象类: #一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' ...
- spring boot + thymeleaf 乱码问题
spring boot + thymeleaf 乱码问题 hellotrms 发布于 2017/01/17 15:27 阅读 1K+ 收藏 0 答案 1 开发四年只会写业务代码,分布式高并发都不会还做 ...
- [Ramda] Handle Errors in Ramda Pipelines with tryCatch
Handling your logic with composable functions makes your code declarative, leading to code that's ea ...
- H3C查看文件内容
<H3C>more logfile.log 创建一个目录 <H3C>mkdir gaochengwang 重命名目录及文件 <H3C>rename wnt 0904 ...
- Linux 内核 启动时间
为见到 PCI 如何工作的, 我们从系统启动开始, 因为那是设备被配置的时候. 当一个 PCI 设备上电时, 硬件保持非激活. 换句话说, 设备只响应配置交易. 在上电时, 设备没有内存并且没有 I/ ...
- C语言 屏幕截图 (GDI)
截取全屏幕 #include <windows.h> void echo(CHAR *str); int CaptureImage(HWND hWnd, CHAR *dirPath, ...
- .net core 读取Excal文件数据及注意事项
添加ExcelDataReader.DataSet引用. 调用下列方法: public class XlsHelper { public static System.Data.DataSet GetX ...
- slim的中间件
slim中间件的作用简单来说就是过滤数据,request过来的数据要经过中间件才能到达内部,然后内部数据要到达外部的时候,也要经过中间件,正常通过才能到达外部
- 微信小程序酒店日历超强功能
首先利用date拿到年月日 月记得+1 ,因为是从0开始的 先遍历月份,跨年年+1 ,月归至1: 然后遍历天数, lastDat = new Date(val.year,val.month,0).ge ...
- 剑指Offer-62.数据流中的中位数(C++/Java)
题目: 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值.我们使 ...