一.安装RabbitMQ
git链接:https://github.com/a870368162/SpringBoot-RabbitMQ
1.安装erlang。 官网地址:http://www.erlang.org/downloads。
2.安装rabbitmq,官网地址:http://www.rabbitmq.com/download.html。
3.安装成功之后,在开始菜单中找到RabbMQ Server,选择RabbMQ Server start既可启动消息队列
4. 在浏览器输入http://127.0.0.1:15672/, 输入默认用户名“guest”和默认密码"guest"就可以登录管理页面了。
二.Spring Boot整合RabbitMQ
1.在pom.xml里引入 jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.在yml文件中配置好RabbitMQ的相关信息
rabbitmq:
addresses:127.0.0.1:5672
username:guest
password:guest
#开启消息确认模式
publisher-returns: true
publisher-confirms: true
template:
#开启mandatory: true, basic.return方法将消息返还给生产者
mandatory: true
listener:
simple:
#手动ACK
acknowledge-mode: manual
default-requeue-rejected: false
concurrency: 1
max-concurrency: 1
prefetch: 1
retry:
enabled: true
三.消息确认机制以及保证消息的可靠性
- RabbitMQ有事务机制跟消息确认机制,开启事务机制的话,会让 channel 处于 transactional 模式、向其 publish 消息、执行 commit 动作。在这种方式下,事务机制会带来大量的多余开销,并会导致吞吐量下降 250% ,所以推荐使用消息确认机制,这里需要注意一点,事务机制跟确认机制只能开启一个!
- 消息确认机制,client 首先要发送 confirm.select 方法帧。取决于是否设置了 no-wait 属性,broker 会相应的判定是否以 confirm.select-ok 进行应答。一旦在 channel 上使用 confirm.select方法,channel 就将处于确认模式。一旦 channel 处于 确认模式,broker 和 client 都将启动消息计数(以 confirm.select 为基础从 1 开始计数)。broker 会在处理完消息后,在当前channel 上通过发送 basic.ack 的方式对其进行 confirm 。delivery-tag 域的值标识了被 confirm 消息的序列号。broker 也可以通过设置 basic.ack 中的 multiple 域来表明到指定序列号为止的所有消息都已被 broker 正确的处理了。
- 开启消息确认模式,队列持久化,交换机持久化,消息持久化,及可保证消息的可靠性(极端情况除外,比如消息队列中的消息在持久化硬盘的过程中宕机,此时的消息可能会丢失) 代码实现如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.DelayQueue;
/**
* @消息队列配置
* @Autor zxf
* @Date 2019/8/15
*/
@Configuration
public class QueueConfig {
private static final Logger logger = LoggerFactory.getLogger(QueueConfig.class);
/**
* 创建订阅模式交换机
* @return
*/
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange(RabbitMQConstant.FANOUT_EXCHANGE,true, false);
}
/**
* 创建路由模式交换机
* @return
*/
@Bean
public DirectExchange directExchange() {
return new DirectExchange(RabbitMQConstant.DIRECT_EXCHANGE,true, false);
}
/**
* 创建主题模式交换机
* @return
*/
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(RabbitMQConstant.TOPIC_EXCHANGE,true, false);
}
/**
* 创建死信交换机,跟普通交换机一样,只是死信交换机只用来接收过期的消息
* @return
*/
@Bean
public DirectExchange deadExchange() {
return new DirectExchange(RabbitMQConstant.DEAD_EXCHANGE, true, false);
}
/**
* 创建死信队列,该队列没有消费者,消息会设置过期时间,消息过期后会发送到死信交换机,在由死信交换机转发至处理该消息的队列中
* @return
*/
@Bean
public Queue BeadQueue() {
Map<String, Object> arguments = new HashMap<>(2);
// 死信路由到死信交换器DLX
arguments.put("x-dead-letter-exchange", RabbitMQConstant.DEAD_EXCHANGE);
arguments.put("x-dead-letter-routing-key", RabbitMQConstant.ROUTING_KEY2);
return new Queue(RabbitMQConstant.DEAD_QUEUE, true, false, false, arguments);
}
/**
* 处理死信队列的消费队列
*
*/
@Bean
public Queue consumerBeadQueue() {
return new Queue(RabbitMQConstant.CONSUMER_BEAD_QUEUE, true); // 队列持久
}
/**
* 创建队列1
* @return
*/
@Bean
public Queue Queue1() {
//队列持久化
return new Queue(RabbitMQConstant.QUEUE_1, true);
}
/**
* 创建队列2
* @return
*/
@Bean
public Queue Queue2() {
return new Queue(RabbitMQConstant.QUEUE_2, true);
}
/**
* 订阅模式队列1绑定交换机
* @return
*/
@Bean
public Binding fanoutBinding1() {
return BindingBuilder.bind(Queue1()).to(fanoutExchange());
}
/**
* 订阅模式队列2绑定交换机
* @return
*/
@Bean
public Binding fanoutBinding2() {
return BindingBuilder.bind(Queue2()).to(fanoutExchange());
}
/**
* 路由模式队列1绑定交换机,通过key1发送
* @return
*/
@Bean
public Binding directBinding1() {
return BindingBuilder.bind(Queue1()).to(directExchange()).with(RabbitMQConstant.ROUTING_KEY1);
}
/**
* 路由模式队列2绑定交换机,通过key2发送
* @return
*/
@Bean
public Binding directBinding2() {
return BindingBuilder.bind(Queue2()).to(directExchange()).with(RabbitMQConstant.ROUTING_KEY2);
}
/**
* 主题模式队列1绑定交换机
* 符号“#”匹配一个或多个词,符号“*”匹配一个词。比如“hello.#”能够匹配到“hello.123.456”,但是“hello.*”只能匹配到“hello.123”
* @return
*/
@Bean
public Binding topicBinding1() {
return BindingBuilder.bind(Queue1()).to(topicExchange()).with(RabbitMQConstant.TOPIC_ROUTINGKEY1);
}
/**
* 主题模式队列1绑定交换机
* 符号“#”匹配一个或多个词,符号“*”匹配一个词。比如“hello.#”能够匹配到“hello.123.456”,但是“hello.*”只能匹配到“hello.123”
* @return
*/
@Bean
public Binding topicBinding2() {
return BindingBuilder.bind(Queue2()).to(topicExchange()).with(RabbitMQConstant.TOPIC_ROUTINGKEY2);
}
/**
* 将死信队列与死信交换机绑定,key1
*
* @return
*/
@Bean
public Binding beadQueuebinding() {
return BindingBuilder.bind(BeadQueue()).to(deadExchange()).with(RabbitMQConstant.ROUTING_KEY1);
}
/**
* 将处理死信队列的消费队列与死信交换机绑定 key2
*
* @return
*/
@Bean
public Binding consumerBeadQueuebinding() {
return BindingBuilder.bind(consumerBeadQueue()).to(deadExchange()).with(RabbitMQConstant.ROUTING_KEY2);
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @消息队列发送工具类
* @Autor zxf
* @Date 2019/8/15
*/
@Component
public class Send implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
private static final Logger log = LoggerFactory.getLogger(Send.class);
private RabbitTemplate rabbitTemplate;
@Autowired
public Send(RabbitTemplate rabbitTemplate) {
super();
this.rabbitTemplate = rabbitTemplate;
this.rabbitTemplate.setMandatory(true);
this.rabbitTemplate.setReturnCallback(this);
this.rabbitTemplate.setConfirmCallback(this);
}
/**
* 发布/订阅模式发送
* @param json
*/
public void routeSend(String json) {
Message message = this.setMessage(json);
//在fanoutExchange中在绑定Q到X上时,会自动把Q的名字当作bindingKey。
this.rabbitTemplate.convertAndSend(RabbitMQConstant.FANOUT_EXCHANGE, "", message);
}
/**
* 简单模式发送
* @param json
*/
public void simplSend(String json) {
Message message = this.setMessage(json);
this.rabbitTemplate.convertAndSend(RabbitMQConstant.QUEUE_1, message);
}
/**
* 路由模式发送
* @param routingKey
* @param json
*/
public void routingSend(String routingKey, String json) {
Message message = this.setMessage(json);
this.rabbitTemplate.convertAndSend(RabbitMQConstant.DIRECT_EXCHANGE, routingKey, message);
}
/**
* 主题模式发送
*
* @param routingKey
* @param json
*/
public void topicSend(String routingKey, String json) {
Message message = this.setMessage(json);
this.rabbitTemplate.convertAndSend(RabbitMQConstant.TOPIC_EXCHANGE, routingKey, message);
}
/**
* 死信模式发送,用于定时任务处理
* @param routingKey
* @param message
*/
public void beadSend(String routingKey, Message message) {
this.rabbitTemplate.convertAndSend(RabbitMQConstant.DEAD_EXCHANGE, routingKey, message);
}
/**
* 设置消息参数
* @param json
* @return
*/
private Message setMessage(String json){
MessageProperties messageProperties = new MessageProperties();
Message message = new Message(json.getBytes(), messageProperties);
//消息持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
}
/**
* 消息确认
* @param correlationData
* @param ack
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.info("消息发送确认成功");
} else {
log.info("消息发送失败:" + cause);
}
}
/**
* 消息发送失败回传
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("return--message:" + new String(message.getBody()) + ",replyCode:" + replyCode + ",replyText:"
+ replyText + ",exchange:" + exchange + ",routingKey:" + routingKey);
try {
Thread.sleep(10000L);
// TODO 重新发送消息至队列,此处应写一套重发机制,重发多少次结束,否则如果消息如果一直发送失败,则会一直发下去!
this.rabbitTemplate.convertAndSend(exchange, routingKey, message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.消费者端手动ACK
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);