描述问题

  最近项目中因为有些数据,需要推送到第三方系统中,因为数据会一直增加,并且需要与第三方系统做相关交互。

相关业务

  本着不影响线上运行效率的思想,我们将增加的消息放入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手动确认消息的,定时获取队列消息实现的更多相关文章

  1. RabbitMq手动确认时的重试机制

    本文转载自RabbitMq手动确认时的重试机制 消息手动确认模式的几点说明 监听的方法内部必须使用channel进行消息确认,包括消费成功或消费失败 如果不手动确认,也不抛出异常,消息不会自动重新推送 ...

  2. RabbitMQ通过http API获取队列消息数量等信息

    参考 RabbitMQ提供了HTTP API手册,发现其中有获取队列情况的API.(本地的API手册地址为:http://localhost:15672/api) 所有API调用都需要做权限验证,需在 ...

  3. RabbitMQ发布订阅实战-实现延时重试队列

    RabbitMQ是一款使用Erlang开发的开源消息队列.本文假设读者对RabbitMQ是什么已经有了基本的了解,如果你还不知道它是什么以及可以用来做什么,建议先从官网的 RabbitMQ Tutor ...

  4. Windows消息理解(系统消息队列,进程消息队列,非队列消息)

    // ====================Windows消息分类==========================在Windows中,消息分为以下三类:标准消息——除WM_COMMAND之外,所 ...

  5. RabbitMQ使用 prefetch_count优化队列的消费,使用死信队列和延迟队列实现消息的定时重试,golang版本

    RabbitMQ 的优化 channel prefetch Count 死信队列 什么是死信队列 使用场景 代码实现 延迟队列 什么是延迟队列 使用场景 实现延迟队列的方式 Queue TTL Mes ...

  6. (六)RabbitMQ消息队列-消息任务分发与消息ACK确认机制(PHP版)

    原文:(六)RabbitMQ消息队列-消息任务分发与消息ACK确认机制(PHP版) 在前面一章介绍了在PHP中如何使用RabbitMQ,至此入门的的部分就完成了,我们内心中一定还有很多疑问:如果多个消 ...

  7. 消息队列手动确认Ack

    以RabbitMQ为例,默认情况下 RabbitMQ 是自动ACK机制,就意味着 MQ 会在消息发送完毕后,自动帮我们去ACK,然后删除消息的信息.这样依赖就存在这样一个问题:如果消费者处理消息需要较 ...

  8. RabbitMQ 消息确认机制以及lazy queue+ disk消息持久化

    一:Basic的一些属性,一些方法 1. 消费端的确认 自动确认: message出队列的时候就自动确认[broke] basicget... 手工确认: message出队列之后,要应用程序自己去确 ...

  9. RabbitMQ获取队列的消息数目

    使用RabbitMQ,业务需求,想要知道队列中还有多少待消费待数据. 方式一: @Value("${spring.rabbitmq.host}") private String h ...

随机推荐

  1. Python--day30--tcp协议(建立链接三次握手,断掉链接四次挥手)和UDP协议

    TCP协议: tcp是可靠的,面向连接的.建立全双工通信. 建立链接的三次握手 链接一旦建立一定是全双工工通信,必然是双方通信. UDP协议: TCP协议和UDP协议的对比: QQ使用的是UDP,因为 ...

  2. BZOJ 4034"树上操作"(DFS序+线段树)

    传送门 •题意 有一棵点数为 N 的树,以点 1 为根,且树点有边权. 然后有 M 个操作,分为三种: 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子树中所有点的 ...

  3. springboot + redis + 注解 + 拦截器 实现接口幂等性校验

    一.概念 幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次 比如: 订单接口, 不能多次创建订单 支付接口, 重复支付同一笔订单只能扣一次钱 支付宝回调接口, 可能会多 ...

  4. JSONPath-简单入门

    JSONPath - 是xpath在json的应用. xml最大的优点就有大量的工具可以分析,转换,和选择性的提取文档中的数据.XPath是这些最强大的工具之一. 如果可以使用xpath来解析json ...

  5. Hamcrest匹配器框架

    其实在之前的文章中已经使用过 Hamcrest 匹配器框架,本篇文章将系统的介绍它的使用. 为什么要用Hamcrest匹配器框架 Hamcrest是一款软件测试框架, 可以通过现有的匹配器类检查代码中 ...

  6. 树莓派4安装ftp服务端

    vsftpd是开源的轻量级的常用ftp服务器.   1,安装vsftpd服务器 (约400KB) sudo apt-get install vsftpd     2,启动ftp服务 sudo serv ...

  7. gradle 打包后第三方登录不上

    使用 gradlew clean assembleReleaseChannels 生成不用的渠道包后 第三方登录不上 原因:打包未设置好APP的 .keystore

  8. python监控模块

    pip install psutil 获取内存信息: >>> import psutil >>> mem = psutil.virtual_memory() #获取 ...

  9. ant design 的Table组件固定表头时不对齐

    现在有一个表格,里面的列数是不固定的(可以重复写入数据),且列数行数都可能很多,就带来一个问题: 必须要固定表头,但是antd 的表格组件设置了固定表格 scroll={{x:1000,y:300}} ...

  10. 前端——BOM与DOM

    目录 前戏 window对象 window的子对象 navigator对象(了解即可) screen对象(了解即可) history对象(了解即可) location对象 弹出框 计时相关 DOM H ...