描述问题

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

相关业务

  本着不影响线上运行效率的思想,我们将增加的消息放入rabbitmq,使用另一个应用获取消费,因为数据只是推送,并且业务的数据有15分钟左右的更新策略,对实时性不是很高所以我们需要一个定时任务来主动链接rabbit去消费,然后将数据以网络方式传送

相关分析

  网络上大致出现了相关的解决办法,但由于实现相关数据丢失及处理、性能和效率等相关基础业务的工作量,望而却步。。。。。。

  还好spring有相关的 org.springframework.amqp 工具包,简化的大量麻烦>_> 让我们开始吧

  了解rabbit的相关几个概念

 了解了这几个概念的时候你可能已经关注到了我们今天的主题SimpleMessageListenerContainer

 我们使用SimpleMessageListenerContainer容器设置消费队列监听,然后设置具体的监听Listener进行消息消费具体逻辑的编写,通过SimpleRabbitListenerContainerFactory我们可以完成相关SimpleMessageListenerContainer容器的管理,

  但对于使用此容器批量消费的方式,官方并没有相关说明,网络上你可能只找到这篇SimpleMessageListenerContainer批量消息处理对于问题描述是很清晰,但是回答只是说的比较简单

  下面我们就对这个问题的答案来个coding

解决办法

  首先我们因为需要失败重试,使用spring的RepublishMessageRecoverer可以解决这个问题,这显然有一个缺点,即将在整个重试期间占用线程。所以我们使用了死信队列

  相关配置

  1. @Bean
  2. ObjectMapper objectMapper() {
  3. ObjectMapper objectMapper = new ObjectMapper();
  4. DateFormat dateFormat = objectMapper.getDateFormat();
  5. JavaTimeModule javaTimeModule = new JavaTimeModule();
  6.  
  7. SimpleModule module = new SimpleModule();
  8. module.addSerializer(new ToStringSerializer(Long.TYPE));
  9. module.addSerializer(new ToStringSerializer(Long.class));
  10. module.addSerializer(new ToStringSerializer(BigInteger.class));
  11.  
  12. javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
  13. javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
  14. javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
  15.  
  16. objectMapper.registerModule(module);
  17. objectMapper.registerModule(javaTimeModule);
  18. objectMapper.setConfig(objectMapper.getDeserializationConfig().with(new ObjectMapperDateFormatExtend(dateFormat)));//反序列化扩展日期格式支持
  19. objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
  20. objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  21. return objectMapper;
  22. }
  23.  
  24. @Bean
  25. RabbitAdmin admin (ConnectionFactory aConnectionFactory) {
  26. return new RabbitAdmin(aConnectionFactory);
  27. }
  28.  
  29. @Bean
  30. MessageConverter jacksonAmqpMessageConverter( ) {
  31. return new Jackson2JsonMessageConverter(objectMapper());
  32. }
  33.  
  34. @Bean
  35. Queue bcwPushControlQueue (RabbitAdmin rabbitAdmin) {
  36. Queue queue = new Queue(Queues.QUEUE_BCW_PUSH);
  37. rabbitAdmin.declareQueue(queue);
  38. return queue;
  39. }
  40. @Bean
  41. Queue bcwPayControlQueue (RabbitAdmin rabbitAdmin) {
  42. Queue queue = new Queue(Queues.QUEUE_BCW_PAY);
  43. rabbitAdmin.declareQueue(queue);
  44. return queue;
  45. }
  46. @Bean
  47. Queue bcwPullControlQueue (RabbitAdmin rabbitAdmin) {
  48. Queue queue = new Queue(Queues.QUEUE_BCW_PULL);
  49. rabbitAdmin.declareQueue(queue);
  50. return queue;
  51. }
  52. /**
  53. * 声明一个交换机
  54. * @return
  55. */
  56. @Bean
  57. TopicExchange controlExchange () {
  58. return new TopicExchange(Exchanges.ExangeTOPIC);
  59. }
  60.  
  61. /**
  62. * 延时重试队列
  63. */
  64. @Bean
  65. public Queue bcwPayControlRetryQueue() {
  66. Map<String, Object> arguments = new HashMap<>();
  67. arguments.put("x-message-ttl", 10 * 1000);
  68. arguments.put("x-dead-letter-exchange", Exchanges.ExangeTOPIC);
  69. // 如果设置死信会以路由键some-routing-key转发到some.exchange.name,如果没设默认为消息发送到本队列时用的routing key
  70. arguments.put("x-dead-letter-routing-key", "queue_bcw.push");
  71. return new Queue("queue_bcw@pay@retry", true, false, false, arguments);
  72. }
  73. /**
  74. * 延时重试队列
  75. */
  76. @Bean
  77. public Queue bcwPushControlRetryQueue() {
  78. Map<String, Object> arguments = new HashMap<>();
  79. arguments.put("x-message-ttl", 10 * 1000);
  80. arguments.put("x-dead-letter-exchange", Exchanges.ExangeTOPIC);
  81. // 如果设置死信会以路由键some-routing-key转发到some.exchange.name,如果没设默认为消息发送到本队列时用的routing key
  82. arguments.put("x-dead-letter-routing-key", "queue_bcw.push");
  83. return new Queue("queue_bcw@push@retry", true, false, false, arguments);
  84. }
  85. /**
  86. * 延时重试队列
  87. */
  88. @Bean
  89. public Queue bcwPullControlRetryQueue() {
  90. Map<String, Object> arguments = new HashMap<>();
  91. arguments.put("x-message-ttl", 10 * 1000);
  92. arguments.put("x-dead-letter-exchange", Exchanges.ExangeTOPIC);
  93. // 如果设置死信会以路由键some-routing-key转发到some.exchange.name,如果没设默认为消息发送到本队列时用的routing key
  94. // arguments.put("x-dead-letter-routing-key", "queue_bcw");
  95. return new Queue("queue_bcw@pull@retry", true, false, false, arguments);
  96. }
  97. @Bean
  98. public Binding bcwPayControlRetryBinding() {
  99. return BindingBuilder.bind(bcwPushControlRetryQueue()).to(controlExchange()).with("queue_bcw.pay.retry");
  100. }
  101. @Bean
  102. public Binding bcwPushControlRetryBinding() {
  103. return BindingBuilder.bind(bcwPushControlRetryQueue()).to(controlExchange()).with("queue_bcw.push.retry");
  104. }
  105. @Bean
  106. public Binding bcwPullControlRetryBinding() {
  107. return BindingBuilder.bind(bcwPushControlRetryQueue()).to(controlExchange()).with("queue_bcw.pull.retry");
  108. }
  109.  
  110. /**
  111. * 队列绑定并关联到RoutingKey
  112. *
  113. * @param queueMessages 队列名称
  114. * @param exchange 交换机
  115. * @return 绑定
  116. */
  117. @Bean
  118. Binding bcwPushBindingQueue(@Qualifier("bcwPushControlQueue") Queue queueMessages,@Qualifier("controlExchange") TopicExchange exchange) {
  119. return BindingBuilder.bind(queueMessages).to(exchange).with("queue_bcw.push");
  120. }
  121. /**
  122. * 队列绑定并关联到RoutingKey
  123. *
  124. * @param queueMessages 队列名称
  125. * @param exchange 交换机
  126. * @return 绑定
  127. */
  128. @Bean
  129. Binding bcwPayBindingQueue(@Qualifier("bcwPayControlQueue") Queue queueMessages, @Qualifier("controlExchange") TopicExchange exchange) {
  130. return BindingBuilder.bind(queueMessages).to(exchange).with("queue_bcw.pay");
  131. }
  132. /**
  133. * 队列绑定并关联到RoutingKey
  134. *
  135. * @param queueMessages 队列名称
  136. * @param exchange 交换机
  137. * @return 绑定
  138. */
  139. @Bean
  140. Binding bcwPullBindingQueue(@Qualifier("bcwPullControlQueue") Queue queueMessages,@Qualifier("controlExchange") TopicExchange exchange) {
  141. return BindingBuilder.bind(queueMessages).to(exchange).with("queue_bcw.pull");
  142. }
  143.  
  144. @Bean
  145. @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
  146. public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
  147. SimpleRabbitListenerContainerFactoryConfigurer configurer,
  148. ConnectionFactory connectionFactory) {
  149. SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
  150. configurer.configure(factory, connectionFactory);
  151. factory.setMessageConverter(jacksonAmqpMessageConverter());
  152. return factory;
  153. }

