SpringBoot使用消息队列RabbitMQ

末鹿安然 提交于 2020-01-15 05:51:44

             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

                        安装完成后,会发现:

                               

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

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

           

               

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

                   ①查看用户

                      

 

                   添加用户名和密码

         

                   ③添加角色和权限

                      

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

          

 

               

           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.6</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("定点消息");

    }
}

 

                     测试结果

 

 

 

 

 

 

 

 

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

 

 

 

 

 

 

 

 

 

 

      

                  

 

 

 

         

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!