一.demo项目结构(两个服务(生产者消费者)共用一个公共配置)
1. spring-rabbitmq-config公共参数模块
/**
* 交换机队列等参数
*/
public class RabbitConstant {
/**
* direct队列、交换机、路由键
*/
public static final String DIRECT_QUEUE_NAME = "direct_queue";
public static final String DIRECT_EXCHANGE_NAME = "direct_exchange";
public static final String DIRECT_ROUTING_KEY = "direct_routing_key";
/**
* fanout队列、交换机、路由键
*/
public static final String FANOUT_QUEUE_NAME1="fanout_queue1";
public static final String FANOUT_QUEUE_NAME2="fanout_queue2";
public static final String FANOUT_EXCHANGE_NAME="fanout_exchange";
/**
* topic队列、交换机、路由键
*/
public static final String PRODUCT_TOPIC_QUEUE_NAME="product_topic_queue"; //对应routingKey=product.*
public static final String ORDER_TOPIC_QUEUE_NAME="order_topic_queue"; //对应routingKey=order.*
public static final String TOPIC_EXCHANGE_NAME="topic_exchange";
/**
* header队列、交换机、路由键
*/
public final static String HEADER_EXCHANGE_NAME = "header_exchange";
public final static String HEADER_NAME_QUEUE = "nameQueue";
public final static String HEADER_AGE_QUEUE = "ageQueue";
/**
* 延迟队列、交换机、交换机类型
*/
public static final String DELAY_QUEUE_NAME = "delay_queue";
public static final String DELAY_EXCHANGE_NAME = "delay_exchange";
public static final String DELAY_EXCHANGE_TYPE = "x-delayed-message";
/**
* 死信队列、交换机、路由键
*/
public static final String DLX_QUEUE_NAME = "dlx_queue";
public static final String DLX_EXCHANGE_NAME = "dlx_exchange";
public static final String DLX_ROUTING_KEY = "dlx_routing_key";
}
2.spring-rabbitmq-producer生产者微服务(只做了单元测试代码)
2.1 application.yml
server:
port: 8091
spring:
rabbitmq:
host: 192.168.89.100
port: 5672
username: guest
password: guest
virtual-host: /
publisher-returns: true
#配置生产者消息发送确认机制
publisher-confirm-type: correlated
template:
retry:
#开启重试机制
enabled: true
#重试起始间隔时间
initial-interval: 1000ms
#最大重试次数
max-attempts: 10
#最大重试间隔时间
max-interval: 10000ms
#间隔时间乘数(这里配置间隔时间乘数为 2,则第一次间隔时间 1 秒,第二次重试间隔时间 2 秒,第三次 4 秒,以此类推)
multiplier: 2
2.2 ProducerConfig.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import static com.zdb.common.constant.RabbitConstant.*;
/**
* @author asdmin
*/
@Configuration
public class ProducerConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
private static final Logger logger = LoggerFactory.getLogger(ProducerConfig.class);
@Resource
private RabbitTemplate rabbitTemplate;
/**
* 配置消息发送的确认机制
*/
@PostConstruct
public void initRabbitTemplate() {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String s) {
if (ack) {
logger.info("{}:消息成功到达交换器", correlationData.getId());
} else {
logger.error("{}:消息发送交换器失败", correlationData.getId());
}
}
@Override
public void returnedMessage(final Message message, final int replyCode, final String replyText,
final String exchange,
final String routingKey) {
// 排除调延迟交换机,因为消息在延迟交换机中延迟,会因消息失效时间和延迟时间的差距触发此函数回调
if (!DELAY_EXCHANGE_NAME.equals(exchange)) {
logger.info("交换机返回消息的方法收到的消息:{} \n交换机回复的内容:{} \n交换机是:{} \n路由:key:{}",
new String(message.getBody()), replyText, exchange, routingKey);
}
}
/**
* 普通直连交换机 direct_exchange
*/
@Bean("directExchange")
DirectExchange grtDirectExchange() {
return new DirectExchange(DIRECT_EXCHANGE_NAME, true, false);
}
/**
* 声明广播交换机 fanout_exchange
*/
@Bean
public FanoutExchange getFanoutExchange() {
return new FanoutExchange(FANOUT_EXCHANGE_NAME, true, false);
}
/**
* 声明主题交换机 topic_exchange
*/
@Bean
public TopicExchange getTopicExchange() {
return new TopicExchange(TOPIC_EXCHANGE_NAME, true, false);
}
/**
* header消息交换机
*/
@Bean("headersExchange")
HeadersExchange getHeadersExchange() {
return new HeadersExchange(HEADER_EXCHANGE_NAME, true, false);
}
/**
* 声明延迟义交换机 delay_exchange
*/
@Bean
public CustomExchange getDelayExchange() {
return new CustomExchange(DELAY_EXCHANGE_NAME, DELAY_EXCHANGE_TYPE, true, false);
}
/**
* 死信交换机 dlx_exchange
*/
@Bean("dlxExchange")
DirectExchange getDlxExchange() {
return new DirectExchange(DLX_EXCHANGE_NAME, true, false);
}
}
2.3 TestSendMessage.java
import com.zdb.common.constant.RabbitConstant;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.UUID;
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestSendMessage {
@Autowired
RabbitTemplate rabbitTemplate;
/**
* 测试直连和死信队列(发送消息到一个未配置消费者的直连队列中,让消息超时成为死信)
* @throws UnsupportedEncodingException
*/
@Test
public void testSendDLXMessage() throws UnsupportedEncodingException {
Message msg = MessageBuilder.withBody(("测试javaboy_queue队列的死信消息!" ).getBytes("UTF-8")).build();
rabbitTemplate.convertAndSend(RabbitConstant.DIRECT_EXCHANGE_NAME, RabbitConstant.DIRECT_ROUTING_KEY, msg ,new CorrelationData(UUID.randomUUID().toString()));
}
/**
* 测试广播消息
*/
@Test
public void testSendFanoutMessage() {
//参数:交换机、路由键、消息体
rabbitTemplate.convertAndSend(RabbitConstant.FANOUT_EXCHANGE_NAME,"","测试生产者产生Fanout消息!");
}
/**
* 测试topic交换机的routingKey 路由模式
*/
@Test
public void testSendTopicMessage() {
rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_EXCHANGE_NAME,"product.test1","测试生产者产生productTopic消息!");
rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_EXCHANGE_NAME,"order.test1","测试生产者产生orderTopic消息!");
}
/**
* 测试header交换机的header头条件
*/
@Test
public void testSendHeaderMessage() throws UnsupportedEncodingException {
Message nameMsg = MessageBuilder
.withBody("hello header! nameQueue".getBytes("UTF-8"))
.setHeader("name", "zdb").build();
Message ageMsg = MessageBuilder
.withBody("hello header! ageQueue".getBytes())
.setHeader("age", "99").build();
rabbitTemplate.convertAndSend(RabbitConstant.HEADER_EXCHANGE_NAME,null,nameMsg,new CorrelationData(UUID.randomUUID().toString()));
rabbitTemplate.convertAndSend(RabbitConstant.HEADER_EXCHANGE_NAME,null,ageMsg,new CorrelationData(UUID.randomUUID().toString()));
}
/**
* 测试延迟消息
* @throws UnsupportedEncodingException
*/
@Test
public void testSendDelayMessage() throws UnsupportedEncodingException {
Message msg = MessageBuilder.withBody(("hello 周定斌,这条10s延迟的消息发送时间为:" + new Date()).getBytes("UTF-8")).setHeader("x-delay", 10000).build();
rabbitTemplate.convertAndSend(RabbitConstant.DELAY_EXCHANGE_NAME, RabbitConstant.DELAY_QUEUE_NAME, msg);
}
/**
* 测试生产者发送消息到mq的确认和回调
* @throws UnsupportedEncodingException
*/
@Test
public void testProducerReturnAndConfirm() throws UnsupportedEncodingException {
Message msg = MessageBuilder.withBody(("测试消息确认和回调!" ).getBytes("UTF-8")).build();
rabbitTemplate.convertAndSend(RabbitConstant.DIRECT_EXCHANGE_NAME, "不存在的routingKey", msg ,new CorrelationData(UUID.randomUUID().toString()));
}
}
3.spring-rabbitmq-consumer消费者微服务
3.1 applicatin.yml
server:
port: 8090
spring:
rabbitmq:
host: 192.168.89.100
port: 5672
username: guest
password: guest
virtual-host: /
##消费消息设置手动ACK,默认是自动
listener:
simple:
acknowledge-mode: manual
3.2 ConsumerConfig.java
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import static com.zdb.common.constant.RabbitConstant.*;
/**
* @author asdmin
*/
@Configuration
public class ConsumerConfig {
/**
* direct直连型消息队列(配合死信队列使用)
*/
@Bean("directQueue")
Queue getDirectQueue() {
Map<String, Object> args = new HashMap<>();
//设置消息过期时间10s
args.put("x-message-ttl", 1000 * 10);
//设置死信交换机
args.put("x-dead-letter-exchange", DLX_EXCHANGE_NAME);
//设置死信 routing_key
args.put("x-dead-letter-routing-key", DLX_ROUTING_KEY);
return new Queue(DIRECT_QUEUE_NAME, true, false, false, args);
}
@Bean("directExchange")
DirectExchange getDirectExchange() {
return new DirectExchange(DIRECT_EXCHANGE_NAME, true, false);
}
@Bean
Binding getDirectBinding(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("directQueue") Queue queue) {
return BindingBuilder.bind(queue).to(directExchange).with(DIRECT_ROUTING_KEY);
}
/**
* fanout模式的消息配置
*/
@Bean
public FanoutExchange getFanoutExchange() {
return new FanoutExchange(FANOUT_EXCHANGE_NAME, true, false);
}
@Bean("fanoutQueue1")
public Queue getFanoutQueue1() {
return new Queue(FANOUT_QUEUE_NAME1, true, false, false);
}
@Bean("fanoutQueue2")
public Queue getFanoutQueue2() {
return new Queue(FANOUT_QUEUE_NAME2, true, false, false);
}
@Bean
public Binding getFanoutBinding1(FanoutExchange fanoutExchange, @Qualifier("fanoutQueue1") Queue queue) {
return BindingBuilder.bind(queue).to(fanoutExchange);
}
@Bean
public Binding getFanoutBinding2(FanoutExchange fanoutExchange, @Qualifier("fanoutQueue2") Queue queue) {
return BindingBuilder.bind(queue).to(fanoutExchange);
}
/**
* topic模式的消息配置(product 和 order)
*/
@Bean
public TopicExchange getTopicExchange() {
return new TopicExchange(TOPIC_EXCHANGE_NAME, true, false);
}
@Bean("productTopicQueue")
public Queue getProductTopicQueue() {
return new Queue(PRODUCT_TOPIC_QUEUE_NAME, true, false, false);
}
@Bean("orderTopicQueue")
public Queue getOrderTopicQueue() {
return new Queue(ORDER_TOPIC_QUEUE_NAME, true, false, false);
}
@Bean
public Binding getProductTopicBinding(TopicExchange topicExchange, @Qualifier("productTopicQueue") Queue queue) {
return BindingBuilder.bind(queue).to(topicExchange).with("product.*");
}
@Bean
public Binding getOrderTopicBinding(TopicExchange topicExchange, @Qualifier("orderTopicQueue") Queue queue) {
return BindingBuilder.bind(queue).to(topicExchange).with("order.*");
}
/**
* header消息队列
*/
@Bean("headersExchange")
HeadersExchange getHeadersExchange() {
return new HeadersExchange(HEADER_EXCHANGE_NAME, true, false);
}
@Bean("nameQueue")
Queue getNameQueue() {
return new Queue(HEADER_NAME_QUEUE, true, false, false);
}
@Bean("ageQueue")
Queue getAgeQueue() {
return new Queue(HEADER_AGE_QUEUE, true, false, false);
}
@Bean
Binding bindingName(@Qualifier("headersExchange") HeadersExchange headersExchange, @Qualifier("nameQueue") Queue queue) {
Map<String, Object> map = new HashMap<>();
map.put("name", "zdb");
return BindingBuilder.bind(queue)
.to(headersExchange).whereAny(map).match();
}
@Bean
Binding bindingAge(@Qualifier("headersExchange") HeadersExchange headersExchange, @Qualifier("ageQueue") Queue queue) {
return BindingBuilder.bind(queue)
.to(headersExchange).where("age").exists();
}
/**
* 延迟队列配置
*/
@Bean("delayedExchange")
CustomExchange getDelayedExchange() {
Map<String, Object> args = new HashMap<>(10);
args.put("x-delayed-type", "direct");
/**
* 交换机名称。
* 交换机类型,这个地方是固定的。 x-delayed-message
* 交换机是否持久化。
* 如果没有队列绑定到交换机,交换机是否删除。
* 其他参数
*/
return new CustomExchange(DELAY_EXCHANGE_NAME, DELAY_EXCHANGE_TYPE, true, false, args);
}
@Bean("delayQueue")
Queue getDelayQueue() {
return new Queue(DELAY_QUEUE_NAME, true, false, false);
}
@Bean
Binding binding(@Qualifier("delayedExchange") CustomExchange customExchange, @Qualifier("delayQueue") Queue queue) {
return BindingBuilder.bind(queue).to(customExchange).with(DELAY_QUEUE_NAME).noargs();
}
/**
* 死信队列(配合普通队列使用,普通队列消息过期后进入私信队列)
*/
@Bean("dlxQueue")
Queue dlxQueue() {
return new Queue(DLX_QUEUE_NAME, true, false, false);
}
@Bean("dlxExchange")
DirectExchange dlxExchange() {
return new DirectExchange(DLX_EXCHANGE_NAME, true, false);
}
@Bean
Binding dlxBinding(@Qualifier("dlxExchange") DirectExchange directExchange, @Qualifier("dlxQueue") Queue queue) {
return BindingBuilder.bind(queue).to(directExchange).with(DLX_ROUTING_KEY);
}
}
3.3 MessageListener.java
package com.zdb.consumer.listener;
import com.rabbitmq.client.Channel;
import com.zdb.common.constant.RabbitConstant;
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.Service;
import java.io.IOException;
/**
* @author asdmin
* Attribute value must be constant
*/
@Service
public class MessageListener {
private static final Logger logger = LoggerFactory.getLogger(MessageListener.class);
/**
* 监听direct直连型队列direct_queue,(测试时不开启接收,检验延迟队列的接收是否正常)
* @param message
* @param channel
* @throws IOException
*/
@RabbitListener(queues = RabbitConstant.DIRECT_QUEUE_NAME)
public void handle(Message message , Channel channel) throws IOException {
try {
//处理消息
byte[] messageBody = message.getBody();
System.out.println(RabbitConstant.DIRECT_QUEUE_NAME+"收到的direct_queue队列消息:"+new String(messageBody));
//模拟消息处理判断接收还是拒收
boolean dealFlag = false;
if(dealFlag){
/**
* 执行完成无异常手动签收信息
* 入参:
* deliveryTag:确认消息的编号,这是每个消息被消费时都会分配一个递增唯一编号
* multiple:批量确认,true表示所有编号小于目前确认消息编号的待确认消息都会被确认,false则只确认当前消息
*/
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
logger.info("业务判断成功,接收并清理mq中的消息:{}",new String(messageBody));
} else {
/**
* 业务异常拒绝签收
* 入参:
* deliveryTag:确认消息的编号,这是每个消息被消费时都会分配一个递增唯一编号
* multiple:批量确认,true表示所有编号小于目前确认消息编号的待确认消息都会被确认,false则只确认当前消息
* requeue:表示是否重新将消息放回队列(如果设置为true,则将消息重新放回队列,如果设置为false,进入死信队列或直接将消息丢弃)。
*/
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
logger.error("业务判断失败,拒收该消息");
}
} catch (Exception e) {
e.printStackTrace();
/**
* 异常拒绝接收
* 入参:
* deliveryTag:确认消息的编号,这是每个消息被消费时都会分配一个递增唯一编号
* requeue:表示是否重新将消息放回队列(如果设置为true,则将消息重新放回队列,如果设置为false,则直接将消息丢弃)。
*/
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
/**
* 监听fanout广播消息队列 my_boot_fanout_queue1 my_boot_fanout_queue2
* @param message
* @param message
* @throws IOException
*/
@RabbitListener(queues = {RabbitConstant.FANOUT_QUEUE_NAME1})
public void dealFanoutMessage1(Message message, Channel channel) throws IOException {
try {
//处理消息
byte[] messageBody = message.getBody();
System.out.println(RabbitConstant.FANOUT_QUEUE_NAME1+"收到的广播消息:"+new String(messageBody));
//执行完成无异常手动签收信息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
//异常拒绝接收
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
@RabbitListener(queues = {RabbitConstant.FANOUT_QUEUE_NAME2})
public void dealFanoutMessage2(Message message, Channel channel) throws IOException {
try {
//处理消息
byte[] messageBody = message.getBody();
System.out.println(RabbitConstant.FANOUT_QUEUE_NAME2+"收到的广播消息:"+new String(messageBody));
//执行完成无异常手动签收信息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
//异常拒绝接收
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
/**
* 监听topic消息队列(产品和订单消息)
* @param message
* @param message
* @throws IOException
*/
@RabbitListener(queues = {RabbitConstant.PRODUCT_TOPIC_QUEUE_NAME})
public void dealProductTopicMessage(Message message,Channel channel) throws IOException {
try {
//处理消息
byte[] messageBody = message.getBody();
System.out.println(RabbitConstant.PRODUCT_TOPIC_QUEUE_NAME+"收到的topic消息:"+new String(messageBody));
//执行完成无异常手动签收信息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
//异常拒绝接收
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
@RabbitListener(queues = {RabbitConstant.ORDER_TOPIC_QUEUE_NAME})
public void dealOrderTopicMessage(Message message,Channel channel) throws IOException {
try {
//处理消息
byte[] messageBody = message.getBody();
System.out.println(RabbitConstant.ORDER_TOPIC_QUEUE_NAME+"收到的topic消息:"+new String(messageBody));
//执行完成无异常手动签收信息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
//异常拒绝接收
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
/**
* 监听header消息队列(name 和 age)
* @param message
* @param message
* @throws IOException
*/
@RabbitListener(queues = {RabbitConstant.HEADER_NAME_QUEUE})
public void dealNameHeaderMessage(Message message,Channel channel) throws IOException {
try {
//处理消息
byte[] messageBody = message.getBody();
System.out.println(RabbitConstant.HEADER_NAME_QUEUE+"收到的nameHeader消息:"+new String(messageBody));
//执行完成无异常手动签收信息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
//异常拒绝接收
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
@RabbitListener(queues = {RabbitConstant.HEADER_AGE_QUEUE})
public void dealAgeHeaderMessage(Message message,Channel channel) throws IOException {
try {
//处理消息
byte[] messageBody = message.getBody();
System.out.println(RabbitConstant.HEADER_AGE_QUEUE+"收到的ageHeader消息:"+new String(messageBody));
//执行完成无异常手动签收信息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
//异常拒绝接收
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
/**
* 监听延迟消息队列my_boot_delay_queue
* @param message
* @param channel
* @throws IOException
*/
@RabbitListener(queues = RabbitConstant.DELAY_QUEUE_NAME)
public void dealDelayMessage(Message message , Channel channel) throws IOException {
try {
//处理消息
byte[] messageBody = message.getBody();
System.out.println(RabbitConstant.DELAY_QUEUE_NAME+"收到的延迟消息:"+new String(messageBody));
//执行完成无异常手动签收信息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
e.printStackTrace();
//异常拒绝接收
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
/**
* 监听死信队列dlx_queue_name
* @param message
* @param channel
* @throws IOException
*/
@RabbitListener(queues = RabbitConstant.DLX_QUEUE_NAME)
public void dealDLXMessage(Message message , Channel channel) throws IOException {
try {
//处理消息
byte[] messageBody = message.getBody();
System.out.println("收到的死信队列消息:"+new String(messageBody));
//执行完成无异常手动签收信息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
e.printStackTrace();
//异常拒绝接收
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
}