首先是要安装RabbitMQ,
安装RabbitMQ就要先安装Elang,
他们的版本一定要对应,不然用不了。
这个是直接粘项目的代码,所以东西比较多,业务交换机是负责主要的转发,
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
在application.yml中加入配置
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: 填自己的用户名
password: 填自己的密码
publisher-confirms: true #消息发送到交换机确认机制,是否确认回调
publisher-returns: true #消息发送到交换机确认机制,是否返回回调
virtual-host: /
如果上面参数有什么不懂的可以查查
如果消息被绝收或者报错,就会转到延时队列(普通队列),然后给延时队列绑定死信队列,当消息到达设置的延时后就会加入死信队列。延时队列(普通队列)不需要消费,因为他和死信组和才能实现延时。
在创建队列的时候指定延时时间,Message也可以指定消息存活时间,如果两个都在则取最小值。
package com.iov.sock.sock.utils.mq;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @author WQY
* @date 2019/07/24
*/
@Configuration
public class RabbitMQConfig {
//业务交换机
public static final String BUSINESS_EXCHANGE_NAME = "iov.business.exchange";
//实时数据队列
public static final String BUSINESS_REALTIEMMSG_NAME = "iov.business.realtimemsg";
//其余数据队列
public static final String BUSINESS_OTHERMSG_NAME = "iov.business.othermsg";
//死信交换机
public static final String DEAD_LETTER_EXCHANGE = "iov.dead.exchange";
//死信routing-key
public static final String DEAD_LETTER_REALTIMEMSG_ROUTING_KEY = "iov.dead.realtimemsg.routingkey";
//死信routing-key
public static final String DEAD_LETTER_OTHERMSG_ROUTING_KEY = "iov.dead.othermsg.routingkey";
//死信队列A
public static final String DEAD_LETTER_REALTIMEMSG_NAME = "iov.dead.realtimemsg";
//死信队列B
public static final String DEAD_LETTER_OTHERMSG_NAME = "iov.dead.othermsg";
//业务失败延迟交换机
public static final String BUSINESS_DELAY_EXCHANGE_NAME = "iov.delay.business.exchange";
//业务失败延迟队列A
public static final String BUSINESS_DELAY_REALTIMEMSG_NAME = "iov.delay.business.realtimemsg";
//业务失败延迟队列B
public static final String BUSINESS_DELAY_OTHERMSG_NAME = "iov.delay.business.othermsg";
// 声明业务Exchange
@Bean("businessExchange")
public TopicExchange businessExchange(){
return new TopicExchange(BUSINESS_EXCHANGE_NAME);
}
// 声明死信Exchange
@Bean("deadLetterExchange")
public DirectExchange deadLetterExchange(){
return new DirectExchange(DEAD_LETTER_EXCHANGE);
}
//声明业务延迟交换机
@Bean("delayBusinessExchange")
public TopicExchange delayBusinessExchange(){
return new TopicExchange(BUSINESS_DELAY_EXCHANGE_NAME);
}
/*
* QueueA对应REALTIMEMSG
* QueueB对应OTHERMSG
*
*/
// 声明业务队列A
@Bean("businessQueueA")
public Queue businessQueueA(){
Map<String, Object> args = new HashMap<>(2);
return QueueBuilder.durable(BUSINESS_REALTIEMMSG_NAME).withArguments(args).build();
}
// 声明业务队列B
@Bean("businessQueueB")
public Queue businessQueueB(){
Map<String, Object> args = new HashMap<>(2);
return QueueBuilder.durable(BUSINESS_OTHERMSG_NAME).withArguments(args).build();
}
// 声明业务延时队列A
@Bean("delayBusinessQueueA")
public Queue delayBusinessQueueA(){
Map<String, Object> args = new HashMap<>(3);
args.put("x-message-ttl", 10*1000);
// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
// x-dead-letter-routing-key 这里声明当前队列的死信路由key
args.put("x-dead-letter-routing-key", DEAD_LETTER_REALTIMEMSG_ROUTING_KEY);
return QueueBuilder.durable(BUSINESS_DELAY_REALTIMEMSG_NAME).withArguments(args).build();
}
// 声明业务延时队列B
@Bean("delayBusinessQueueB")
public Queue delayBusinessQueueB(){
Map<String, Object> args = new HashMap<>(3);
args.put("x-message-ttl", 10*1000);
// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
// x-dead-letter-routing-key 这里声明当前队列的死信路由key
args.put("x-dead-letter-routing-key", DEAD_LETTER_OTHERMSG_ROUTING_KEY);
return QueueBuilder.durable(BUSINESS_DELAY_OTHERMSG_NAME).withArguments(args).build();
}
// 声明死信队列A
@Bean("deadLetterQueueA")
public Queue deadLetterQueueA(){
Map<String, Object> args = new HashMap<>(1);
args.put("x-message-ttl",10*1000);
return QueueBuilder.durable(DEAD_LETTER_REALTIMEMSG_NAME).withArguments(args).build();
}
// 声明死信队列B
@Bean("deadLetterQueueB")
public Queue deadLetterQueueB(){
Map<String, Object> args = new HashMap<>(1);
// x-dead-letter-exchange 声明 死信交换机
//args.put(DEAD_LETTER_QUEUE_KEY, "DL_EXCHANGE");
// x-dead-letter-routing-key 声明 死信路由键
//args.put(DEAD_LETTER_ROUTING_KEY, "KEY_R");
//过期时间
args.put("x-message-ttl",10*1000);
return QueueBuilder.durable(DEAD_LETTER_OTHERMSG_NAME).withArguments(args).build();
}
// 声明业务队列A绑定关系
@Bean
public Binding businessBindingA(@Qualifier("businessQueueA") Queue queue,
@Qualifier("businessExchange") TopicExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("iov.realtimemsg");
}
// 声明业务队列B绑定关系
@Bean
public Binding businessBindingB(@Qualifier("businessQueueB") Queue queue,
@Qualifier("businessExchange") TopicExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("iov.othermsg");
}
// 声明业务延迟队列A绑定关系
@Bean
public Binding delayBusinessBindingA(@Qualifier("delayBusinessQueueA") Queue queue,
@Qualifier("delayBusinessExchange") TopicExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("iov.delay.realtimemsg");
}
// 声明业务延迟队列B绑定关系
@Bean
public Binding delayBusinessBindingB(@Qualifier("delayBusinessQueueB") Queue queue,
@Qualifier("delayBusinessExchange") TopicExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("iov.delay.othermsg");
}
// 声明死信队列A绑定关系
@Bean
public Binding deadLetterBindingA(@Qualifier("deadLetterQueueA") Queue queue,
@Qualifier("deadLetterExchange") DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_REALTIMEMSG_ROUTING_KEY);
}
// 声明死信队列B绑定关系
@Bean
public Binding deadLetterBindingB(@Qualifier("deadLetterQueueB") Queue queue,
@Qualifier("deadLetterExchange") DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_OTHERMSG_ROUTING_KEY);
}
}
发送消息
还重写的两个回调
在application.yml中加入
publisher-confirms: true #消息发送到交换机确认机制,是否确认回调
publisher-returns: true #消息发送到交换机确认机制,是否返回回调
confirm
returnedMessage
这两个就比较好理解了
第一个是发送到消费者以后如果消费者拒收执行,
下一个是找不到队列执行
Message可以设置消息的唯一标识,也是消息确认用的,报错可以知道是那条消息没发过去
package com.iov.sock.sock.utils.mq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
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.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
public class BusinessMessageSender implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback, InitializingBean {
public static final Logger log = LoggerFactory.getLogger(BusinessMessageSender.class);
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendReamTimeMsg(String msg){
Message message = MessageBuilder.withBody(msg.getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
.setMessageId(UUID.randomUUID()+"").build();
// 自定义消息唯一标识
CorrelationData correlationData = new CorrelationData();
correlationData.setId(message.getMessageProperties().getMessageId());
rabbitTemplate.convertSendAndReceive(RabbitMQConfig.BUSINESS_EXCHANGE_NAME, "iov.realtimemsg", message,correlationData);
}
public void sendOtherMsg(String msg){
Message message = MessageBuilder.withBody(msg.getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
.setMessageId(UUID.randomUUID()+"").build();
// 自定义消息唯一标识
CorrelationData correlationData = new CorrelationData();
correlationData.setId(message.getMessageProperties().getMessageId());
rabbitTemplate.convertSendAndReceive(RabbitMQConfig.BUSINESS_EXCHANGE_NAME, "iov.othermsg", message,correlationData);
}
/**
* 用于实现消息发送到RabbitMQ交换器后接收ack回调。
* 如果消息发送确认失败就进行重试。
*
* @param correlationData
* @param ack
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
// 消息回调确认失败处理
if (!ack) {
// 这里以做消息的从发等处理
System.out.println("消息发送失败,消息ID:"+correlationData.getId());
} else {
System.out.println("消息发送成功,消息ID:"+correlationData.getId()+"===ack:"+ack);
}
}
/**
* 用于实现消息发送到RabbitMQ交换器,但无相应队列与交换器绑定时的回调。
* 发生脑裂的时候会发生这种情况
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.error("MQ消息发送失败,replyCode:{}, replyText:{},exchange:{},routingKey:{},消息体:{}",
replyCode, replyText, exchange, routingKey, message.getBody());
}
@Override
public void afterPropertiesSet() throws Exception {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
}
生产者这边基本就完了
然后开始写消费者
消费者的配置文件做一些改动
加入手动确认
acknowledge-mode: manual
rabbitmq:
host: 127.0.0.1
port: 5672
username: 你的用户名
password: 密码
publisher-confirms: true #消息发送到交换机确认机制,是否确认回调
publisher-returns: true #消息发送到交换机确认机制,是否返回回调
virtual-host: /
listener:
simple:
acknowledge-mode: manual
default-requeue-rejected: false
direct:
acknowledge-mode: manual
default-requeue-rejected: false
然后写主队列的消费者
这是使用于我们项目的业务逻辑,其实到这步消息队列已经可以实现了
package com.iov.sock809.sock809.utils.mq;
import com.iov.sock809.sock809.context.params.ContextParams;
import com.iov.sock809.sock809.utils.HexStringUtils;
import com.rabbitmq.client.Channel;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.UUID;
@Slf4j
@Component
public class BusinessMessageReceiver implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback, InitializingBean {
public static final Logger log = LoggerFactory.getLogger(BusinessMessageReceiver.class);
@Autowired
private RabbitTemplate rabbitTemplate;
//监听的对了key,在生产者里配置了的
@RabbitListener(queues = "iov.business.realtimemsg")
public void receiveRealTimeMsg(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
log.info("收到realtimemsg:{}", msg);
boolean ack = true;
Exception exception = null;
try {
处理业务逻辑
} catch (Exception e){
ack = false;
exception = e;
}
//判断ack是否报错则加入死信队列
if (ack){
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
} else {
log.error("消息消费发生异常,error msg:{}", exception.getMessage(), exception);
//此处依然回复true是因为交给延时队列进行处理了,
//拒收报错信息,并踢出主队列,加入延迟队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
//将消息转到延时队列进行处理
sendDelayQueue(new String(message.getBody()),"iov.delay.realtimemsg");
}
}
@RabbitListener(queues = "iov.business.othermsg")
public void receiveOtherMsg(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
log.info("收到othermsg:{}", msg);
System.out.println(message.getMessageProperties().getDeliveryTag());
boolean ack = true;
Exception exception = null;
try {
处理业务逻辑
} catch (Exception e){
ack = false;
exception = e;
}
//判断ack是否报错则加入死信队列
if (ack){
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
} else {
log.error("消息消费发生异常,error msg:{}", exception.getMessage(), exception);
//拒收报错信息,并将报错信息加入延迟队列,延迟一定
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
//加入延迟队列
sendDelayQueue(new String(message.getBody()),"iov.delay.othermsg");
}
}
public void sendDelayQueue(String msg,String routingKey){
Message message = MessageBuilder.withBody(msg.getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
.setMessageId(UUID.randomUUID()+"").build();
// 自定义消息唯一标识
CorrelationData correlationData = new CorrelationData();
correlationData.setId(message.getMessageProperties().getMessageId());
rabbitTemplate.convertSendAndReceive("iov.delay.business.exchange", routingKey, message,correlationData);
}
/**
* 用于实现消息发送到RabbitMQ交换器后接收ack回调。
* 如果消息发送确认失败就进行重试。
*
* @param correlationData
* @param ack
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
// 消息回调确认失败处理
if (!ack) {
// 这里以做消息的从发等处理
System.out.println("消息发送失败,消息ID:"+correlationData.getId());
} else {
System.out.println("消息发送成功,消息ID:"+correlationData.getId()+"===ack:"+ack);
}
}
/**
* 用于实现消息发送到RabbitMQ交换器,但无相应队列与交换器绑定时的回调。
* 发生脑裂的时候会发生这种情况
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.error("MQ消息发送失败,replyCode:{}, replyText:{},exchange:{},routingKey:{},消息体:{}",
replyCode, replyText, exchange, routingKey, message.getBody());
}
@Override
public void afterPropertiesSet() throws Exception {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
}
下面加入死信队列,
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
上面的代码中,这句算是踢出当前队列的。也就是踢出主队列,因为主队列的数据量非常大,所以把报错的或者拒收的都放到延时队列中等时间到了 以后转入死信队列进行再次消费投递。如果在失败就进行相应的保存处理。
死信队列消费
由于消息已经经过了重发,所以在失败的话就抛弃这条消息。
@RabbitListener这个注解就是监听指定队列,如果有消息进来,消费者就进行消费。
package com.iov.sock809.sock809.utils.mq;
import com.iov.sock809.sock809.context.params.ContextParams;
import com.iov.sock809.sock809.utils.HexStringUtils;
import com.rabbitmq.client.Channel;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* 死信队列监听
* 接收delay对了ttl超时的消息
* @author wqy
* @date
*/
@Component
@Slf4j
public class DeadLetterMessageReceiver {
public static final Logger log = LoggerFactory.getLogger(DeadLetterMessageReceiver.class);
@RabbitListener(queues = "iov.dead.realtimemsg")
public void receiveA(Message message, Channel channel) throws IOException {
System.out.println("收到死信realtimemsg:" + new String(message.getBody()));
System.out.println("收到死信realtimemsg:" + message.getMessageProperties().getDeliveryTag());
String msg = new String(message.getBody());
log.info("收到死信realtimemsg:{}", msg);
boolean ack = true;
Exception exception = null;
try {
处理业务逻辑
} catch (Exception e){
ack = false;
exception = e;
}
//判断ack是否报错则加入死信队列
if (ack){
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
} else {
log.error("消息消费发生异常,error msg:{}", exception.getMessage(), exception);
//三个参数,第一个是回复标识,第三个是是否重回队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
@RabbitListener(queues = "iov.dead.othermsg")
public void receiveB(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
log.info("收到死信othermisg:{}", msg);
boolean ack = true;
Exception exception = null;
try {
处理业务
} catch (Exception e){
ack = false;
exception = e;
}
//判断ack是否报错则加入死信队列
if (ack){
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
} else {
log.error("消息消费发生异常,error msg:{}", exception.getMessage(), exception);
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
}
初用RabbitMQ,有不足还请指出。