maven的配置类还是和前面的一样。
package com.te.mm.factoryconfig;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
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.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.te.mm.Entity.ObjConsert;
/**
* 创建一个生产者工厂,然后注册到spring容器中,当然我们也可以使用springboot的自动装配
*
* 自动装备所在的包package org.springframework.boot.autoconfigure.amqp;
* @ConfigurationProperties(prefix = "spring.rabbitmq") RabbitProperties.class
* 对springboot自动装配有过了解的童鞋,就你那个看懂上面springboot是如何进行对MQ装配的了
* @author Administrator
*
*/
@Configuration
@EnableRabbit
public class RabbitProducerConfig {
@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;
@Value("${spring.rabbitmq.virtual-host}")
private String virtualHosthost;
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host,port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(virtualHosthost);
return connectionFactory;
}
@Bean(name="tt")//和kafka的配置仓库类一莫一样很牛逼的
public RabbitListenerContainerFactory<SimpleMessageListenerContainer> tt(){
//ConcurrentKafkaListenerContainerFactory
//消息的统一过滤器
MessageConverter messageConverter = new ObjConsert();
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConcurrentConsumers(5);//允许同时消费数量为5
factory.setMaxConcurrentConsumers(10);//允许同时最大消费数量为10
factory.setReceiveTimeout(10000L);//10秒
factory.setMessageConverter(messageConverter);//具体的逻辑要自己在ObjConsert里面写
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);//设置手动提交
factory.setConnectionFactory(connectionFactory());
return factory;
}
@Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
//template.setDefaultReceiveQueue(queue);//设置默认接收队列
return template;
}
@Bean(name="defauilTemplate")
public RabbitTemplate rabbitTemplateDefault() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setDefaultReceiveQueue("fastSending");//设置默认接收队列
return template;
}
}
上文中的SimpleRabbitListenerContainerFactory 是笔者看了kafka的类似源码,写的,很强大,可以扩展N多的东西
/***
* 消费者异常处理器()
* 有时候我们使用的时候,可能是一个异常类对应一个死信队列
* @author 刘新杨
* 菩提本无树,
* 明镜亦非台。
*/
@Service(value="RabbitConsumerListenerErrorHandler")//使用的时候可以直接在@rabbitMQ中errorHandler设置名字即可
public class RabbitConsumerListenerErrorHandler implements RabbitListenerErrorHandler {
@Override
public Object handleError(Message amqpMessage, org.springframework.messaging.Message<?> message,
ListenerExecutionFailedException exception) throws Exception {
System.out.println("消费失败的异常是:"+amqpMessage.getMessageProperties().getConsumerQueue());
return "此处应该做消费重新消费,或者发送到死信交换机";
}
上图中可以为单一消费者服务,做重发消息的机制,也可以通过记录日志的形式后续做补偿。
package com.te.mm.listener;
import java.util.List;
import java.util.Map;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import com.rabbitmq.client.Channel;
import com.te.mm.Entity.Order;
/**
* 正常情况下消费者队列应该在其他项目中,这里只做演示所以就放在了在这里
*
* @author Administrator
*
*/
@Component
public class RabbitReceiver {
// SimpleRabbitListenerContainerFactory此类可以自定义一些配置详细请点击
// @RabbitListener(bindings = @QueueBinding(value = @Queue(value =
// "${spring.rabbitmq.listener.order.queue.name}", durable =
// "${spring.rabbitmq.listener.order.queue.durable}"), exchange =
// @Exchange(value = "${spring.rabbitmq.listener.order.exchange.name}", durable
// = "${spring.rabbitmq.listener.order.exchange.durable}", type =
// "${spring.rabbitmq.listener.order.exchange.type}",
// ignoreDeclarationExceptions =
// "${spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions}"),
// key = "${spring.rabbitmq.listener.order.key}"),containerFactory="tt")
// @RabbitHandler
// public void onMessage(Message message, Channel channel) throws Exception {
//
//
// System.out.println("*******");
// System.out.println("消息体内容:" + message.getPayload());
// long deTag = (Long) message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
// channel.basicAck(deTag, false);
// }
@RabbitListener(containerFactory = "tt", errorHandler = "RabbitConsumerListenerErrorHandler", queues = "TestDirectQueue")
@RabbitHandler // 此注解加上之后可以接受对象型消息
public void onOrderMessage(@Payload List<Order> orders, Channel channel, @Headers Map<String, Object> heads)
throws Exception {
for (Order order : orders) {
System.out.println(order);
}
// 此处也可以带messagid等信息作为唯一标识来确保消息的幂等操作
/**
* 做幂等操作可用redis或者DB来存储唯一标识符,每次消费前
* 先查询是否消费了,如果没有消费就在消费逻辑。
*/
System.out.println("消息唯一标识符:" + heads.get("number"));
long deTag = (Long) heads.get(AmqpHeaders.DELIVERY_TAG);
System.out.println(deTag);
// 告诉服务器,已经消费成功,
channel.basicAck(deTag, false);
}
@RabbitListener(containerFactory="tt", errorHandler = "RabbitConsumerListenerErrorHandler", queues = "dead_queue")
@RabbitHandler
public void onDeadMessage2(Message message, Channel channel, @Headers Map<String, Object> heads) throws Exception {
System.out.println("死信队列message"+new String(message.getBody()));
long deTag = (Long) heads.get(AmqpHeaders.DELIVERY_TAG);
// 正常业务逻辑,当出现异常的时候,走死信队列重新消费。
System.out.println(deTag);
// 告诉服务器,已经消费成功,
channel.basicAck(deTag, false);
}
@RabbitListener(containerFactory="tt", errorHandler = "RabbitConsumerListenerErrorHandler", queues = "test_queue_1")
@RabbitHandler
public void delayMessage(Message message, Channel channel, @Headers Map<String, Object> heads) throws Exception {
System.out.println("分发队列message"+new String(message.getBody()));
long deTag = (Long) heads.get(AmqpHeaders.DELIVERY_TAG);
// 正常业务逻辑,当出现异常的时候,走死信队列重新消费。
System.out.println(deTag);
// 告诉服务器,已经消费成功,
channel.basicAck(deTag, false);
}
@RabbitListener(containerFactory="tt", errorHandler = "RabbitConsumerListenerErrorHandler", queues = "TestFanoutQueue")
@RabbitHandler
public void faountMessage(Message message, Channel channel, @Headers Map<String, Object> heads) throws Exception {
System.out.println("延时队列message"+new String(message.getBody()));
long deTag = (Long) heads.get(AmqpHeaders.DELIVERY_TAG);
// 正常业务逻辑,当出现异常的时候,走死信队列重新消费。
System.out.println(deTag);
// 告诉服务器,已经消费成功,
channel.basicAck(deTag, false);
}
@RabbitListener(containerFactory="tt", errorHandler = "RabbitConsumerListenerErrorHandler", queues = "TestFanoutQueue2")
@RabbitHandler
public void faount2Message(Message message, Channel channel, @Headers Map<String, Object> heads) throws Exception {
System.out.println("分发队列message"+new String(message.getBody()));
long deTag = (Long) heads.get(AmqpHeaders.DELIVERY_TAG);
// 正常业务逻辑,当出现异常的时候,走死信队列重新消费。
System.out.println(deTag);
// 告诉服务器,已经消费成功,
channel.basicAck(deTag, false);
/**
* 其实此处可以使用basicNack,那么就不需要在添加消息的过期时间了。
* 下面的代码可以实现,当业务逻辑出现异常的时候走死信交换机,重新消费。
* 或者第三个参数设置为true则会重新消费(要注意幂等性,如果只有一个消费者那么
* 说明消费者代码逻辑有误,重新消费无多大意义)
*/
// try {
// System.err.println("业务逻辑");
// } catch (Exception e) {
//
//channel.basicNack(deTag, false,false);
// }
}
}
上图为消费者的核心代码
下面我们详细讲一下相关注解
@Payload 加上之后spring会把message格式的数据自动转换为此注解后面跟着的对象
@RabbitListener 核心消费注解,参数如下:
String id() borker中的唯一标识。
containerFactory() 里面配置消息是否自动确认,超时时间,最大消费数量,第一张图说明
String[] queues 可以写一个队列,可以写多个队列{“queue1”,"queue2"}
Queue[] queuesToDeclare() 可以声明队列使用的 @Queue("里面是队列的名字,持久化等参数")
QueueBinding[] bindings() default {} 可以使用QueueBinding(注解在实现消费者队列创建,交换机创建,绑定创建,参数设置等)
String priority() 这种队列的优先级,数字越大,优先级越高
String group() default ""; 具体没用过,不知道和kafka的组概念是否相同
String errorHandler() 自己定义的消费者异常处理器
大致消费者端重要的参数就那么多。
源码地址https://github.com/LxyTe/Study/tree/master/TechnicalIntegration
-------------------本文知识储备均来自于蚂蚁课堂,感谢余总的指点