下面就是我们的主题,定时任务使用的是org.springframework.scheduling

  1. /**
  2. * 手动确认消息的,定时获取队列消息实现
  3. */
  4. public abstract class QuartzSimpleMessageListenerContainer extends SimpleMessageListenerContainer {
  5. protected final Logger logger = LoggerFactory.getLogger(getClass());
  6. private List<Message> body = new LinkedList<>();
  7. public long start_time;
  8. private Channel channel;
  9. @Autowired
  10. private ObjectMapper objectMapper;
  11. @Autowired
  12. private RabbitTemplate rabbitTemplate;
  13.  
  14. public QuartzSimpleMessageListenerContainer() {
  15. // 手动确认
  16. this.setAcknowledgeMode(AcknowledgeMode.MANUAL);
  17.  
  18. this.setMessageListener((ChannelAwareMessageListener) (message,channel) -> {
  19. long current_time = System.currentTimeMillis();
  20. int time = (int) ((current_time - start_time)/1000);
  21. logger.info("====接收到{}队列的消息=====",message.getMessageProperties().getConsumerQueue());
  22. Long retryCount = getRetryCount(message.getMessageProperties());
  23. if (retryCount > 3) {
  24. logger.info("====此消息失败超过三次{}从队列的消息删除=====",message.getMessageProperties().getConsumerQueue());
  25. try {
  26. channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
  27. } catch (IOException ex) {
  28. ex.printStackTrace();
  29. }
  30. return;
  31. }
  32.  
  33. this.body.add(message);
  34. /**
  35. * 判断数组数据是否满了,判断此监听器时间是否大于执行时间
  36. * 如果在最后延时时间段内没有业务消息,此监听器会一直开着
  37. */
  38. if(body.size()>=3 || time>60){
  39. this.channel = channel;
  40. callback();
  41. }
  42. });
  43.  
  44. }
  45. private void callback(){
  46. // channel = getChannel(getTransactionalResourceHolder());
  47. if(body.size()>0 && channel !=null && channel.isOpen()){
  48. try {
  49. callbackWork();
  50. }catch (Exception e){
  51. logger.error("推送数据出错:{}",e.getMessage());
  52.  
  53. body.stream().forEach(message -> {
  54. Long retryCount = getRetryCount(message.getMessageProperties());
  55. if (retryCount <= 3) {
  56. logger.info("将消息置入延时重试队列,重试次数:" + retryCount);
  57. rabbitTemplate.convertAndSend(Exchanges.ExangeTOPIC, message.getMessageProperties().getReceivedRoutingKey()+".retry", message);
  58. }
  59. });
  60.  
  61. } finally{
  62.  
  63. logger.info("flsher too data");
  64.  
  65. body.stream().forEach(message -> {
  66. //手动acknowledge
  67. try {
  68. channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
  69. } catch (IOException e) {
  70. logger.error("手动确认消息失败!");
  71. e.printStackTrace();
  72. }
  73. });
  74.  
  75. body.clear();
  76. this.stop();
  77.  
  78. }
  79. }
  80.  
  81. }
  82. abstract void callbackWork() throws Exception;
  83. /**
  84. * 获取消息失败次数
  85. * @param properties
  86. * @return
  87. */
  88. private long getRetryCount(MessageProperties properties){
  89. long retryCount = 0L;
  90. Map<String,Object> header = properties.getHeaders();
  91. if(header != null && header.containsKey("x-death")){
  92. List<Map<String,Object>> deaths = (List<Map<String,Object>>)header.get("x-death");
  93. if(deaths.size()>0){
  94. Map<String,Object> death = deaths.get(0);
  95. retryCount = (Long)death.get("count");
  96. }
  97. }
  98. return retryCount;
  99. }
  100.  
  101. @Override
  102. @Scheduled(cron = "0 0/2 * * * ? ")
  103. public void start() {
  104. logger.info("start push data scheduled!");
  105. //初始化数据,将未处理的调用stop方法,返还至rabbit
  106. body.clear();
  107. super.stop();
  108. start_time = System.currentTimeMillis();
  109. super.start();
  110.  
  111. logger.info("end push data scheduled!");
  112. }
  113.  
  114. public List<WDNJPullOrder> getBody() {
  115.  
  116. List<WDNJPullOrder> collect = body.stream().map(data -> {
  117. byte[] body = data.getBody();
  118. WDNJPullOrder readValue = null;
  119. try {
  120. readValue = objectMapper.readValue(body, new TypeReference<WDNJPullOrder>() {
  121. });
  122. } catch (IOException e) {
  123. logger.error("处理数据出错{}",e.getMessage());
  124. }
  125. return readValue;
  126. }
  127. ).collect(Collectors.toList());
  128.  
  129. return collect;
  130.  
  131. }
  132.  
  133. }

