SpringBoot使用消息队列RabbitMQ

时间:2023-03-09 02:55:17
SpringBoot使用消息队列RabbitMQ

RabbitMQ 即一个消息队列,主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲、消息分发的作用。RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,AMQP,即Advanced Message Queuing Protocol, 高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间, 提高了系统的吞吐量。

简单概念

             几个名词术语:    

        Broker - 简单来说就是消息队列服务器的实体。

      Exchange - 消息路由器,转发消息到绑定的队列上,指定消息按什么规则,路由到哪个队列。

      Queue - 消息队列,用来存储消息,每个消息都会被投入到一个或多个队列。

      Binding - 绑定,它的作用就是把 Exchange 和 Queue 按照路由规则绑定起来。

      RoutingKey - 路由关键字,Exchange 根据这个关键字进行消息投递。

      Producter - 消息生产者,产生消息的程序。

      Consumer - 消息消费者,接收消息的程序。

      Channel - 消息通道,在客户端的每个连接里可建立多个Channel,每个channel代表一个会话。

       一般消息队列都是生产者(Producter)将消息发送到队列(Queue),消费者(Consumer)监听队列进行消费。rabbitmq中一个虚拟主机(默认 /)持有一个或者多个交换机(Exchange)。 用户只能在虚拟主机的粒度进行权限控制,交换机根据一定的策略(RoutingKey)绑定(Binding)到队列(Queue)上, 这样生产者和队列就没有直接联系,而是将消息发送的交换机,交换机再把消息转发到对应绑定的队列上。

交换机(Exchange)作为RabbitMQ的一个重要概念,最常用的有四种类型:

     Direct: 先匹配, 再投送。即在绑定时设定一个routing_key, 消息的routing_key匹配时, 才会被交换器投送到绑定的队列中去. 交换机跟队列必须是精确的对应关                                 系,这种最为简单。

     Topic: 转发消息主要是根据通配符。在这种交换机下,队列和交换机的绑定会定义一种路由模式,那么,通配符就要在这种路由模式和路由键之间匹配后交换                               机才能转发消息,这种可以认为是Direct 的灵活版

Headers: 也是根据规则匹配, 相较于 direct 和 topic 固定地使用 routingkey , headers则是一个自定义匹配规则的类型, 在队列与交换器绑定时会设定一组键值                                   对规则,消息中也包括一组键值对( headers属性),当这些键值对有一对或全部匹配时,消息被投送到对应队列。

Fanout : 消息广播模式,不管路由键或者是路由模式,会把消息发给绑定给它的全部队列,如果配置了routingkey会被忽略

      Ack机制

      在RabbitMQ的消息队列编程中,有个非常重要的概念叫消息确认机制,也就是Ack机制。每个Consumer可能需要一段时间才能处理完收到的数据。如果在这个过程中,Consumer出错了或者异常退出了,而数据还没有处理完成,那么非常不幸,这段数据就丢失了。因为我们采用no-ack的方式进行确认,也就是说,每次Consumer接到数据后,而不管是否处理完成,RabbitMQ Server会立即把这个Message标记为完成,然后从queue中删除了。 如果一个Consumer异常退出了,它处理的数据能够被另外的Consumer处理,这样数据在这种情况下就不会丢失了。

    为了保证数据不被丢失,RabbitMQ支持消息确认机制,即acknowledgments。为了保证数据能被正确处理而不仅仅是被Consumer收到,那么我们不能采用no-ack。而应该是在处理完数据后发送ack。 在处理数据后发送的ack,就是告诉RabbitMQ数据已经被接收,处理完成,RabbitMQ可以去安全的删除它了。

    如果Consumer退出了但是没有发送ack,那么RabbitMQ就会把这个Message发送到下一个Consumer。这样就保证了在Consumer异常退出的情况下数据也不会丢失。

    这里并没有用到超时机制。RabbitMQ仅仅通过Consumer的连接中断来确认该Message并没有被正确处理。也就是说,RabbitMQ给了Consumer足够长的时间来做数据处理。 这样即使你通过Ctr-C中断了Recieve.cs,那么Message也不会丢失了,它会被分发到下一个Consumer。如果忘记了ack,那么后果很严重。当Consumer退出时,Message会重新分发。然后RabbitMQ会占用越来越多的内存,由于RabbitMQ会长时间运行,因此这个“内存泄漏”是致命的

    下面的例子使用手动Ack模式

