1. MQ

  MQ(Message Queue),消息队列,是生产者和消费者模型中传递信息的容器,主要用于线程或进程之间通信。

  MQ主要的应用场景为:应用解耦、异步处理,流量削锋,日志处理等。

  应用解耦:假设应用要与应用B、C、D通信,当某个应用挂掉或者进行调整后,其他应用都做出相应的调整。但是使用MQ之后,每个应用只需要从消息中间件中发送或消费消息,而不关心其他应用是否为正常状态。

  异步处理:将消息发送到消息中间件中,继续处理下面的业务,而不需要等待消费者消费完成的响应。

  流量削锋:当出现秒杀等业务场景时,短时间内将大量消息存储在消息队列中,当达到消息阈值后,消息队列拒绝接手消息,应用返回错误页面。

  日志处理:业务在处理过程中,将日志使用异步方式存储,不仅不会阻塞业务执行,提高效率,而且消息中间件的可靠性也能满足业务日志不丢失等可靠性要求。

  常见的MQ有:ActiveMQ、RabbitMQ、Kafka、RocketMQ、ZeroMQ,其实Redis也可以支持MQ功能。

2. 常见MQ对比

特性 ActiveMQ RabbitMQ RocketMQ Kafka
单机吞吐量 万级 万级 十万级 十万级
时效性 ms级 μs级 ms级 ms级
高可用性 高,主从架构 高,主从架构 非常高,分布式架构 非常高,分布式架构
消息可靠性 较低概率丢失 不丢失 优化后可不丢失 优化后可不丢失
MQ功能支持 较全 较全 较全 简单
优点 功能较全 延时低,可靠 扩展性好 大数据领域及日志采集上普遍使用

3. RabbitMQ

  RabbitMQ是使用Erlang语言来编写的,并且基于AMQP协议。Erlang语言在数据交互方面性能较优秀,具有和原生Socket一样的延迟,这也是RabbitMQ高性能的原因所在。

  RabbitMQ特点:

  a. 开源、性能交友,消息持久化不丢失,可靠性得到保障

  b. 提供可靠性的消息投递模式、返回模式

  c. 与spring-boot-starter-amqp完美整合,API功能丰富且资料较多

  d. 支持集群,保障高可用

4. AMQP协议

  AMQP(Advanced Message Queuing Protocol),是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端、中间件不同产品、不同开发语言等条件的限制。

  AMQP基本概念:

  Server:服务,接收客户端请求。

  Connection:连接,应用于Server之间的TCP连接。

  Channel:信道,消息的读写在信道中进行。每个客户端可以建立多个通道,每个通道即一个会话任务。

  Message:消息,应用于Server之间传递的数据实体。每个消息由Properties和Body组成,Properties存储消息的优先级、延迟等配置属性,Body及消息内容。

  Virtual Host:虚拟主机,用于Server内部之间逻辑隔离。每个Server之间可以有多个虚拟主机,每个虚拟主机内有多个交换机和队列,每个虚拟主机内的交换机和队列名称不能相同。

  Exchange:交换机,用于接收消息,并按照一定的路由规则将消息绑定到对应的队列。常见的交换机类型有:Direct、Topic、Fanout、Header等。

  Queue:队列,用于保存消息并交给消费者消费消息。

  Binding:绑定,交换机和队列之间的虚拟连接,每个绑定连接中包含对一个或多个路由键。

  RoutingKey:路由键,生产者发生消息时会指定对应的路由键规则,交换机根据规则将消息绑定到具体的队列上,消息者通过队列消息消息。

  概念中提到两个属性:Connection和Channel。既然存在Connection,又为什么需要Channel。原因是:在一个业务场景中必然存在多个线程环境操作RabbitMQ Server进行生产和消费消息,因此将会建立多个Connection,即多个TCP连接。对于操作系统而言,TCP连接的创建和销毁是十分浪费资源的,在高并发使用场景中,势必达到性能瓶颈。所以RabitMQ采用TCP连接复用方式,使用Channel建立应用与Server连接,提高效率,也便于管理。



