springboot集成rabbitMq
0.前言
发现md代码内部不换行的排版问题(复制的代码),是md的缺陷一直没改,在这样下去要放弃md了使用其他编辑器,很影响阅读,所以这篇博客暂时用默认编辑器。
1.新建工程,添加依赖
compile('org.springframework.boot:spring-boot-starter-amqp')
|
2.配置application.yml
#配置服务端
spring:
application:
name: rabbit-test
rabbitmq:
host: 10.2.6.33
port: 5672
username: gaojinliang
password: 【自己对应的密码】
|
3.RabbitMq的基本概念
内部实际上也是AMQP中的基本概念,AMQP中增加了Exchange和Binding的角色。生产者把消息发布到Exchange上,消息最终到达队列并被消费者接收,而
Binding决定交换器的消息应该发送到哪个队列。
Exchange 类型
Exchange分发消息的时候根据类型的不同分发策略有区别,目前有四种类型,direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header
而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:
3.1direct
消息中的路由键(routing key)如果和Binding中的binding key一致,交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发routing key标记为“dog”的消息,不会转发“dog.puppy”,也不会转发“dog.guard”等等。他是完全匹配,单播的模式。
以下是direct的场景,也是最简单,最常用的模式。
MQ配置
@Configuration
public class RabbitQueueConfig {
@Bean
public Queue queue1() {
return new Queue("queue1", true); //持久化
}
}
发送者
@Autowired
private AmqpTemplate rabbitTemplate;
@GetMapping("/queue")
public String sendQueueMsg(@RequestParam("msg") String msg) {
rabbitTemplate.convertAndSend("queue1", msg);
return "success";
}
接收者
@Component
public class QueueReceiver {
@RabbitListener(queues = "queue1")
@RabbitHandler
public void process(String strs) {
System.out.println("接收到:" + strs);
}
}
|
3.2fanout
每个发到fanout类型交换器的消息都会分到所有绑定的队列上。fanout交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被
转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout类型转发消息是最快的。
以下是fanout场景,单个微服务集群的情况下每个节点都绑定到一个direct交换机上 (例子中是服务端口区分队列,也可以是别的key),然后每个节点都能收到
消息做一些配置更新等操作。
MQ配置
@Configuration
public class RabbitFanoutConfig {
@Value("${server.port}")
private String port;
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange ("fanoutExchange");
}
@Bean
public Queue fanoutTp() {
return new Queue("fanout.tp."+port);
}
@Bean
public Binding fanoutBinding() {
return BindingBuilder.bind(fanoutTp()).to(fanoutExchange());
}
}
发送者
@GetMapping("/fanout")
public String sendFanoutMsg(@RequestParam("msg") String msg) {
rabbitTemplate.convertAndSend("fanoutExchange","", msg);
return "success";
}
接收者
@Component
public class FanoutRecerver {
@RabbitListener(queues = "fanout.tp.${server.port}")
@RabbitHandler
public void process(String strs) {
System.out.println("fanout.tp接收到:" + strs);
}
}
|
3.3topic
topic交换器通过模式匹配分配消息的路由键属性,将路由键和单个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定建的字符串切分成单词,这些单词
之间用点隔开。他同样也会识别两个通配符:符号“#”和符号“*”。#匹配0个或多个单词,*匹配不多不少一个单词。
以下是topic场景,单个微服务接收到同一个消息后多个功能协调工作,也适用按分类更新、同一问题需要特定人员知晓等场景
MQ配置:
@Configuration
public class RabbitTopicConfig {
@Bean
public TopicExchange topicExchange() {
return new TopicExchange ("topicExchange");
}
@Bean
public Queue topicTp1() {
return new Queue("topic.tp2.one");
}
@Bean
public Queue topicTp2() {
return new Queue("topic.tp2.two");
}
@Bean
public Binding binding1() {
return BindingBuilder.bind(topicTp1()).to(topicExchange()).with("topic.tp2.*"); //* 后面匹配单个key
}
@Bean
public Binding binding2() {
return BindingBuilder.bind(topicTp2()).to(topicExchange()).with("topic.tp2.#"); //# 后面匹配多个key
}
}
发送者
@GetMapping("/topic")
public String sendTopicMsg(@RequestParam("msg") String msg) {
rabbitTemplate.convertAndSend("topicExchange","topic.tp2.all", msg);
return "success";
}
接收者
@Component
public class TopicRecerver {
@RabbitListener(queues = "topic.tp2.one")
@RabbitHandler
public void process(String strs) {
System.out.println("topic.tp2.one 接收到:" + strs);
}
@RabbitListener(queues = "topic.tp2.two")
@RabbitHandler
public void process2(String strs) {
System.out.println("topic.tp2.two 接收到:" + strs);
}
}
|
4.接收端手动确认
上面提到的内容都是消息自动确认,当我们程序中收到消息处理失败需要MQ重发时就需要用到手动确认。
application.yml 配置 spring.rabbitmq.listener.simple.acknowledge-mode=MANUAL
spring:
application:
name: rabbit-test
rabbitmq:
host: 10.2.6.33
port: 5672
username: liuyanhui
password: liuyanhui
listener:
simple:
acknowledge-mode: MANUAL
在监听一端
@Component
public class QueueReceiver {
@RabbitListener(queues = "queue2")
@RabbitHandler
public void process(String strs, Message message, Channel channel) throws IOException {
System.out.println("接收到:" + strs);
//channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //确认成功
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); //确认失败
}
}
channel.basicAck 确认成功
channel.basicNack 确认失败 : 最后一个参数代表是否需要重发,需要根据实际执行情况做选择,否则可能陷入死循环。