rabbitmq消息队列,消息发送失败,消息持久化,消费者处理失败相关
转:https://blog.csdn.net/u014373554/article/details/92686063
项目是使用springboot项目开发的,前是代码实现,后面有分析发送消息失败、消息持久化、消费者失败处理方法和发送消息解决方法及手动确认的模式
先引入pom.xml
<!--rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
application 配置文件
spring:
rabbitmq:
host: IP地址
port: 5672
username: 用户名
password: 密码 RabbitConfig配置文件
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope; /**
Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输,
Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
Queue:消息的载体,每个消息都会被投到一个或多个队列。
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来.
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离。
Producer:消息生产者,就是投递消息的程序.
Consumer:消息消费者,就是接受消息的程序.
Channel:消息通道,在客户端的每个连接里,可建立多个channel.
*/
@Configuration
@Slf4j
public class RabbitConfig { @Value("${spring.rabbitmq.host}")
private String host; @Value("${spring.rabbitmq.port}")
private int port; @Value("${spring.rabbitmq.username}")
private String username; @Value("${spring.rabbitmq.password}")
private String password; public static final String EXCHANGE_A = "my_mq_exchange_A";
public static final String EXCHANGE_B = "my_mq_exchange_B";
public static final String EXCHANGE_C = "my_mq_exchange_C"; public static final String QUEUE_A="QUEUE_A";
public static final String QUEUE_B="QUEUE_B";
public static final String QUEUE_C="QUEUE_C"; public static final String ROUTINGKEY_A = "spring-boot-routingKey_A";
public static final String ROUTINGKEY_B = "spring-boot-routingKey_B";
public static final String ROUTINGKEY_C = "spring-boot-routingKey_C"; @Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host,port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost("/");
connectionFactory.setPublisherConfirms(true); //设置发送消息失败重试
connectionFactory.setChannelCacheSize(100);//解决多线程发送消息 return connectionFactory;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public RabbitTemplate rabbitTemplate(){
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setMandatory(true); //设置发送消息失败重试
return template; }
//配置使用json转递数据
@Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
/*public SimpleMessageListenerContainer messageListenerContainer(){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory()); MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageHandler());
adapter.setDefaultListenerMethod(new Jackson2JsonMessageConverter());
return container;
}*/ /**
* 针对消费者配置
* 1. 设置交换机类型
* 2. 将队列绑定到交换机
* FanoutExchange: 将消息分发到所有的绑定队列,无 routingkey的概念
* HeadersExchange: 通过添加属性key - value匹配
* DirectExchange: 按照routingkey分发到指定队列
* TopicExchange : 多关键字匹配
* @return
*/
@Bean
public DirectExchange defaultExchange(){
return new DirectExchange(EXCHANGE_A,true,false);
} @Bean
public Queue queueA(){
return new Queue(QUEUE_A,true);// 队列持久化
} @Bean
public Queue queueB(){
return new Queue(QUEUE_B,true);// 队列持久化
} /**
* 一个交换机可以绑定多个消息队列,也就是消息通过一个交换机,可以分发到不同的队列当中去。
* @return
*/
@Bean
public Binding binding(){
return BindingBuilder.bind( queueA()).to(defaultExchange()).with(RabbitConfig.ROUTINGKEY_A);
} @Bean
public Binding bindingB(){
return BindingBuilder.bind( queueB()).to(defaultExchange()).with(RabbitConfig.ROUTINGKEY_A);
} }
生成者
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID; /**
* 生产者
*/
@Component
@Slf4j
public class ProducerMessage implements RabbitTemplate.ConfirmCallback , RabbitTemplate.ReturnCallback{ private RabbitTemplate rabbitTemplate; @Autowired
public ProducerMessage(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
rabbitTemplate.setConfirmCallback(this::confirm); //rabbitTemplate如果为单例的话,那回调就是最后设置的内容
rabbitTemplate.setReturnCallback(this::returnedMessage);
rabbitTemplate.setMandatory(true);
} public void sendMsg (Object content){
CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend(RabbitConfig.EXCHANGE_A,RabbitConfig.ROUTINGKEY_A,content,correlationId); } /**
* 消息发送到队列中,进行消息确认
* @param correlationData
* @param ack
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info(" 消息确认的id: " + correlationData);
if(ack){
log.info("消息发送成功");
//发送成功 删除本地数据库存的消息
}else{
log.info("消息发送失败:id "+ correlationData +"消息发送失败的原因"+ cause);
// 根据本地消息的状态为失败,可以用定时任务去处理数据 }
} /**
* 消息发送失败返回监控
* @param message
* @param i
* @param s
* @param s1
* @param s2
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
log.info("returnedMessage [消息从交换机到队列失败] message:"+message); }
}
消费者
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException; /**
* 消费者
*/ @Slf4j
@Component public class ComsumerMessage { @RabbitListener(queues = RabbitConfig.QUEUE_A)
public void handleMessage(Message message,Channel channel) throws IOException{
try {
String json = new String(message.getBody());
JSONObject jsonObject = JSONObject.fromObject(json);
log.info("消息了【】handleMessage" + json);
int i = 1/0;
//业务处理。
/**
* 防止重复消费,可以根据传过来的唯一ID先判断缓存数据中是否有数据
* 1、有数据则不消费,直接应答处理
* 2、缓存没有数据,则进行消费处理数据,处理完后手动应答
* 3、如果消息 处理异常则,可以存入数据库中,手动处理(可以增加短信和邮件提醒功能)
*/ //手动应答
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}catch (Exception e){
log.error("消费消息失败了【】error:"+ message.getBody());
log.error("OrderConsumer handleMessage {} , error:",message,e);
// 处理消息失败,将消息重新放回队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,true);
} } }
发送消息:调用生成的方法
import com.zz.blog.BlogApplicationTests;
import com.zz.blog.mq.ProducerMessage;
import net.sf.json.JSONObject;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.UUID;
public class Message extends BlogApplicationTests {
@Autowired
private ProducerMessage producerMessage; @Test
public void sendMessage(){
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", UUID.randomUUID().toString());
jsonObject.put("name","TEST");
jsonObject.put("desc","订单已生成");
//防止发送消息失败,将发送消息存入本地。 producerMessage.sendMsg(jsonObject.toString()); }
}
rabbitTemplate的发送消息流程是这样的:
1 发送数据并返回(不确认rabbitmq服务器已成功接收)
2 异步的接收从rabbitmq返回的ack确认信息
3 收到ack后调用confirmCallback函数
注意:在confirmCallback中是没有原message的,所以无法在这个函数中调用重发,confirmCallback只有一个通知的作用
在这种情况下,如果在2,3步中任何时候切断连接,我们都无法确认数据是否真的已经成功发送出去,从而造成数据丢失的问题。
最完美的解决方案只有1种:
使用rabbitmq的事务机制。
但是在这种情况下,rabbitmq的效率极低,每秒钟处理的message在几百条左右。实在不可取。
基于上面的分析,我们使用一种新的方式来做到数据的不丢失。
在rabbitTemplate异步确认的基础上
1 在本地缓存已发送的message
2 通过confirmCallback或者被确认的ack,将被确认的message从本地删除
3 定时扫描本地的message,如果大于一定时间未被确认,则重发
当然了,这种解决方式也有一定的问题:
想象这种场景,rabbitmq接收到了消息,在发送ack确认时,网络断了,造成客户端没有收到ack,重发消息。(相比于丢失消息,重发消息要好解决的多,我们可以在consumer端做到幂等)。
消息存入本地:在message 发消息的写数据库中。
消息应答成功,则删除本地消息,失败更改消息状态,可以使用定时任务去处理。
消息持久化:
消费者:
/**
* 防止重复消费,可以根据传过来的唯一ID先判断缓存数据库中是否有数据
* 1、有数据则不消费,直接应答处理
* 2、缓存没有数据,则进行消费处理数据,处理完后手动应答
* 3、如果消息 处理异常则,可以存入数据库中,手动处理(可以增加短信和邮件提醒功能)
*/
rabbitmq消息队列,消息发送失败,消息持久化,消费者处理失败相关的更多相关文章
- Spring boot实战项目整合阿里云RocketMQ (非开源版)消息队列实现发送普通消息,延时消息 --附代码
一.为什么选择RocketMQ消息队列? 首先RocketMQ是阿里巴巴自研出来的,也已开源.其性能和稳定性从双11就能看出来,借用阿里的一句官方介绍:历年双 11 购物狂欢节零点千万级 TPS.万亿 ...
- RabbitMQ系列(四)RabbitMQ事务和Confirm发送方消息确认——深入解读
RabbitMQ事务和Confirm发送方消息确认--深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...
- RabbitMQ事务和Confirm发送方消息确认
RabbitMQ事务和Confirm发送方消息确认——深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...
- 【MQ】java 从零开始实现消息队列 mq-02-如何实现生产者调用消费者?
前景回顾 上一节我们学习了如何实现基于 netty 客服端和服务端的启动. [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]java 从零开始实现消息队列 mq-02-如何实现生产者调用 ...
- SpringBoot集成RabbitMQ消息队列搭建与ACK消息确认入门
1.RabbitMQ介绍 RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性.扩展性.高可用性等方面表现不俗.Rabbi ...
- RabbitMQ消息队列(六)-消息任务分发与消息ACK确认机制(.Net Core版)
在前面一章介绍了在.Net Core中如何使用RabbitMQ,至此入门的的部分就完成了,我们内心中一定还有很多疑问:如果多个消费者消费同一个队列怎么办?如果这几个消费者分任务的权重不同怎么办?怎么把 ...
- RabbitMQ消息队列(四): 消息路由
1. 路由: 前面的示例中,我们或得到的消息为广播消息,但是无法更精确的获取消息的子集,比如:日志消息,worker1只需要error级别的日志, 而worker2需要info,warning,err ...
- Kafka 消息队列系列之分布式消息队列Kafka
介绍 ApacheKafka®是一个分布式流媒体平台.这到底是什么意思呢?我们认为流媒体平台具有三个关键功能:它可以让你发布和订阅记录流.在这方面,它类似于消息队列或企业消息传递系统.它允许您以容 ...
- php消息队列之 think queue消息队列初体验
使用thinkphp 5的 消息队列 think queue ● php think queue:listen --queue queuename ● php think queue:work -- ...
- 4、网络并发编程--僵尸进程、孤儿进程、守护进程、互斥锁、消息队列、IPC机制、生产者消费者模型、线程理论与实操
昨日内容回顾 操作系统发展史 1.穿孔卡片 CPU利用率极低 2.联机批处理系统 CPU效率有所提升 3.脱机批处理系统 CPU效率极大提升(现代计算机雏形) 多道技术(单核CPU) 串行:多个任务依 ...
随机推荐
- CodeForces 494B Obsessive String ——(字符串DP+KMP)
这题的题意就很晦涩.题意是:问有多少种方法,把字符串s划分成不重叠的子串(可以不使用完s的所有字符,但是这些子串必须不重叠),使得t串是所有这些新串的子串.譬如第一个样例,"ababa&qu ...
- Nginx内置变量及正则语法
对于很多Nginx初学者来说,配置文件是必须要看懂的.但是当公司的Nginx配置文件放在你面前的时候你总会被一些带着"$"符号和一大推看不懂的的正则给正懵逼.没错带着"$ ...
- xpath 轴定位表达方式
xpath的使用基本语法: 1.// 从根节点开始,查找对象是全文. 2./ 从当前标签的路径开始查找 3.text()获取当前路径下的文本 4.@+类名或者id名 查找类名或者id的名字 5. .一 ...
- jQuery 的on()方法
jQuery 的on()方法 一.总结 一句话总结: 1.普通添加事件:$("a").on("click", function () {执行的代码}) 2.未创 ...
- 重新部署环境之后,总是提示表doesn't have a default value
SQLSTATE[HY000]: General error: 1364 Field 'college' doesn't have a default value 数据库严格模式开启了,解决方法: ...
- Activiti核心API
ProcessEngine 说明: 1) 在Activiti中最核心的类,其他的类都是由他而来. 2) 产生方式: 在前面看到了两种创建ProcessEngine(流程引擎)的方式 ...
- SpringBoot学习之一 Unable to find a single main class from the following candidates
在启动SpringBoot项目是报错 Unable to find a single main class from the following candidates [boot.myboot.Sam ...
- xampp 启动mysql 显示busy
1.端口被占用问题:2.默认3306端口并没有被占用:3.删除xampp\mysql\data\下的ibdata1再重启:4.没装其他mysql.
- socket之IO多路复用
概述 目的:同一个线程同时处理多个IO请求. 本文以python的select模块来实现socket编程中一个server同时处理多个client请求的问题. web框架tornado就是以此实现多客 ...
- 最新 完美世界java校招面经 (含整理过的面试题大全)
从6月到10月,经过4个月努力和坚持,自己有幸拿到了网易雷火.京东.去哪儿.完美世界等10家互联网公司的校招Offer,因为某些自身原因最终选择了完美世界.6.7月主要是做系统复习.项目复盘.Leet ...