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. linux 身份鉴别口令复杂度整改

    口令复杂度: 1.首先安装apt install libpam-cracklib -y2.vim /etc/pam.d/common-password3.在第2步末尾添加password requis ...

  2. 【进阶之路】Mybatis-Plus中乐观锁@version注解的问题与解决方案

    大家好,我是练习java两年半时间的南橘,从一名连java有几种数据结构都不懂超级小白,到现在懂了一点点的进阶小白,学到了不少的东西.知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了 ...

  3. elasticsearch快速安装启动

    准备 docker docker内安装centos容器,模拟服务器环境 centos容器安装 下载centos容器 docker pull centos 启动docker容器 docker run - ...

  4. 面试BAT问的最多的27道MyBatis 面试题(含答案和思维导图总结)

    前言 关于MyBatis总结了一个思维导图希望对大家有帮助 什么是 Mybatis? Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身, ...

  5. guitar pro系列教程(二十六):Guitar Pro教程之虚拟吉他功能讲解

    上一章节我们讲述了Guitar Pro的组织小节的相关功能,那么本章节我们还是采用图文结合的方式为大家讲解关于guitar pro中一些虚拟的吉他功能一 一做出讲解,感兴趣的朋友可以一起进来学习了解哦 ...

  6. Go-Web编程_表单_0x02_验证表单的输入

    开发Web的一个原则就是,不能信任用户输入的任何信息,所以验证和过滤用户的输入信息就变得非常重要,我们经常会在微博.新闻中听到某某网站被入侵了,存在什么漏洞,这些大多是因为网站对于用户输入的信息没有做 ...

  7. python3安装mysqlclient,解决django使用pymysql报错的问题

    1.起因 在django中为了使用MySQL,一般是在项目目录下的__init__.py中添加 import pymysql pymysql.install_as_MySQLdb() # 使用pymy ...

  8. Java基础教程——运算符

    运算符 算术运算符 加 减 乘 除 取余 自加 自减 + - * / % ++ -- public class 算术运算符 { public static void main(String[] arg ...

  9. 基于混沌Logistic加密算法的图片加密与还原

    摘要 一种基于混沌Logistic加密算法的图片加密与还原的方法,并利用Lena图和Baboon图来验证这种加密算法的加密效果.为了能够体现该算法在图片信息加密的效果,本文还采用了普通行列置乱加密算法 ...

  10. Cys_Control(三) MTextBox

    一.查看TextBox原样式 通过Blend查看TextBox原有样式 <Window.Resources> <SolidColorBrush x:Key="TextBox ...