前言
在之前的文章中,
rabbitmq的延时任务和普通任务
已经介绍过了普通的方式使用rabbitmq来实现普通任务和延时任务的调度,不过现在基本都会用springboot,所以这里还是来写一篇springboot使用rabbitmq的
一 普通任务
安装erlang,安装rabbitmq,这里就省略了
概念:queue,生产者,消费者,exchange,routeKey
生产者是消息发送者,消费者是消息接收者.这两者中间有exchange和queue.消费者需要标注queue来获取queue中的消息,exchange也就是相当于交换机,到底把哪个生产者的消息发给哪个消费者,这需要exchange根据routekey来定,比如routeKey=”key.a”和queue=”queue_a”绑定了,那么生产者必须在参数里面携带”key.a”才能发送到”queue_a”中去.
Routing key是以小数点隔开的字符串, TopicExchange模式下, 它有,#两个通配符可以用,其中代表一个单词,#代表0个或多个, 比如routeKey=”key.*”,那么生产者带的参数为”key.a”,”key.b”都可以发到”queue_a”
二 搭建springboot工程
直接使用idea的骨架生产就好了,然后pom.xml依赖如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zgd.rabbitmq</groupId>
<artifactId>springboot-rabbitmq-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring Boot Blank Project (from https://github.com/making/spring-boot-blank)</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.zgd.rabbitmq.App</start-class>
<java.version>1.8</java.version>
<lombok.version>1.14.8</lombok.version>
<springboot.version>2.0.4.RELEASE</springboot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>${springboot.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
这里要注意版本的一致,spring-boot-test和springboot的版本要一致
三 代码
3.1 配置类
写一个配置类,配置queue,exchange,绑定binding
在这个类中,定义了三个队列topic.queue_a
,topic.queue_b
,topic.queue_like
,定义了一个exchangeexchange
,分别绑定了三个routeKey topic.queue_a
,topic.queue_b
和topic.*
注意这里bindingExchange这三个方法中的入参Queue_B和exchange都是上面声明的方法名,不是变量名,且需要和方法名一致才能确定绑定的是哪个queue和exchange
而且只在Topic Exchange下,通配routeKey才生效!!!
package com.zgd.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: zgd
* @Date: 18/08/30 15:18
* @Description: 队列配置
*/
@Configuration
public class RabbitConfig {
final static String QUEUE_A = "topic.queue_a";
final static String QUEUE_B = "topic.queue_b";
//模糊队列,满足topic.*的都会发送到这个队列
final static String QUEUE_LIKE = "topic.queue_like";
//------------队列queue只和消费者直接对接-----------
@Bean
public Queue Queue_A() {
return new Queue(QUEUE_A);
}
@Bean
public Queue Queue_B() {
return new Queue(QUEUE_B);
}
@Bean
public Queue Queue_Like() {
return new Queue(QUEUE_LIKE);
}
//--------------生产者通过exchange交换器来决定发送到哪个queue-------------
@Bean
TopicExchange exchange() {
return new TopicExchange("exchange");
}
//-------------绑定exchange和queue,通过with()中的值来作为绑定key,让exchange知道生产者的消息该分配到哪条queue-----------------
/**
* 使用"topic.queue_a"来作为key来匹配生产者和队列,如果生产者使用这个key,就会经过exchange发到QUEUE_A队列
* @param Queue_A
* @param exchange
* @return
*/
@Bean
//入参中的Queue_A和exchange是方法名,不是参数名
Binding bindingExchangeA(Queue Queue_A, TopicExchange exchange) {
return BindingBuilder.bind(Queue_A).to(exchange).with("topic.queue_a");
}
/**
* 使用"topic.queue_b"来作为key来匹配生产者和队列,如果生产者使用这个key,就会经过exchange发到QUEUE_B队列
* @param Queue_B
* @param exchange
* @return
*/
@Bean
Binding bindingExchangeB(Queue Queue_B, TopicExchange exchange) {
return BindingBuilder.bind(Queue_B).to(exchange).with("topic.queue_b");
}
/**
* 使用"topic.#"来作为key来模糊匹配,生产者使用的key是topic.开头的,都会经过exchange发到QUEUE_LIKE队列
* @param Queue_Like
* @param exchange
* @return
*/
@Bean
Binding bindingExchangeLike(Queue Queue_Like, TopicExchange exchange) {
return BindingBuilder.bind(Queue_Like).to(exchange).with("topic.*");
}
}
3.2 生产者
this.rabbitTemplate.convertAndSend()三个参数,第一个是exchange名,第二个是routeKey,第三个是消息
其中sendMessageA和sendMessageB都是一对一,routeKey和queueName一样,完全匹配,sendMessageA只会向topic.queue_a队列发送,b则对应
sendMessageLike中,因为刚刚在配置类配置了routeKey topic.*
,所以是一对多,所有符合topic.*
的都会发到Queue_Like队列中去,包括上面的sendMessageA和sendMessageB
package com.zgd.rabbitmq.producer;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @Author: zgd
* @Date: 18/08/30 15:21
* @Description: 生产者
*/
@Component
public class RabbitmqProducer {
@Autowired
private AmqpTemplate rabbitTemplate;
public void sendMessageA(String msg) {
String context = "[生产者A发送消息]\t"+new Date().toLocaleString() + "\t------------> "+msg ;
System.out.println(context);
this.rabbitTemplate.convertAndSend("exchange","topic.queue_a", msg);
}
public void sendMessageB(String msg) {
String context = "[生产者B发送消息]\t"+new Date().toLocaleString() + "\t------------> "+msg ;
System.out.println(context);
this.rabbitTemplate.convertAndSend("exchange","topic.queue_b", msg);
}
public void sendMessageLike(String msg) {
String context = "[生产者like发送消息]\t"+new Date().toLocaleString() + "\t------------> "+msg ;
System.out.println(context);
this.rabbitTemplate.convertAndSend("exchange","topic.aa",msg);
}
}
3.3 消费者
使用注解@RabbitHandler和@RabbitListener来指定一个queue,监听这个queue,可以监听多个queue消费
package com.zgd.rabbitmq.consumer;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @Author: zgd
* @Date: 18/08/30 15:22
* @Description: 消费者
*/
@Component
public class RabbitmqConsumer {
@RabbitHandler
@RabbitListener(queues = "topic.queue_a")
public void processA(String msg) {
System.out.println("[消费者queue_a收到消息]:\t" + new Date().toLocaleString()+"\t------------> "+ msg);
}
@RabbitHandler
@RabbitListener(queues = "topic.queue_b")
public void processB(String msg) {
System.out.println("[消费者queue_b收到消息]:\t" + new Date().toLocaleString()+"\t------------> "+ msg);
}
@RabbitHandler
@RabbitListener(queues = "topic.queue_like")
public void processLike(String msg) {
System.out.println("[模糊匹配的消费者queue_like收到消息]:\t" + new Date().toLocaleString()+"\t------------> "+ msg);
}
}
3.4 测试
package com.zgd.rabbitmq.test;
import com.zgd.rabbitmq.producer.DelayRabbitmqProducer;
import com.zgd.rabbitmq.producer.RabbitmqProducer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.TimeUnit;
/**
* @Author: zgd
* @Date: 18/08/30 15:28
* @Description:
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqTest {
@Autowired
private RabbitmqProducer producer;
@Test
public void sendMsg() throws Exception {
producer.sendMessageA("AAAAAAAAA");
producer.sendMessageB("BBBBBBBBB");
producer.sendMessageLike("********");
}
}
可以看到模糊匹配,也就是topic模式下,队列topic.queue_like在用topic.*
的routeKey与exchange绑定后,生产者发送topic.queue_a, topic.queue_b, topic.queue_like的消息都经过topic.queue_like,被processLike
消费者消费了, 而processA和processB都是消费各自的消息
四 延时任务
4.1 配置类修改
增加一个样式的队列,exchange
//-----------------------延时任务--------------------------
/**
* 延时任务的exchange
* @return
*/
@Bean
public DirectExchange delayExchange() {
DirectExchange directExchange = new DirectExchange("delay_exchange", true, false);
/**只需简单一步开启延时消息,md找了好久,原来这么简单*/
directExchange.setDelayed(true);
return directExchange;
}
/**
* 延时任务的队列
* @return
*/
@Bean
public Queue delayQueue() {
Queue queue = new Queue("queue.delay");
return queue;
}
@Bean
Binding binding(Queue delayQueue,DirectExchange delayExchange) {
return BindingBuilder.bind(delayQueue).to(delayExchange).with("delay.abc");
}
注意这里不是用的TopicExchange了,而是DirectExchange
4.2 生产者
package com.zgd.rabbitmq.producer;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Timer;
import java.util.concurrent.TimeUnit;
/**
* @Author: zgd
* @Date: 18/08/30 17:46
* @Description: 延时任务生产者
*/
@Component
public class DelayRabbitmqProducer {
@Autowired
private AmqpTemplate rabbitTemplate;
/**
* 发送延时消息
* @param msg
* @param millisecond
*/
public void sendDelay(String msg, final Integer millisecond){
String context = "[延时任务发送消息]\t"+new Date().toLocaleString() + "\t------------> "+msg ;
System.out.println(context);
rabbitTemplate.convertAndSend("delay_exchange", "delay.abc", msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setHeader("x-delay",millisecond);
return message;
}
});
}
}
4.3 消费者
@RabbitHandler
@RabbitListener(queues = "queue.delay")
public void processDelay(String msg) {
System.out.println("[延时任务收到消息]:\t" + new Date().toLocaleString()+"\t------------> "+ msg);
}
4.4 测试
@Autowired
private DelayRabbitmqProducer delayProducer;
@Test
public void sendDelayMsg() throws Exception {
delayProducer.sendDelay("延时消息",10000);
while (true){
}
}
测试成功
代码:
https://github.com/zzzgd/demo-rabbitmq/tree/master/springboot-rabbitmq-demo