转: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消息队列,消息发送失败,消息持久化,消费者处理失败相关的更多相关文章

  1. Spring boot实战项目整合阿里云RocketMQ (非开源版)消息队列实现发送普通消息,延时消息 --附代码

    一.为什么选择RocketMQ消息队列? 首先RocketMQ是阿里巴巴自研出来的,也已开源.其性能和稳定性从双11就能看出来,借用阿里的一句官方介绍:历年双 11 购物狂欢节零点千万级 TPS.万亿 ...

  2. RabbitMQ系列(四)RabbitMQ事务和Confirm发送方消息确认——深入解读

    RabbitMQ事务和Confirm发送方消息确认--深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...

  3. RabbitMQ事务和Confirm发送方消息确认

    RabbitMQ事务和Confirm发送方消息确认——深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...

  4. 【MQ】java 从零开始实现消息队列 mq-02-如何实现生产者调用消费者?

    前景回顾 上一节我们学习了如何实现基于 netty 客服端和服务端的启动. [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]java 从零开始实现消息队列 mq-02-如何实现生产者调用 ...

  5. SpringBoot集成RabbitMQ消息队列搭建与ACK消息确认入门

    1.RabbitMQ介绍 RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性.扩展性.高可用性等方面表现不俗.Rabbi ...

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

    在前面一章介绍了在.Net Core中如何使用RabbitMQ,至此入门的的部分就完成了,我们内心中一定还有很多疑问:如果多个消费者消费同一个队列怎么办?如果这几个消费者分任务的权重不同怎么办?怎么把 ...

  7. RabbitMQ消息队列(四): 消息路由

    1. 路由: 前面的示例中,我们或得到的消息为广播消息,但是无法更精确的获取消息的子集,比如:日志消息,worker1只需要error级别的日志, 而worker2需要info,warning,err ...

  8. Kafka 消息队列系列之分布式消息队列Kafka

    介绍 ApacheKafka®是一个分布式流媒体平台.这到底是什么意思呢?我们认为流媒体平台具有三个关键功能:它可以让你发布和订阅记录流.在这方面,它类似于消​​息队列或企业消息传递系统.它允许您以容 ...

  9. php消息队列之 think queue消息队列初体验

    使用thinkphp 5的  消息队列 think queue ● php think queue:listen --queue queuename ● php think queue:work -- ...

  10. 4、网络并发编程--僵尸进程、孤儿进程、守护进程、互斥锁、消息队列、IPC机制、生产者消费者模型、线程理论与实操

    昨日内容回顾 操作系统发展史 1.穿孔卡片 CPU利用率极低 2.联机批处理系统 CPU效率有所提升 3.脱机批处理系统 CPU效率极大提升(现代计算机雏形) 多道技术(单核CPU) 串行:多个任务依 ...

随机推荐

  1. Atcoder ABC 139B

    Atcoder ABC 139B 题意: 一开始有1个插口,你的插排有 $ a $ 个插口,你需要 $ b $ 个插口,问你最少需要多少个插排. 解法: 暴力模拟. CODE: #include< ...

  2. 微服务中使用MQ——RabbitMQ

    概念 什么是消息 消息是指在两个独立的系统间传递的数据.这两个系统可以是两台计算机,也可以是两个进程. 消息是平台无关和语言无关的! 什么是队列 队列是一种数据结构,内部是用数组或链表实现的, 队列的 ...

  3. 跨域方案JSONP与CORS的各自优缺点以及应用场景

    转自 https://www.zhihu.com/question/41992168/answer/217903179 首先明确:JSONP与CORS的使用目的相同,并且都需要服务端和客户端同时支持, ...

  4. 使用pycharm发布python程序到ubuntu中运行

    前提条件: 1.ubuntu安装了vsftpd,可以参考:https://www.cnblogs.com/xienb/p/9322805.html 2.安装专业版pycharm 步骤: 1.新建一个P ...

  5. Servlet的概述

    A: Servlet的概述: server applet , 是一个运行在服务器端的小应用程序 B: 就是一个接口,作用: servlet 通常通过 HTTP(超文本传输协议)接收和响应来自 Web ...

  6. C#中用委托实现C++的回调函数

    C++中抓图回调函数 void (CALLBACK* DisplayCBFun)(long nPort,char * pBuf,long nSize,long nWidth,long nHeight, ...

  7. Git Command之Code Review

    原文链接 准备 Step 1. Create a team and add a teammate Step 2. Create a repository with some content 应用 Cl ...

  8. 图解 HTTP 笔记(二)——简单的 HTTP 协议

    本章主要以 HTTP 1.0 为例,讲解 HTTP 协议的基本结构. 在两台计算机之间使用 HTTP 协议进行通讯时,在一条通讯线路上必定有一端是客户端,另一端则是服务器端. 请求访问文本或图像等资源 ...

  9. JAVA 基础编程练习题25 【程序 25 求回文数】

    25 [程序 25 求回文数] 题目:一个 5 位数,判断它是不是回文数.即 12321 是回文数,个位与万位相同,十位与千位相同. package cskaoyan; public class cs ...

  10. linux rz sz替代方案

    SFTP是基于SSH的文件传输协议,与ZMODEM相比具有更加安全且更为快速的文件传输功能. 如何利用SFTP接收文件: 1. 在本地提示以sftp命令登陆拟要接收文件的主机.Xshell:\> ...