5. 常见交换机

  • Default Exchange

      默认交换机。实则为名称为空的直连交换机。特点是每个队列都会绑定到默认交换机上,且路由键与队列名称相同。例如:创建名称为“simple-queue”的队列,AMQP代理会自动将该队列绑定到默认交换机上,绑定的路由键也为“simple-queue”。
  • Direct Exchange

      直流交换机。消息携带路由键(RoutingKey)经过该类型交换机时会绑定到规则匹配的队列中。

  • Fanout Exchange

      扇形交换机。交换机直接与队列绑定,及时绑定路由键也不起作用。当生产者发送消息后,消息经过该类型交换机直接绑定到与该交换机绑定的所有队列上。

  • Topic Exchange

      主题交换机。实则为直连交换机和扇形交换机相结合,即模糊匹配。其中规则为星号代表必须为一个单词,井号为零个或多个单词。例如:队列1的绑定键为A.*,队列2的绑定键为B.#,如果消息绑定的路由键为A.C,则队列1会收到;如果消息绑定的路由键为A.C.D,则队列2会收到。

  • Header Exchange

      头交换机。该类型交换机不依赖于路由键,而是由消息内容中的header属性进行匹配。

6. 消息确认

  消费者在消费消息往往会因为网络或其他原因导致异常,因此需要和队列建立确认机制才能表明该条消息已经成功消费。因此在AMQP协议中给出两种建议:

  (1)自动确认模式:即消息发送给消费者后立即从队列中删除;

  (2)手动确认模式:即消费者者将消息者处理后给队列发送确认回执(ACK机制,Acknowledgement),再根据情况删除消息。

7. 安装RabbitMQ

  Docker部署RabbitMQ

8. Direct Exchange示例

  • 创建生产者

  • 修改pom.xml
<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.c3stones</groupId>
<artifactId>direct-exchange-provider-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>direct-exchange-provider-demo</name>
<description>Direct Exchange Provider Demo</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath />
</parent> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies> </project>
  • 添加配置文件application.yml
server:
port: 8980 spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
  • RabbitMQ配置类
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* RabbitMQ配置类
*
* @author CL
*
*/
@Configuration
public class RabbitMqConfig { /**
* 交换机名称
*/
public static final String EXCHANGE_NAME = "c3stones.direct"; /**
* 路由键
*/
public static final String ROUNTING_KEY = "test"; /**
* 队列名称
*/
public static final String QUEUE_NAME = "test.queue"; /**
* 配置Direct交换机
*
* @return
*/
@Bean
public DirectExchange directExchange() {
return new DirectExchange(EXCHANGE_NAME);
} /**
* 配置队列
*
* @return
*/
@Bean
public Queue testQueue() {
return new Queue(QUEUE_NAME);
} /**
* 将队列与交换机通过路由键绑定
*
* @return
*/
@Bean
public Binding binding() {
return BindingBuilder.bind(testQueue()).to(directExchange()).with(ROUNTING_KEY);
} }
  • 创建发送消息Controller
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import com.c3stones.config.RabbitMqConfig; /**
* 发送消息Controller
*
* @author CL
*
*/
@RestController
public class SendMsgController { private static Logger log = LoggerFactory.getLogger(SendMsgController.class); @Autowired
private RabbitTemplate rabbitTemplate; /**
* 发送消息
*
* @param msg 消息内容
* @return
*/
@RequestMapping(value = "/send", method = RequestMethod.GET)
public boolean send(String msg) {
try {
rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_NAME, RabbitMqConfig.ROUNTING_KEY, msg);
} catch (AmqpException e) {
log.error("发送消息异常:{}", e);
return false;
}
return true;
}
}
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; /**
* 启动类
*
* @author CL
*
*/
@SpringBootApplication
public class DirectProviderApplication { public static void main(String[] args) {
SpringApplication.run(DirectProviderApplication.class, args);
} }
  • 启动项目,并测试发送消息

  • 查看RabbitMQ管理界面



      可以看出RabbitMQ Server已经接收到消息,并经过交换机成功转发到队列中,此时消息为等待消费状态。
  • 创建消费者

  • 修改pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.c3stones</groupId>