后续

当然定时任务的启动,你可以写到相关rabbit容器实现的里面,但是这里并不是很需要,所以对于这个的小改动,同学你可以自己实现

  1. @Scheduled(cron = "0 0/2 * * * ? ")
  2.  
  3. public void start()
  1.  

使用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--day25--抽象类

    什么是抽象类: 抽象类: #一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' ...

  2. spring boot + thymeleaf 乱码问题

    spring boot + thymeleaf 乱码问题 hellotrms 发布于 2017/01/17 15:27 阅读 1K+ 收藏 0 答案 1 开发四年只会写业务代码,分布式高并发都不会还做 ...

  3. [Ramda] Handle Errors in Ramda Pipelines with tryCatch

    Handling your logic with composable functions makes your code declarative, leading to code that's ea ...

  4. H3C查看文件内容

    <H3C>more logfile.log 创建一个目录 <H3C>mkdir gaochengwang 重命名目录及文件 <H3C>rename wnt 0904 ...

  5. Linux 内核 启动时间

    为见到 PCI 如何工作的, 我们从系统启动开始, 因为那是设备被配置的时候. 当一个 PCI 设备上电时, 硬件保持非激活. 换句话说, 设备只响应配置交易. 在上电时, 设备没有内存并且没有 I/ ...

  6. C语言 屏幕截图 (GDI)

    截取全屏幕 #include <windows.h>   void echo(CHAR *str); int CaptureImage(HWND hWnd, CHAR *dirPath, ...

  7. .net core 读取Excal文件数据及注意事项

    添加ExcelDataReader.DataSet引用. 调用下列方法: public class XlsHelper { public static System.Data.DataSet GetX ...

  8. slim的中间件

    slim中间件的作用简单来说就是过滤数据,request过来的数据要经过中间件才能到达内部,然后内部数据要到达外部的时候,也要经过中间件,正常通过才能到达外部

  9. 微信小程序酒店日历超强功能

    首先利用date拿到年月日 月记得+1 ,因为是从0开始的 先遍历月份,跨年年+1 ,月归至1: 然后遍历天数, lastDat = new Date(val.year,val.month,0).ge ...

  10. 剑指Offer-62.数据流中的中位数(C++/Java)

    题目: 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值.我们使 ...