SpringBoot整合RabbitMQ
AMQP简介
AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个线路层的协议规范,而不是API规范(例如JMS)。
由于AMQP是一个线路层协议规范,因此它天然就是跨平台的,就像SMTP、HTTP等协议一样,只要开发者按照规范的格式发送数据,任何平台都可以通过AMQP进行消息交互。
像目前流行的StormMQ、RabbitMQ等都实现了AMQP。
和JMS一样,使用AMQP也是使用AMQP的某个实现,本案例以RabbitMQ为例介绍AMQP的使用。
RabbitMQ简介
RabbitMQ是一个实现了AMQP的开源消息中间件,使用高性能的Erlang编写。RabbitMQ具有可靠性、支持多种协议、高可用、支持消息集群以及多语言客户端等特点,在分布式系统中存储转发消息,具有不错的性能表现。
添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
配置RabbitMQ的基本连接信息:
spring:
rabbitmq:
host: 172.19.25.170
port: 5672
username: xxx
password: 123
RabbitMQ配置:
在RabbitMQ中,所有的消息生产者提交的消息都会交由Exchange进行再分配,Exchange会根据不同的策略将消息分发到不同的Queue中。
RabbitMQ中一共提供了4种不同的Exchange策略,分别是Direct、Fanout、Topic以及Header,这4种不同的策略中,前3种的使用频率较高,第4种的使用频率较低。
(1)Direct
DirectExchange的路由策略是将消息队列绑定到一个DirectExchange上,当一条消息到达DirectExchange时会被转发到与该条消息routing key相同的Queue上
DirectExchange的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // DirectExchange和Binding两个Bean的配置可以省略掉,即如果使用DirectExchange,只配置一个Queue的实例即可 @Configuration public class RabbitDirectConfig { public final static String DIRECTNAME = "sang-direct" ; @Bean Queue queue() { // 首先提供一个消息队列Queue return new Queue( "hello-queue" ); } @Bean DirectExchange directExchange() { // 然后创建一个DirectExchange对象,三个参数分别是名字、重启后是否依然有效以及长期未用时是否删除 return new DirectExchange(DIRECTNAME, true , false ); } @Bean Binding binding() { return BindingBuilder.bind(queue()).to(directExchange()).with( "direct" ); } } |
接下来配置一个消费者:
1 2 3 4 5 6 7 8 9 10 11 | @Slf4j @Component public class DirectReceiver { // 通过@RabbitListener注解指定一个方法是一个消息消费方法,方法参数就是所接收到的消息 @RabbitListener (queues = "hello-queue" ) public void handler1(String msg) { log.info( "DirectReceiver:" + msg); } } |
(2)Fanout
FanoutExchange的数据交换策略是把所有到达FanoutExchange的消息转发给所有与它绑定的Queue,在这种策略中,routingkey将不起任何作用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | @Configuration public class RabbitFanoutConfig { public final static String FANOUTNAME = "sang-fanout" ; @Bean FanoutExchange fanoutExchange() { // 首先创建FanoutExchange,参数的含义与创建DirectExchange参数的含义一致 return new FanoutExchange(FANOUTNAME, true , false ); } // 然后创建两个Queue,再将这两个Queue都绑定到FanoutExchange上 @Bean Queue queueOne() { return new Queue( "queue-one" ); } @Bean Queue queueTwo() { return new Queue( "queue-two" ); } @Bean Binding bindingOne() { return BindingBuilder.bind(queueOne()).to(fanoutExchange()); } @Bean Binding bindingTwo() { return BindingBuilder.bind(queueTwo()).to(fanoutExchange()); } } @Component public class FanoutReceiver { @RabbitListener (queues = "queue-one" ) public void handler1(String message) { System.out.println( "FanoutReceiver:handler1:" + message); } @RabbitListener (queues = "queue-two" ) public void handler2(String message) { System.out.println( "FanoutReceiver:handler2:" + message); } } |
(3)Topic
TopicExchange是比较复杂也比较灵活的一种路由策略,在TopicExchange中,Queue通过routingkey绑定到TopicExchange上,当消息到达TopicExchange后,TopicExchange根据消息的routingkey将消息路由到一个或者多个Queue上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | @Configuration public class RabbitHeaderConfig { public final static String HEADERNAME = "sang-header" ; @Bean HeadersExchange headersExchange() { return new HeadersExchange(HEADERNAME, true , false ); } @Bean Queue queueName() { return new Queue( "name-queue" ); } @Bean Queue queueAge() { return new Queue( "age-queue" ); } @Bean Binding bindingName() { Map<String, Object> map = new HashMap<>(); map.put( "name" , "sang" ); /* * whereAny表示消息的Header中只要有一个Header匹配上map中的key/value,就把该消息路由到名为“name-queue”的Queue上 * 这里也可以使用whereAll方法,表示消息的所有Header都要匹配 * whereAny和whereAll实际上对应了一个名为x-match的属性 */ return BindingBuilder.bind(queueName()).to(headersExchange()).whereAny(map).match(); } @Bean Binding bindingAge() { // bindingAge中的配置则表示只要消息的Header中包含age,无论age的值是多少,都将消息路由到名为“age-queue”的Queue上 return BindingBuilder.bind(queueAge()).to(headersExchange()).where( "age" ).exists(); } } @Component public class HeaderReceiver { @RabbitListener (queues = "name-queue" ) public void handler1( byte [] msg) { System.out.println( "HeaderReceiver:name:" + new String(msg, 0 , msg.length)); } @RabbitListener (queues = "age-queue" ) public void handler2( byte [] msg) { System.out.println( "HeaderReceiver:age:" + new String(msg, 0 , msg.length)); } } |
(4)Header
HeadersExchange是一种使用较少的路由策略,HeadersExchange会根据消息的Header将消息路由到不同的Queue上,这种策略也和routingkey无关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | @Configuration public class RabbitTopicConfig { public final static String TOPICNAME = "sang-topic" ; @Bean TopicExchange topicExchange() { // 首先创建TopicExchange,参数和前面的一致 return new TopicExchange(TOPICNAME, true , false ); } // 然后创建三个Queue,第一个Queue用来存储和“xiaomi”有关的消息,第二个Queue用来存储和“huawei”有关的消息,第三个Queue用来存储和“phone”有关的消息。 @Bean Queue xiaomi() { return new Queue( "xiaomi" ); } @Bean Queue huawei() { return new Queue( "huawei" ); } @Bean Queue phone() { return new Queue( "phone" ); } // 将三个Queue分别绑定到TopicExchange上,第一个Binding中的“xiaomi.#”表示消息的routingkey凡是以“xiaomi”开头的,都将被路由到名称为“xiaomi”的Queue上 @Bean Binding xiaomiBinding() { return BindingBuilder.bind(xiaomi()).to(topicExchange()).with( "xiaomi.#" ); } // 第二个Binding中的“huawei.#”表示消息的routingkey凡是以“huawei”开头的,都将被路由到名称为“huawei”的Queue上 @Bean Binding huaweiBinding() { return BindingBuilder.bind(huawei()).to(topicExchange()).with( "huawei.#" ); } // 第三个Binding中的“#.phone.#”则表示消息的routingkey中凡是包含“phone”的,都将被路由到名称为“phone”的Queue上 @Bean Binding phoneBinding() { return BindingBuilder.bind(phone()).to(topicExchange()).with( "#.phone.#" ); } } @Component public class TopicReceiver { @RabbitListener (queues = "phone" ) public void handler1(String message) { System.out.println( "PhoneReceiver:" + message); } @RabbitListener (queues = "xiaomi" ) public void handler2(String message) { System.out.println( "XiaoMiReceiver:" + message); } @RabbitListener (queues = "huawei" ) public void handler3(String message) { System.out.println( "HuaWeiReceiver:" + message); } } |
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | @Slf4j @Api (tags = "RabbitMqSendController" ) @RequestMapping ( "/mq" ) @RestController public class RabbitMqSendController { @Autowired RabbitTemplate rabbitTemplate; @GetMapping (value = "/directTest" ) public void directTest(String message) { log.info( "directTest:" + message); rabbitTemplate.convertAndSend( "hello-queue" , message); } @GetMapping (value = "/fanoutTest" ) public void fanoutTest(String message) { log.info( "fanoutTest:" + message); rabbitTemplate.convertAndSend(RabbitFanoutConfig.FANOUTNAME, "" , message); } @GetMapping (value = "/topicTest" ) public void topicTest() { rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "xiaomi.news" , "小米新闻.." ); rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "huawei.news" , "华为新闻.." ); rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "xiaomi.phone" , "小米手机.." ); rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "huawei.phone" , "华为手机.. " ); rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME, "phone.news" , "手机新闻.." ); } @GetMapping (value = "/headerTest" ) public void headerTest() { Message nameMsg = MessageBuilder.withBody( "hello header! name-queue" .getBytes()).setHeader( "name" , "sang" ).build(); Message ageMsg = MessageBuilder.withBody( "hello header! age-queue" .getBytes()).setHeader( "age" , "99" ).build(); rabbitTemplate.send(RabbitHeaderConfig.HEADERNAME, null , ageMsg); rabbitTemplate.send(RabbitHeaderConfig.HEADERNAME, null , nameMsg); } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律