<artifactId>direct-exchange-consumer-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>direct-exchange-consumer-demo</name>
<description>Direct Exchange Consumer Demo</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath />
</parent> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies> </project>
  • 添加配置文件application.yml
server:
port: 8981 spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
  • 创建处理消息Service
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component; /**
* 处理消息Service
*
* @author CL
*
*/
@Component
public class HandleMsgService { private static Logger log = LoggerFactory.getLogger(HandleMsgService.class); /**
* 方法1-处理消息
*
* @param msg 消息内容
*/
@RabbitListener(queues = "test.queue")
public void handle1(String msg) {
log.info("方法1已接收到消息:{}", msg);
} }
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; /**
* 启动类
*
* @author CL
*
*/
@SpringBootApplication
public class DirectConsumerApplication { public static void main(String[] args) {
SpringApplication.run(DirectConsumerApplication.class, args);
} }
  • 启动项目,并观察控制台打印日志
2020-07-24 16:45:30.284  INFO 5400 --- [ntContainer#0-1] com.c3stones.service.HandleMsgService    : 方法1已接收到消息:测试

  可以看到,在项目启动后消费者Service已经成功监听到消息,并消费。

  • 修改处理消息Service,配置多个监听方法监听同一队列
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component; /**
* 处理消息Service
*
* @author CL
*
*/
@Component
public class HandleMsgService { private static Logger log = LoggerFactory.getLogger(HandleMsgService.class); /**
* 方法1-处理消息
*
* @param msg 消息内容
*/
@RabbitListener(queues = "test.queue")
public void handle1(String msg) {
log.info("方法1已接收到消息:{}", msg);
} /**
* 方法2-处理消息
*
* @param msg 消息内容
*/
@RabbitListener(queues = "test.queue")
public void handle2(String msg) {
log.info("方法2已接收到消息:{}", msg);
} /**
* 方法3-处理消息
*
* @param msg 消息内容
*/
@RabbitListener(queues = "test.queue")
public void handle3(String msg) {
log.info("方法3已接收到消息:{}", msg);
} }
  • 重启消费者,并启动生产者
  • 通过Postman发送三条消息,并观察消费者控制台
2020-07-24 16:47:49.471  INFO 8532 --- [ntContainer#0-1] com.c3stones.service.HandleMsgService    : 方法2已接收到消息:测试1
2020-07-24 16:47:51.567 INFO 8532 --- [ntContainer#1-1] com.c3stones.service.HandleMsgService : 方法1已接收到消息:测试2
2020-07-24 16:47:54.070 INFO 8532 --- [ntContainer#2-1] com.c3stones.service.HandleMsgService : 方法3已接收到消息:测试3

  可以看到多个方法监听队列,并不会重复消费消息,而是轮询消费。

9. Fanout Exchange示例

  • 创建生产者

  • 修改pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.c3stones</groupId>
<artifactId>fanout-exchange-provider-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>fanout-exchange-privoder-demo</name>
<description>Fanout Exchange Provider Demo</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath />
</parent> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies> </project>
  • 添加配置文件application.yml
server:
port: 8982 spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
  • 创建RabbitMQ配置类
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; /**
* RabbitMQ配置类
*
* @author CL
*
*/
@Configuration
public class RabbitMqConfig { /**
* 交换机名称
*/
public static final String EXCHANGE_NAME = "c3stones.fanout"; /**
* 队列1名称
*/
public static final String QUEUE_NAME_1 = "test1.fanout.queue"; /**
* 队列2名称
*/
public static final String QUEUE_NAME_2 = "test2.fanout.queue"; /**
* 队列3名称
*/
public static final String QUEUE_NAME_3 = "test3.fanout.queue"; /**
* 配置Direct交换机
*
* @return
*/
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange(EXCHANGE_NAME);
} /**
* 配置队列1
*
* @return
*/
@Bean
public Queue test1Queue() {
return new Queue(QUEUE_NAME_1);
} /**
* 配置队列2
*
* @return
*/
@Bean
public Queue test2Queue() {
return new Queue(QUEUE_NAME_2);
} /**
* 配置队列3
*
* @return
*/
@Bean
public Queue test3Queue() {
return new Queue(QUEUE_NAME_3);
} /**
* 将队列1与交换机绑定
*
* @return
*/
@Bean
public Binding bindingQueue1() {
return BindingBuilder.bind(test1Queue()).to(fanoutExchange());
} /**
* 将队列2与交换机绑定
*
* @return
*/
@Bean
public Binding bindingQueue2() {
return BindingBuilder.bind(test2Queue()).to(fanoutExchange());
} /**
* 将队列3与交换机绑定
*
* @return
*/
@Bean
public Binding bindingQueue3() {
return BindingBuilder.bind(test3Queue()).to(fanoutExchange());
} }
  • 创建发送消息Controller
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import com.c3stones.config.RabbitMqConfig; /**
* 发送消息Controller
*
* @author CL
*
*/
@RestController
public class SendMsgController { private static Logger log = LoggerFactory.getLogger(SendMsgController.class); @Autowired
private RabbitTemplate rabbitTemplate; /**
* 发送消息
*
* @param msg 消息内容
* @return
*/
@RequestMapping(value = "/send", method = RequestMethod.GET)
public boolean send1(String msg) {
try {
rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_NAME, null, msg);
} catch (AmqpException e) {
log.error("发送消息异常:{}", e);
return false;
}
return true;
} }
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; /**
* 启动类
*
* @author CL
*
*/
@SpringBootApplication
public class FanoutProviderApplication { public static void main(String[] args) {
SpringApplication.run(FanoutProviderApplication.class, args);
} }
  • 启动项目,并测试发送消息

  • 创建消费者

  • 修改pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.c3stones</groupId>
<artifactId>fanout-exchange-consumer-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>fanout-exchange-consumer-demo</name>
<description>Fanout Exchange Consumer Demo</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath />
</parent> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies> </project>
  • 添加配置文件application.yml
server:
port: 8983 spring:
rabbitmq:
host: 1277.0.0.1
port: 5672
username: guest
password: guest
  • 创建处理消息Service
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component; /**
* 处理消息Service
*
* @author CL
*
*/
@Component
public class HandleMsgService { private static Logger log = LoggerFactory.getLogger(HandleMsgService.class); /**
* 方法1-处理消息
*
* @param msg 消息内容
*/
@RabbitListener(queues = "test1.fanout.queue")
public void handle1(String msg) {
log.info("方法1已接收到消息:{}", msg);
} /**
* 方法2-处理消息
*
* @param msg 消息内容
*/
@RabbitListener(queues = "test2.fanout.queue")
public void handle2(String msg) {
log.info("方法2已接收到消息:{}", msg);
} /**
* 方法3-处理消息
*
* @param msg 消息内容
*/
@RabbitListener(queues = "test3.fanout.queue")
public void handle3(String msg) {
log.info("方法3已接收到消息:{}", msg);
} }
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; /**
* 启动类
*
* @author CL
*
*/
@SpringBootApplication
public class FanoutConsumerApplication { public static void main(String[] args) {
SpringApplication.run(FanoutConsumerApplication.class, args);
} }
  • 启动项目,并观察控制台打印日志
2020-07-24 17:11:51.177  INFO 12620 --- [ntContainer#0-1] com.c3stones.service.HandleMsgService    : 方法3已接收到消息:测试
2020-07-24 17:11:51.180 INFO 12620 --- [ntContainer#1-1] com.c3stones.service.HandleMsgService : 方法2已接收到消息:测试
2020-07-24 17:11:51.189 INFO 12620 --- [ntContainer#2-1] com.c3stones.service.HandleMsgService : 方法1已接收到消息:测试

  可以看到,与该类型交换机绑定的队列,均可监听到消息。

10. Topic Exchange示例

  • 创建生产者

  • 修改pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.c3stones</groupId>
<artifactId>topic-exchange-provider-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>topic-exchange-provider-demo</name>
<description>Topic Exchange Provider Demo</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath />
</parent> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies> </project>
  • 添加配置文件application.yml
server:
port: 8984 spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
  • 创建RabbitMQ配置类
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* RabbitMQ配置类
*
* @author CL
*
*/
@Configuration
public class RabbitMqConfig { /**
* 交换机名称
*/
public static final String EXCHANGE_NAME = "c3stones.topic"; /**
* 绑定键1
*/
public static final String BINDING_KEY_1 = "topic.key1"; /**
* 绑定键2
*/
public static final String BINDING_KEY_2 = "topic.key2"; /**
* 绑定键前缀,即以topic.开头的键值都会被监听
*/
public static final String BINDING_KEY_PREFIX = "topic.#"; /**
* 队列1名称
*/
public static final String QUEUE_NAME_1 = "test1.queue"; /**
* 队列2名称
*/
public static final String QUEUE_NAME_2 = "test2.queue"; /**
* 配置Direct交换机
*
* @return
*/
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(EXCHANGE_NAME);
} /**
* 配置队列1
*
* @return
*/
@Bean
public Queue test1Queue() {
return new Queue(QUEUE_NAME_1);
} /**
* 配置队列2
*
* @return
*/
@Bean
public Queue test2Queue() {
return new Queue(QUEUE_NAME_2);
} /**
* 将队列1与交换机通过绑定键1绑定
*
* @return
*/
@Bean
public Binding bindingQueue1() {
return BindingBuilder.bind(test1Queue()).to(topicExchange()).with(BINDING_KEY_1);
} /**
* 将队列2与交换机通过绑定键前缀绑定
*
* @return
*/
@Bean
public Binding bindingQueue2() {
return BindingBuilder.bind(test2Queue()).to(topicExchange()).with(BINDING_KEY_PREFIX);
} }
  • 创建发送消息Controller
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import com.c3stones.config.RabbitMqConfig; /**
* 发送消息Controller
*
* @author CL
*
*/
@RestController
public class SendMsgController { private static Logger log = LoggerFactory.getLogger(SendMsgController.class); @Autowired
private RabbitTemplate rabbitTemplate; /**
* 发送消息1
*
* @param msg 消息内容
* @return
*/
@RequestMapping(value = "/send1", method = RequestMethod.GET)
public boolean send1(String msg) {
try {
rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_NAME, RabbitMqConfig.BINDING_KEY_1, msg);
} catch (AmqpException e) {
log.error("发送消息1异常:{}", e);
return false;
}
return true;
} /**
* 发送消息2
*
* @param msg 消息内容
* @return
*/
@RequestMapping(value = "/send2", method = RequestMethod.GET)
public boolean send2(String msg) {
try {
rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_NAME, RabbitMqConfig.BINDING_KEY_2, msg);
} catch (AmqpException e) {
log.error("发送消息2异常:{}", e);
return false;
}
return true;
}
}
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; /**
* 启动类
*
* @author CL
*
*/
@SpringBootApplication
public class TopicProviderApplication { public static void main(String[] args) {
SpringApplication.run(TopicProviderApplication.class, args);
} }
  • 启动项目,并测试发送两条消息



  • 创建消费者

  • 修改pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.c3stones</groupId>
<artifactId>topic-exchange-consumer-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>topic-exchange-consumer-demo</name>
<description>Topic Exchange Consumer Demo</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath />
</parent> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies> </project>
  • 添加配置文件application.yml
server:
port: 8985 spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
  • 创建处理消息Service
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component; /**
* 处理消息Service
*
* @author CL
*
*/
@Component
public class HandleMsgService { private static Logger log = LoggerFactory.getLogger(HandleMsgService.class); /**
* 方法1-处理队列1消息
*
* @param msg 消息内容
*/
@RabbitListener(queues = "test1.queue")
public void handle1(String msg) {
log.info("方法1已接收到消息:{}", msg);
} /**
* 方法2-处理队列2消息
*
* @param msg 消息内容
*/
@RabbitListener(queues = "test2.queue")
public void handle2(String msg) {
log.info("方法2已接收到消息:{}", msg);
} }
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; /**
* 启动类
*
* @author CL
*
*/
@SpringBootApplication
public class TopicConsumerApplication { public static void main(String[] args) {
SpringApplication.run(TopicConsumerApplication.class, args);
} }
  • 启动项目,并观察控制台打印日志
2020-07-24 18:32:29.916  INFO 3776 --- [ntContainer#1-1] com.c3stones.service.HandleMsgService    : 方法1已接收到消息:测试1
2020-07-24 18:32:29.916 INFO 3776 --- [ntContainer#0-1] com.c3stones.service.HandleMsgService : 方法2已接收到消息:测试1
2020-07-24 18:32:29.919 INFO 3776 --- [ntContainer#0-1] com.c3stones.service.HandleMsgService : 方法2已接收到消息:测试2

  可以看到,方法2监听绑定键以“topic.”开头的消息,即两条消息都会监听到,方法1只监听绑定键为“topic.key1”的消息,即只能监听到第一条消息。

11. 消息确认示例

  以Direct Exchange为例。

  • 修改生产者配置文件application.yml
server:
port: 8980 spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
publisher-confirm: true # 确认消息已发送到交换机
publisher-returns: true # 确认消息已发送到队列
  • 修改RabbitMQ配置类,添加发送确认回调
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* RabbitMQ配置类
*
* @author CL
*
*/
@Configuration
public class RabbitMqConfig { private static Logger log = LoggerFactory.getLogger(RabbitMqConfig.class); @Autowired
private ConnectionFactory connectionFactory; /**
* 交换机名称
*/
public static final String EXCHANGE_NAME = "c3stones.confirm.direct"; /**
* 路由键
*/
public static final String ROUNTING_KEY = "test.confirm.key"; /**
* 队列名称
*/
public static final String QUEUE_NAME = "test.confirm.queue"; /**
* 配置Direct交换机
*
* @return
*/
@Bean
public DirectExchange directExchange() {
return new DirectExchange(EXCHANGE_NAME);
} /**
* 配置队列
*
* @return
*/
@Bean
public Queue testQueue() {
return new Queue(QUEUE_NAME);
} /**
* 将队列与交换机通过路由键绑定
*
* @return
*/
@Bean
public Binding binding() {
return BindingBuilder.bind(testQueue()).to(directExchange()).with(ROUNTING_KEY);
} /**
* 配置消息发送模板
*
* @return
*/
@Bean
public RabbitTemplate createRabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); // 确认消息已发送到交换机
rabbitTemplate.setConfirmCallback(new ConfirmCallback() { @Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (!ack) {
log.error("发送到交换机失败!原因:{}", cause);
}
} }); // 强制调用回调方法
rabbitTemplate.setMandatory(true); // 确认消息已发送到队列
rabbitTemplate.setReturnCallback(new ReturnCallback() { @Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange,
String routingKey) {
log.error("绑定到队列异常,消息:{},回应码:{},回应文本:{},交换机:{},路由键:{}", message, replyCode, replyText, exchange,
routingKey);
} }); return rabbitTemplate;
} }
  • 修改发送消息Controller,添加发送消息ID
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import com.c3stones.config.RabbitMqConfig; /**
* 发送消息Controller
*
* @author CL
*
*/
@RestController
public class SendMsgController { private static Logger log = LoggerFactory.getLogger(SendMsgController.class); @Autowired
private RabbitTemplate rabbitTemplate; /**
* 发送消息
*
* @param msg 消息内容
* @return
*/
@RequestMapping(value = "/send", method = RequestMethod.GET)
public boolean send(String msg) {
try {
rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_NAME, RabbitMqConfig.ROUNTING_KEY, msg,
new CorrelationData(UUID.randomUUID().toString()));
} catch (AmqpException e) {
log.error("发送消息异常:{}", e);
return false;
}
return true;
}
}
  • 启动消息,并测试发送消息

      请在发送消息Controller中分别指定正确的交换机名称正确队列、错误交换机正确队列、错误交换机错误队列、正确交换机错误队列四种情况,并仔细观察控制台。
  • 修改消费者配置文件
