Spring Boot 整合RabbitMQ
RabbitMQ 相关介绍
RabbitMQ 是一款用于接收、存储和转发消息额度开源中间件,实际应用系统中可以实现消息分发、异步通信和业务模块解耦功能。RabbitMQ 核心要点其实在于消息、消息模型、生产者和消费者。RabbitMQ 的消息模型有许多种,如基于FanoutExchange 消息模型,基于DirectExchange 的消息模型和TopicExchange。这些消费模型都有一个共性,那就是他们几乎都包含交换机、路由和队列等基础组件。
RabbitMQ 名词介绍
- 生产者 : 用于生产发送消息
- 消费者: 用于监听、接受消息、消费消息和处理消息
- 消息: 可以看做是实际的传输数据
- 列队: 消息的暂存区或者存储区,可以看做一个中转站。
- 交换机:同样是消息的中转站,用于首次接受和 分发消息。
- 路由:相当于秘钥、地址或者“第三方”,一般不得单独使用,而是和交换机绑定一起使用,将消息路由到指定列队
导入springboot - rabbit 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
yml 文件的配置
spring :
rabbitmq:
host: 192.168.0.133
port: 5672
username: guest
password: guest
virtual-host: /
## 主要配置列队 路由 和交换机
mq :
ev: local
basic :
info :
queue :
name : ${mq.ev}.middleware.mq.basic.info.queue
exchange:
name : ${mq.ev}.middleware.mq.basic.info.exchange
routing:
key:
name: ${mq.ev}.middleware.mq.basic.info.routing.key
配置rabbitmq 的消费发送模式
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class RabbitMQConfig {
/** 定义日志*/
private static final Logger loger = LoggerFactory.getLogger(RabbitMQConfig.class);
/** 自动配置RabbitMQ 的连接工厂*/
@Autowired
private CachingConnectionFactory cachingConnectionFactory;
//自动装配消息监听所在的容器工厂配置类实例
@Autowired
private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigure;
/**
* 单一消费者配置实例
* @return
*/
@Bean("singleListenerContainer")
public SimpleRabbitListenerContainerFactory listenerContainerFactory(){
//定义消息监听所在的容器工厂
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
/** 设置容器工厂所用的实例*/
factory.setConnectionFactory(cachingConnectionFactory);
/** 设置消息在传输中的格式,这里采用JSON 的格式进行传输*/
factory.setMessageConverter(new Jackson2JsonMessageConverter());
/** 设置并发消费者的初始化数量,这里设置为一个*/
factory.setConcurrentConsumers(1);
/** 设置消费者最大并发数量.,这里设置5个*/
factory.setMaxConcurrentConsumers(5);
/** 设置并发消费者实例中每个实例拉取的消息数量*/
factory.setPrefetchCount(1);
return factory;
}
/**
* 多消费者配置实例
* @return
*/
@Bean("multiListnerContainer")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(){
//定义消息监听工厂所在的容器
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
/** 设置容器工厂所用的实例*/
factoryConfigure.configure(factory,cachingConnectionFactory);
/** 设置消息格式的传输格式,*/
/** 设置消息在传输中的格式,这里采用JSON 的格式进行传输*/
factory.setMessageConverter(new Jackson2JsonMessageConverter());
/** 设置消息的确认模式。这里为NONE ,表示不需要确认消费*/
factory.setAcknowledgeMode(AcknowledgeMode.NONE);
/** 设置并发消费者的初始化数量,这里设置为一个*/
factory.setConcurrentConsumers(10);
/** 设置消费者最大并发数量.,这里设置5个*/
factory.setMaxConcurrentConsumers(15);
/** 设置并发消费者实例中每个实例拉取的消息数量*/
factory.setPrefetchCount(10);
return factory;
}
/**
* 自定义配置RabbitMQ 发消息的操作组件RabbitTemplate
* @return
*/
@Bean
public RabbitTemplate rabbitTemplate(){
// 设置“发送消息后进行确认”
cachingConnectionFactory.setPublisherConfirms(true);
/** 设置“发送消息后返回确认信息”*/
cachingConnectionFactory.setPublisherReturns(true);
/** 构造发送消息组件实例对象*/
RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
rabbitTemplate.setMandatory(true);
// 发消息后如果成功,则输出“消息发送成功”的反馈信息
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
loger.info("消息发送成功:correlationData({}),ack({}),cause({})",correlationData,b,s);
}
});
/** 发送消息后,如果发送失败,则输出“消息发送失败-消息丢失” 的反馈信息*/
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
loger.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message({})",s1,s2,s,message);
}
});
return rabbitTemplate;
}
// 定义读取配置文件的环境变量实例
@Autowired
private Environment environment;
/**
* c创建简单的消息模型:队列 、 交互机 和路由
* @return
*/
@Bean(name = "basicQueue")
public Queue basicQueue(){
return new Queue(environment.getProperty("mq.basic.info.queue.name"),true);
}
@Bean
public DirectExchange basicExchange(){
return new DirectExchange(environment.getProperty("mq.basic.info.exchange.name"),true,false);
}
/**
* 创建绑定
* @return
*/
@Bean
public Binding basicBinding() {
return BindingBuilder
.bind(basicQueue())
.to(basicExchange())
.with(environment.getProperty("mq.basic.info.routing.key.name"));
}
}
生产者类
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
* 基本消息模型 - 生产者
*/
@Component
public class BasciPublisher {
private static final Logger loger = LoggerFactory.getLogger(BasciPublisher.class);
/** JSON 序列化和反序列化*/
@Autowired
private ObjectMapper objectMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
/** 定义环境变量读取*/
@Autowired
private Environment environment;
public void sendMsg(String message){
if (! Strings.isNullOrEmpty(message)) {
try {
/*定义消息传输的格式*/
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplate.setExchange(environment.getProperty("mq.basic.info.exchane.name"));
rabbitTemplate.setRoutingKey(environment.getProperty()"mq.basic.info.routing.key.name");
Message msg = MessageBuilder.withBody(message.getBytes("utf-8")).build();
rabbitTemplate.convertAndSend(msg);
System.out.println("基本消息模型-生产者-发送消息:"+message);
loger.info("基本消息模型-生产者-发送消息:{}",message);
}catch (Exception e){
loger.info("基本消息模型-生产者-发送消息异常:{}",message,e.fillInStackTrace());
}
}
}
}
消费者类 监听队列
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
/**
* 基本消息模型- 消费者
*/
@Component
public class BasicConsumer {
private static final Logger log = LoggerFactory.getLogger(BasicConsumer.class);
@Autowired
private ObjectMapper objectMapper;
@RabbitListener(queues = "${mq.basic.info.queue.name}",containerFactory = "singleListenerContainer")
public void consumeMsg(@Payload byte[] msg){
try {
String message = new String(msg,"utf-8");
log.info("基本消息模型- 消费者-监听消息:{}",message);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
测试类
package application;
import com.ruoyi.RuoYiApplication;
import com.ruoyi.framework.rabbitmq.BasciPublisher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RuoYiApplication.class)
public class rabbitMqTest {
@Autowired
private BasciPublisher basciPublisher;
@Test
public void test1(){
String msg = "~~~~~~~~~~ 我是一串数据 ~~~~~~~~~~~~";
basciPublisher.sendMsg(msg);
}
}
这就成功的监听到了列队里面的消息。
RabbitMQ 在消息的发送、传输和接收的过程中,可以保证消息成功发送不会丢失,以及确认被消费。
消息高可用和确认消费
使用RabbitMQ 在实际的应用过程中,如果配置和使用不当,则会出现各种令人头疼的问题
- 发送出去的消息不知道有没有成功,即采用RabbitTemplate操作组件发送消息的时候,开发者认为消息已经发送出去了,然而在某些情况下却很可能发送失败。(如:交换机、路由、和队列绑定结构的消息模型不存在)
- 由于某些特殊原因,RabbitMQ服务出现宕机和崩溃等问题,导致其需要执行重启操作。如果列队中仍有大量的消息没有被消费,则很有可能在重启RabbitMQ 服务的过程中发生消息丢失的现象。
- 消费者在监听处理消息的时候,可能会出现监听失败或者直接崩溃等问题,导致消息所在的列队找不到对应的消费者而不断的重新入队,最终被重复消费的现象。
针对第一种情况Rabbit MQ 会在要求生产者发送消息之后进行“发送确认”,当确认成功是即代表发送出去了!
如下
/**
* 自定义配置RabbitMQ 发消息的操作组件RabbitTemplate
* @return
*/
@Bean
public RabbitTemplate rabbitTemplate(){
// 设置“发送消息后进行确认”
cachingConnectionFactory.setPublisherConfirms(true);
/** 设置“发送消息后返回确认信息”*/
cachingConnectionFactory.setPublisherReturns(true);
/** 构造发送消息组件实例对象*/
RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
rabbitTemplate.setMandatory(true);
// 发消息后如果成功,则输出“消息发送成功”的反馈信息
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
loger.info("消息发送成功:correlationData({}),ack({}),cause({})",correlationData,b,s);
}
});
/** 发送消息后,如果发送失败,则输出“消息发送失败-消息丢失” 的反馈信息*/
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
loger.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message({})",s1,s2,s,message);
}
});
return rabbitTemplate;
}
针对第二种情况,即如何保证RabbitMQ 队列种的消息不丢失;RabbitMq在创建创建队列、交换机是设置其持久化参数为true,即durable 设置为true。除此之外在创建消息是RabbitMQ要求设置消息的持久化模式为:“持久化”。
/**
* c创建简单的消息模型:队列 、 交互机 和路由
* @return
*/
@Bean(name = "basicQueue")
public Queue basicQueue(){
return new Queue(environment.getProperty("mq.basic.info.queue.name"),true);
}
@Bean
public DirectExchange basicExchange(){
return new DirectExchange(environment.getProperty("mq.basic.info.exchange.name"),true,false);
}
针对第三种情况,即如何保证消息能够被准备消费、不重复消费,RabbitMQ提供了“消费应答机制”,即ACK模式。分别为NONE(无需确认)、AUTO(自动确认)、MANUAL(手动确认),不同的机制对应不同的 场景。在实际的生产环境种一般都是消息的确认机制,只有当消息被确认消费后,消息才会从列队种被移除。