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. [LeetCode题解]83. 删除排序链表中的重复元素 | 递归 + 迭代

    方法一:递归 解题思路 通过递归法,每次判断目前头节点与给定的节点是否相等.如是,继续判断下一个节点,否则保存当前头节点,设置 next 指向下次递归得到的节点,然后返回当前节点. 代码 /** * ...

  2. Nacos服务发现源码解析

    1.Spring服务发现的统一规范 Spring将这套规范定义在Spring Cloud Common中 discovery包下面定义了服务发现的规范 核心接口:DiscoveryClient 用于服 ...

  3. Git本地仓库和远程仓库冲突解决

    场景描述: 在本地创建了一个git repo,并且执行了,git init命令,创建了.gitignore文件,或者README.md文件: 在远程创建了一个git repo,创建时也初始化了.git ...

  4. 单线程的Redis有哪些慢动作?

    持续原创输出,点击上方蓝字关注我 目录 前言 为什么 Redis 这么火? 键和值的保存形式? 为什么哈希表操作变慢了? 集合的操作效率? 有哪些数据结构? 不同操作的复杂度? 总结 前言 现在一提到 ...

  5. 面试大厂必看!就凭借这份Java多线程和并发面试题,我拿到了字节和美团的offer!

    最近好多粉丝私信我说在最近的面试中老是被问到多线程和高并发的问题,又对这一块不是很了解,很简单就被面试官给问倒了,被问倒的后果当然就是被刷下去了,因为粉丝要求,我最近也是花了两天时间 给大家整理了这一 ...

  6. 新鲜出炉!凭借着这份面试宝典,我终于拿下了字节跳动的offer!

    前言 我做Java也十来年了,现在也算是中层管理,每次招聘都需要找一些面试题,干脆自己整理了一份,这份面试宝典是从我 去年开始收集的,一方面是给公司招聘用,另一方面也是想用它,来挖掘自己在 Java ...

  7. uniapp兄弟组件如何修改数据?一看就废!

    1. 如A组件里有个num = 10 并需要在生命周期函数created里通过uniapp提供的uni.$on方法来注册全局事件,并加一个形参.( uni.$on( '自定义事件名') , 形参 =& ...

  8. LeetCode周赛#206

    1583. 统计不开心的朋友 #模拟 #暴力 题目链接 题意 有n为朋友,对每位朋友i,preference[i]包含 按亲密度从大到小 的朋友编号. 朋友们会被分为若干对,配对情况由pairs数组给 ...

  9. 三:robot framework常用关键字

    该部分介绍的是内置库:Builtin,估不需要导入,即可使用 1.RF中定义一个变量: ${XXX}   XXX表示:变量名 *** Settings *** *** Test Cases *** 定 ...

  10. 从零做网站开发:基于Flask和JQuery,实现表格管理平台

    摘要:本文将为大家带来基于Flask框架和JQuery实现管理平台网站的开发功能. [写在前面] 你要开发网站? 嗯.. 会Flask吗? 什么东西,没听过... 会JQuery吗? 是python的 ...