server:
port: 8981 spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
listener:
direct:
acknowledge-mode: manual # 手动确认
simple:
acknowledge-mode: manual # 手动确认
  • 修改处理消息Service
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component; import com.rabbitmq.client.Channel; /**
* 处理消息Service
*
* @author CL
*
*/
@Component
public class HandleMsgService { private static Logger log = LoggerFactory.getLogger(HandleMsgService.class); /**
* 方法1-处理消息
*
* @param msg 消息内容
*/
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "test.confirm.queue", durable = "true"), key = "test.confirm.key", exchange = @Exchange("c3stones.confirm.direct")))
public void handle1(Message message, Channel channel) {
try {
log.info("方法1已接收到消息:{}", message.getBody()); // 模拟处理异常
// int a = 1 / 0; // 正常消费,手动应答
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
log.info("方法1处理消息异常:{}", e); // 异常消费,将消息重新放入队列里
try {
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
} catch (IOException e1) {
log.info("将消息重新放入队列里异常:{}", e1);
}
}
} }
  • 启动项目,并测试

      测试正常处理消息,和处理消息时发送异常两种情况,并观察RabbitMQ Server中队列详情。

12. 项目地址

  spring-boot-rabbitmq-demo

SpringBoot整合RabbitMQ实践教程的更多相关文章

  1. 【SpringBoot系列5】SpringBoot整合RabbitMQ

    前言: 因为项目需要用到RabbitMQ,前几天就看了看RabbitMQ的知识,记录下SpringBoot整合RabbitMQ的过程. 给出两个网址: RabbitMQ官方教程:http://www. ...

  2. Springboot 整合RabbitMq ,用心看完这一篇就够了

    该篇文章内容较多,包括有rabbitMq相关的一些简单理论介绍,provider消息推送实例,consumer消息消费实例,Direct.Topic.Fanout的使用,消息回调.手动确认等. (但是 ...

  3. springboot学习笔记-6 springboot整合RabbitMQ

    一 RabbitMQ的介绍 RabbitMQ是消息中间件的一种,消息中间件即分布式系统中完成消息的发送和接收的基础软件.这些软件有很多,包括ActiveMQ(apache公司的),RocketMQ(阿 ...

  4. SpringBoot系列八:SpringBoot整合消息服务(SpringBoot 整合 ActiveMQ、SpringBoot 整合 RabbitMQ、SpringBoot 整合 Kafka)

    声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:SpringBoot 整合消息服务 2.具体内容 对于异步消息组件在实际的应用之中会有两类: · JMS:代表作就是 ...

  5. 一篇学习完rabbitmq基础知识,springboot整合rabbitmq

    一   rabbitmq 介绍 MQ全称为Message Queue,即消息队列, RabbitMQ是由erlang语言开发,基于AMQP(Advanced MessageQueue 高级消息队列协议 ...

  6. 【MQ中间件】RabbitMQ -- SpringBoot整合RabbitMQ(3)

    1.前言说明 前面一篇博客中提到了使用原生java代码进行测试RabbitMQ实现多种交换机类型的队列场景.但是在项目中我们一般使用SpringBoot项目,而且RabbitMQ天生对于Spring的 ...

  7. 功能:SpringBoot整合rabbitmq,长篇幅超详细

    SpringBoot整合rabbitMq 一.介绍 消息队列(Message Queue)简称mq,本文将介绍SpringBoot整合rabbitmq的功能使用 队列是一种数据结构,就像排队一样,遵循 ...

  8. springboot整合rabbitmq实现生产者消息确认、死信交换器、未路由到队列的消息

    在上篇文章  springboot 整合 rabbitmq 中,我们实现了springboot 和rabbitmq的简单整合,这篇文章主要是对上篇文章功能的增强,主要完成如下功能. 需求: 生产者在启 ...

  9. RabbitMQ入门到进阶(Spring整合RabbitMQ&SpringBoot整合RabbitMQ)

    1.MQ简介 MQ 全称为 Message Queue,是在消息的传输过程中保存消息的容器.多用于分布式系统 之间进行通信. 2.为什么要用 MQ 1.流量消峰 没使用MQ 使用了MQ 2.应用解耦 ...

随机推荐

  1. spring boot和spring cloud的区别

    Java中说到微服务肯定离不开Spring Boot和Spring Cloud,这两者到底有什么关系,什么是微服务,如果单纯的说SpringBoot,SpringCloud,都是不准确的,那应该怎么回 ...

  2. nginx 负载均衡设置

    upstream lucky5{ server 127.0.0.1:3000 weight=10; server 127.0.0.1:3001 weight=5; } server{ location ...

  3. 阿里四面P7稳了,得亏我会这些Spring面试题,果然大厂都爱问它们

    前言 先说一下本人情况吧,末流985毕业,毕业之后一直在一家不大不小的公司里安稳上班.上半年因为疫情的原因公司调整了工资,我也是随波逐流跟随大家辞了职.辞职之后向阿里.字节这些都投了简历(但是只收到了 ...

  4. 怎么用在线思维导图Ayoa规划个人任务

    在Ayoa的任务板功能中可以对某一任务进行详细设置,例如改变紧急情况/重要程度.添加到我的计划工具.设置开始日期.截止日期等. 图1:任务详情设置 而这里的"我的计划工具"就是一个 ...

  5. Earmaster——音乐爱好者必备软件

    有很多喜爱音乐但是却由于一些"不可抗力"而没能学习到音乐基础的小伙伴,相信你们在自学乐器或是声乐的时候总会因为基础不扎实而看不懂一些复杂的乐谱,换别的曲子练习之后发现依旧看不懂,由 ...

  6. MathType中余弦函数的输入

    余弦函数是三角函数中十分重要的一个知识点,余弦函数的俩种形式分别为a2=b2+c2-2bccosA和cosA=(b2+c2-a2)/2bc,接下来我们分别介绍一下这俩种形式的输入. 具体步骤如下: 步 ...

  7. 系统兼容软件CrossOver和虚拟机软件,哪个好用?

    想要在Mac上运行Windows软件的方法有很多种,比较常见的有安装双系统以及虚拟机.但是安装双系统会导致一个很大的问题,就是占用了过多的硬盘空间,这样一来会导致可使用的空间减少. 目前来说,大家都不 ...

  8. LeetCode周赛#207

    5519. 重新排列单词间的空格 #字符串 #模拟 题目链接 题意 给定字符串text,该字符串由若干被空格包围的单词组成,也就说两个单词之间至少存在一个空格.现要你重新排列空格,使每对相邻单词间空格 ...

  9. C++基础知识篇:C++ 存储类

    存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期.这些说明符放置在它们所修饰的类型之前.下面列出 C++ 程序中可用的存储类: auto register static extern m ...

  10. Kafka入门之consumer--rebalance流程

    重平衡(rebalance) 旧版本Kafka依托于Zk进行rebalance,新版本consumer使用了Kafka内置的一个全新的组协调协议.对于每个组而言,Kafka的某个broker会被选举为 ...