1.环境准备:

                   RabbitMQ需要安装erlangRabbitMQ Server 

                   erlang是一种通用的面向并发的编程语言 ,RabbitMQ则是由其编写,下载地址:http://www.erlang.org/downloads

  RabbitMQ Server 下载地址 :http://www.rabbitmq.com/install-windows.html

                        安装完成后,会发现:

SpringBoot使用消息队列RabbitMQ

此时,RabbitMQ并未启动,可通过手动点击 rabbitmq_server-3.7.9\sbin 目录下的 rabbitmq-plugins.bat   来启动 ;

也可以通过命令行来添加可视化启动界面:

SpringBoot使用消息队列RabbitMQ

SpringBoot使用消息队列RabbitMQ

创建用户名密码和分配权限:

①查看用户

SpringBoot使用消息队列RabbitMQ

②添加用户名和密码

       SpringBoot使用消息队列RabbitMQ  

③添加角色和权限

SpringBoot使用消息队列RabbitMQ

最后 ,访问http://localhost:15672/ ,输入用户密码

SpringBoot使用消息队列RabbitMQ

2.SpringBoot中集成

maven依赖

  

    <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<exclusions>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

配置文件


##########################################################
################## 所有profile共有的配置 #################
########################################################## ################### spring配置 ###################
spring:
profiles:
active: dev --- #####################################################################
######################## 开发环境profile ##########################
#####################################################################
spring:
profiles: dev
rabbitmq:
host: 127.0.0.1
port: 5672
username: spring
password: 123456
publisher-confirms: true #支持发布确认
publisher-returns: true #支持发布返回
listener:
simple:
acknowledge-mode: manual #采用手动应答
concurrency: 1 #指定最小的消费者数量
max-concurrency: 1 #指定最大的消费者数量
retry:
enabled: true #是否支持重试 logging:
level:
ROOT: INFO
com:
xncoding: DEBUG
file: D:/logs/app.log #日志路径

 

配置类

最核心的类就是RabbitMQ的配置类,在里面定制模版类、声明交换机、队列、绑定交换机到队列。这里声明了一个Direct类型的交换机,并通过路由键绑定到一个队列中来测试Direct模式, 另外还声明了Fanout类型的交换机,并绑定到2个队列来测试广播模式;

监听器

                                编写消息队列的监听器类,监听队列消息并做相应的处理,并通过Ack机制确认处理完成:

                  

@Component
public class Receiver {
private static final Logger log = LoggerFactory.getLogger(Receiver.class); /**
* FANOUT广播队列监听一.
*
* @param message the message
* @param channel the channel
* @throws IOException the io exception 这里异常需要处理
*/
@RabbitListener(queues = {"FANOUT_QUEUE_A"})
public void on(Message message, Channel channel) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
log.debug("FANOUT_QUEUE_A " + new String(message.getBody()));
} /**
* FANOUT广播队列监听二.
*
* @param message the message
* @param channel the channel
* @throws IOException the io exception 这里异常需要处理
*/
@RabbitListener(queues = {"FANOUT_QUEUE_B"})
public void t(Message message, Channel channel) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
log.debug("FANOUT_QUEUE_B " + new String(message.getBody()));
} /**
* DIRECT模式.
*
* @param message the message
* @param channel the channel
* @throws IOException the io exception 这里异常需要处理
*/
@RabbitListener(queues = {"DIRECT_QUEUE"})
public void message(Message message, Channel channel) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
log.debug("DIRECT " + new String(message.getBody()));
}
}

消息发送者

                                  再写一个消息发送服务,用来向交换机发送消息:

/**
* 消息发送服务
*/
@Service
public class SenderService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private RabbitTemplate rabbitTemplate; /**
* 测试广播模式.
*
* @param p the p
* @return the response entity
*/
public void broadcast(String p) {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("FANOUT_EXCHANGE", "", p, correlationData);
} /**
* 测试Direct模式.
*
* @param p the p
* @return the response entity
*/
public void direct(String p) {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("DIRECT_EXCHANGE", "DIRECT_ROUTING_KEY", p, correlationData);
} }

运行测试

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class SenderServiceTest {
@Autowired
private SenderService senderService; @Test
public void testCache() {
// 测试广播模式
senderService.broadcast("同学们集合啦!");
// 测试Direct模式
senderService.direct("定点消息"); }
}

                     测试结果

SpringBoot使用消息队列RabbitMQ

SpringBoot使用消息队列RabbitMQ

代码下载地址:https://pan.baidu.com/s/1fAvUM3XKIqvFAZwk7c1YvA