参考博文
https://blog.csdn.net/qq_35387940/article/details/100514134?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
https://blog.csdn.net/skiof007/article/details/80914318
https://blog.csdn.net/u011212394/article/details/100086728
https://blog.csdn.net/qq_40625058/article/details/105584732
1.交互机的类型
1.Direct Exchange
直连型交换机,根据消息携带的路由键将消息投递给对应队列。
大致流程,有一个队列绑定到一个直连交换机上,同时赋予一个路由键 routing key 。
然后当一个消息携带着路由值为X,这个消息通过生产者发送给交换机时,交换机就会根据这个路由值X去寻找绑定值也是X的队列。
2.Fanout Exchange
扇型交换机,这个交换机没有路由键概念,就算你绑了路由键也是无视的。 这个交换机在接收到消息后,会直接转发到绑定到它上面的所有队列。
3.Topic Exchange
主题交换机,这个交换机其实跟直连交换机流程差不多,但是它的特点就是在它的路由键和绑定键之间是有规则的。
简单地介绍下规则:
- (星号) 用来表示一个单词 (必须出现的)
(井号) 用来表示任意数量(零个或多个)单词
通配的绑定键是跟队列进行绑定的,举个小例子
队列Q1 绑定键为 .TT. 队列Q2绑定键为 TT.#
如果一条消息携带的路由键为 A.TT.B,那么队列Q1将会收到;
如果一条消息携带的路由键为TT.AA.BB,那么队列Q2将会收到;
主题交换机是非常强大的,为啥这么膨胀?
当一个队列的绑定键为 "#"(井号) 的时候,这个队列将会无视消息的路由键,接收所有的消息。
当 * (星号) 和 # (井号) 这两个特殊字符都未在绑定键中出现的时候,此时主题交换机就拥有的直连交换机的行为。
所以主题交换机也就实现了扇形交换机的功能,和直连交换机的功能。
2.生成者和消费者项目构建
1.生产者
mq-provider项目pom
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.3.RELEASE
org.example
mq-provider
1.0-SNAPSHOT
mq-provider
UTF-8
1.8
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-amqp
org.springframework.boot
spring-boot-starter-test
test
junit
junit
4.12
test
applicatione配置文件:
server.port=9005
spring.application.name=mq-provider
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
2.消费者
项目pom:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.3.RELEASE
org.example
mq-consumer
1.0-SNAPSHOT
mq-consumer
UTF-8
1.7
1.7
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-amqp
junit
junit
4.11
test
application配置文件:
server.port=8005
spring.application.name=mq-consumer
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
3.交换机实例
1.直流交换
生产者直流配置文件,DirectConfig,定义队列,交换机和绑定:
@Configuration
public class DirectConfig {
//申明队列
@Bean
public Queue directQueue(){
return new Queue("directQueue",true);
}
//申明交换机
@Bean
public DirectExchange directExchange(){
return new DirectExchange("directExchange",true,false);
}
//绑定
@Bean
public Binding binding(){
return BindingBuilder.bind(directQueue())
.to(directExchange())
.with("directRouting");
}
}
生产者Controller类发送消息:
@Autowired
RabbitTemplate rabbitTemplate;
@GetMapping("/direct")
public String direct(){
Map map = new HashMap<>();
map.put("now",new Date());
rabbitTemplate.convertAndSend("directExchange","directRouting",map);
return "ok";
}
消费者接收信息类:
package cwh.mqConsumer.mq;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@RabbitListener(queues = "directQueue")
public class directReceiver {
@RabbitHandler
public void process(Map message){
System.out.println("直流消费端收到"+message.toString());
}
}
2.主题交换
生产者主题配置文件,TopicConfig,定义队列,交换机和绑定:
package cwh.rabbitMq.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TopicConfig {
private static final String man = "topic.man";
private static final String woman = "topic.woman";
@Bean
public Queue firstQueue(){
return new Queue(TopicConfig.man);
}
@Bean
public Queue secondQueue(){
return new Queue(TopicConfig.woman);
}
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("topicExchange");
}
@Bean
public Binding binding1(){
return BindingBuilder.bind(firstQueue()).to(topicExchange()).with(man);
}
@Bean
public Binding binding2(){
return BindingBuilder.bind(secondQueue()).to(topicExchange()).with("topic.#");
}
}
生产者Controller类发送消息:
@GetMapping("/topic1")
public String topic1(){
Map map = new HashMap<>();
map.put("now",new Date());
map.put("msg","推送这是topic消息man");
rabbitTemplate.convertAndSend("topicExchange","topic.man",map);
return "ok";
}
@GetMapping("/topic2")
public String topic2(){
Map map = new HashMap<>();
map.put("now",new Date());
map.put("msg","推送这是topic消息woman");
rabbitTemplate.convertAndSend("topicExchange","topic.woman",map);
return "ok";
}
消费者接收信息类:
@Component
@RabbitListener(queues = "topic.man")
public class topicReceiver1 {
@RabbitHandler
public void process(Map message){
System.out.println("topic消费端2收到"+message.toString());
}
}
3.扇出交换机
生产者扇出配置文件,TopicConfig,定义队列,交换机和绑定:
package cwh.rabbitMq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutConfig {
@Bean
public Queue fanoutQueueA(){
return new Queue("fanoutQueueA");
}
@Bean
public Queue fanoutQueueB(){
return new Queue("fanoutQueueB");
}
@Bean
public Queue fanoutQueueC(){
return new Queue("fanoutQueueC");
}
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("fanoutExchange");
}
@Bean
public Binding bindingA(){
return BindingBuilder.bind(fanoutQueueA()).to(fanoutExchange());
}
@Bean
public Binding bindingB(){
return BindingBuilder.bind(fanoutQueueB()).to(fanoutExchange());
}
@Bean
public Binding bindingC(){
return BindingBuilder.bind(fanoutQueueC()).to(fanoutExchange());
}
}
生产者Controller类发送消息:
@GetMapping("/fanout1")
public String fanout1(){
Map map = new HashMap<>();
map.put("now",new Date());
map.put("msg","推送这是fanout1消息");
rabbitTemplate.convertAndSend("fanoutExchange",null,map);
return "ok";
}
消费者接收信息类:
@Component
@RabbitListener(queues = "fanoutQueueA")
public class FanoutReceiver1 {
@RabbitHandler
public void process(Map message){
System.out.println("扇出消费端1收到"+message.toString());
}
}
4.生产者消息确认
当生产者发送的消息无法找到交换机或者无法找到队列以及成功发送会掉用以下两个回调方法
配置类:
package cwh.rabbitMq.config;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
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;
@Configuration
public class rabbitConfig {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
//设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("ConfirmCallback: "+"相关数据:"+correlationData);
System.out.println("ConfirmCallback: "+"确认情况:"+ack);
System.out.println("ConfirmCallback: "+"原因:"+cause);
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("ReturnCallback: "+"消息:"+message);
System.out.println("ReturnCallback: "+"回应码:"+replyCode);
System.out.println("ReturnCallback: "+"回应信息:"+replyText);
System.out.println("ReturnCallback: "+"交换机:"+exchange);
System.out.println("ReturnCallback: "+"路由键:"+routingKey);
}
});
return rabbitTemplate;
}
}
5.消费者手动返回确认消息
注意:
顺序问题:先业务处理后,最后再确认消息。否则先确认消息,报错后捕获异常放回队列时,mq已经将该消息抛弃,会报channel错误,导致无法将消息返回队列。
5.1方式一,配置类
配置类:
package cwh.mqConsumer.config;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MessageListenerConfig {
@Autowired
private CachingConnectionFactory connectionFactory;
@Autowired
private MyAckReceiver myAckReceiver;//消息接收处理类
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setConcurrentConsumers(1);
container.setMaxConcurrentConsumers(1);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息
//设置一个队列
container.setQueueNames("directQueue","topic.man");
//如果同时设置多个如下: 前提是队列都是必须已经创建存在的
// container.setQueueNames("TestDirectQueue","TestDirectQueue2","TestDirectQueue3");
//另一种设置队列的方法,如果使用这种情况,那么要设置多个,就使用addQueues
//container.setQueues(new Queue("TestDirectQueue",true));
//container.addQueues(new Queue("TestDirectQueue2",true));
//container.addQueues(new Queue("TestDirectQueue3",true));
container.setMessageListener(myAckReceiver);
return container;
}
}
自定义处理消息类:
package cwh.mqConsumer.config;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class MyAckReceiver implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message) {
}
//{key=value,key=value,key=value} 格式转换成map
private Map mapStringToMap(String str) {
str = str.substring(1, str.length() - 1);
String[] strs = str.split(",");
Map map = new HashMap();
for (String string : strs) {
String key = string.split("=")[0].trim();
String value = string.split("=")[1];
map.put(key, value);
}
return map;
}
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//因为传递消息的时候用的map传递,所以将Map从Message内取出需要做些处理
String msg = message.toString();
String[] msgArray = msg.split("'");//可以点进Message里面看源码,单引号直接的数据就是我们的map消息数据
Map msgMap = mapStringToMap(msgArray[1].trim());
String messageId=msgMap.get("messageId");
String messageData=msgMap.get("messageData");
String createTime=msgMap.get("createTime");
System.out.println(" MyAckReceiver messageId:"+messageId+" messageData:"+messageData+" createTime:"+createTime);
System.out.println("消费的主题消息来自:"+message.getMessageProperties().getConsumerQueue());
channel.basicAck(deliveryTag, true);
// channel.basicReject(deliveryTag, true);//为true会重新放回队列
} catch (Exception e) {
channel.basicReject(deliveryTag, false);
e.printStackTrace();
}
}
}
5.1方式二,显示处理
消费端配置文件:
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#一次请求能处理消息的个数
spring.rabbitmq.listener.simple.prefetch=1
#开启消息确认机制
spring.rabbitmq.publisher-confirms=true
#开启消息发送失败放回
spring.rabbitmq.publisher-returns=true
接收代码处理:
@Component
@RabbitListener(queues = "hello")
public class HelloReceiver {
@RabbitHandler
public void hello(String msg, Channel channel, Message message) throws IOException {
try {
int test = 5/0;
System.out.println("this is helloReceiver"+msg);
//表明消息已收到
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
catch (Exception e){
//返回消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
// channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
System.out.println("消息重新回到队列");
}
}
}
6.死信队列
参考博文:https://www.cnblogs.com/theRhyme/p/10874409.html
1.生产者配置交换机和队列
package cwh.rabbitMq.config;
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;
@Configuration
public class DeadLetterConfig {
public static String DEAD_LETTER_EXCHANGE = "deadLetterExchange";
public static String DEAD_LETTER_QUEUE = "deadLetterQueue";
public static String REDIRECT_QUEUE = "deadRedirectQueue";
public static String DEAD_LETTER_REDIRECT_ROUTING_KEY = "deadRedirectRoutingKey";
public static String DEAD_LETTER_ROUTING_KEY ="deadRoutingKey";
//申明交换机
@Bean
public DirectExchange deadLetterExchange(){
return ExchangeBuilder.directExchange(DEAD_LETTER_EXCHANGE).durable(true).build();
}
//申明死信队列
@Bean
public Queue deadLetterQueue(){
Map args = new HashMap<>();
args.put("x-dead-letter-exchange",DEAD_LETTER_EXCHANGE);
args.put("x-dead-letter-routing-key",DEAD_LETTER_REDIRECT_ROUTING_KEY);
return QueueBuilder.durable(DEAD_LETTER_QUEUE).withArguments(args).build();
}
//申明重定向队列
@Bean
public Queue redirectQueue(){
return QueueBuilder.durable(REDIRECT_QUEUE).build();
}
//绑定死信队列到死信交换机上
@Bean
public Binding bindingDead(){
return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with(DEAD_LETTER_ROUTING_KEY);
}
//绑定重定向队列到死信交换机上
@Bean
public Binding bindingRedirect(){
return BindingBuilder.bind(redirectQueue()).to(deadLetterExchange()).with(DEAD_LETTER_REDIRECT_ROUTING_KEY);
}
}
2.设置过期时间
public class myMessagePostProcessor implements MessagePostProcessor {
private long time = 10;
public myMessagePostProcessor(long time) {
this.time = time;
}
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties messageProperties = message.getMessageProperties();
// 设置编码
messageProperties.setContentEncoding("utf-8");
// 设置过期时间10*1000毫秒
messageProperties.setExpiration(Long.toString(time*1000));
return message;
}
}
3.发送消息
@GetMapping("/ttlDeadLetter")
public String ttlDeadLetter(){
Map map = new HashMap<>();
map.put("now",new Date());
map.put("msg","推送这是ttlDeadLetter消息");
myMessagePostProcessor processor = new myMessagePostProcessor(10);
rabbitTemplate.convertAndSend(DeadLetterConfig.DEAD_LETTER_EXCHANGE,DeadLetterConfig.DEAD_LETTER_ROUTING_KEY,map,processor);
return "ok";
}
4.消费者处理死信交换机处理的消息
@Component
@RabbitListener(queues = "deadRedirectQueue")
public class DeadRedirectReceiver {
@RabbitHandler
public void process(Map msg, Channel channel, Message message) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
System.out.println("死信重定向消费端收到"+msg.toString());
}
}
7.延迟队列
demo延迟队列通过rabbitmq_delayed_message_exchange插件来实现的,是为了解决堵塞问题。
1.生产者配置
@Configuration
public class DelayConfig {
public static String DELAY_QUEUE_NAME = "delayQueueName";
public static String DELAY_EXCHANGE_NAME = "delayExchangeName";
public static String DELAY_ROUTING_KEY = "delayRoutingKey";
@Bean
public Queue delayQueue(){
return new Queue(DELAY_QUEUE_NAME);
}
@Bean
public CustomExchange delayExchange(){
Map args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAY_EXCHANGE_NAME,"x-delayed-message",true,false,args);
}
@Bean
public Binding bindingNotify(){
return BindingBuilder.bind(delayQueue()).to(delayExchange()).with(DELAY_ROUTING_KEY).noargs();
}
}
2.生产者时间配置类
public class myDelayPostProcessor implements MessagePostProcessor {
private Integer time = 10;
public myDelayPostProcessor(Integer time) {
this.time = time;
}
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties messageProperties = message.getMessageProperties();
// 设置编码
messageProperties.setContentEncoding("utf-8");
// 设置过期时间10*1000毫秒
messageProperties.setDelay(time*1000);
return message;
}
}
3.生产者消息发送
@GetMapping("/delay")
public String delay(Integer time){
Map map = new HashMap<>();
map.put("now",new Date());
map.put("msg","推送这是时间为"+time+"的delay消息");
rabbitTemplate.convertAndSend(DelayConfig.DELAY_EXCHANGE_NAME,DelayConfig.DELAY_ROUTING_KEY,map,new myDelayPostProcessor(time));
return "ok";
}
4.消费者消费
@Component
@RabbitListener(queues = "delayQueueName")
public class DelayReceiver {
@RabbitHandler
public void process(Map msg, Channel channel, Message message) throws IOException {
try {
//表明消息已收到
// int test = 5/0;
System.out.println("延迟消费端收到"+msg.toString());
System.out.println(new Date());
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
catch (Exception e){
//返回消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
// channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
System.out.println("消息重新回到队列");
}
}
}
demo地址:https://github.com/CAIXIAOHAO-H/RabbitMq-demo.git