spring boot 整合rabbit MQ

spring boot 整合rabbit MQ

rabbit MQ

rabbit MQ 是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用[Erlang]语言编写的,而集群和故障转移是构建在[开放电信平台]框架上的。所有主要的编程语言均有与代理接口通讯的客户端[库] 来自百度百科 rabbit MQ

  • rabbit 和其他消息中间件不同的是,rabbit有一个exchange路由的概念
  • exchange 交换机,就相当于一个路由一样,把我们的消息分发到不通的queue ,rabbit支持三种exchange
    • direct 顾名思义,就是直连,相当于点对点的模式
    • topic 广播订阅模式,但是它可以根据我们的路由key的不通,转发到不通的人queue
    • fanout 广播订阅模式 ,这种模式下,路由key会失效,它会将消息发送到所有绑定的queue

与spring boot 的整合

直接参照spring.io的官方文档,讲的很清楚简单,贴一下链接

  • 引入相关依赖

     <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-amqpartifactId>
    dependency>
    
  • 启动类

    @SpringBootApplication
    public class SpringBootRabbitProduceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringBootRabbitProduceApplication.class, args);
        }
    
  • 配置服务器信息

    spring:
      rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest
    
  • rabbit 配置类,这里具体解析下

    • Queue queue() 注册一个bean,类型是一个queue对象,

    • TopicExchange topicExchange() ,注册一个topic exchange

    • Binding bindingQueueA() 对queue和exchange进行绑定

      BindingBuilder.bind(queueA()).to(topicExchange()).with("topic.a")表示绑定queueA 这个队列到topicExchange 上,路由key是topic.a ,表示只有匹配topic.a的消息转发到 queueA这个队列上

      BindingBuilder.bind(queueB()).to(topicExchange()).with("topic.#") 表示绑定queueB到topicExchange 上,路由key是topic.#", .#表示topic开头的,无论后面是什么都会转发到绑定的queue上面

    • FanoutExchange 上面介绍过,绑定在FanoutExchange上面的,会忽略路由key,所有的详细都会发送到其绑定的所有queue上面

    @Configuration
    public class RabbitMQConfig {
    
        @Bean
        public Queue queue(){
            return new Queue("testQueue");
        }
    
        //------------------------测试topic-exchange-------------------------------------------------------------//
        @Bean
        public Queue queueA(){
            return new Queue("queueA");
        }
        @Bean
        public Queue queueB(){
            return new Queue("queueB");
        }
        @Bean
        public TopicExchange topicExchange(){
            return new TopicExchange("topic");
        }
        @Bean
        public Binding bindingQueueA(){
            //绑定 topic.a 才会路由到queueA
            return BindingBuilder.bind(queueA()).to(topicExchange()).with("topic.a");
        }
        @Bean
        public Binding bindingQueueB(){
            //绑定 topic.# 只要是topic.开头的都路由到queueB
            return BindingBuilder.bind(queueB()).to(topicExchange()).with("topic.#");
        }
    
    
        //------------------------测试 fanout exchange--------------------------------------------------------------//
    
        @Bean
        public Queue queueC(){
            return new Queue("queueC");
        }
        @Bean
        public Queue queueD(){
            return new Queue("queueD");
        }
    
        @Bean
        public FanoutExchange fanoutExchange(){
            return new FanoutExchange("fanout");
        }
    
        @Bean
        public Binding bindingQueueC(){
            return BindingBuilder.bind(queueC()).to(fanoutExchange());
        }
    
        @Bean
        public Binding bindingQueueD(){
            return BindingBuilder.bind(queueD()).to(fanoutExchange());
        }
    }
    
  • 消费

    直接在类或者方法上加上 @RabbitListener(queues = "queueName") 注解,queues表示要消费的队列

        /**
         * 消费 topic exchange queueA 消息
         * @param message
         */
       // @RabbitListener(queues = "queueA")
        public void consumerQueueA(Object message){
            try {
                log.info("----------------------------收到queueA消息:{}",message);
            }catch (Exception e){
                log.info("--",e);
            }
        }
        /**
         * 消费 topic exchange queueB 消息
         * @param message
         */
       // @RabbitListener(queues = "queueB")
        public void consumerQueueB(Object message){
            try {
                log.info("----------------------------收到queueB消息:{}",message);
            }catch (Exception e){
                log.info("--",e);
            }
        }
        /**
         * 消费 topic exchange queueB 消息
         * @param message
         */
        @RabbitListener(queues = "queueC")
        public void consumerQueueC(Object message){
            try {
                log.info("----------------------------queueC收到消息:{}",message);
            }catch (Exception e){
                log.info("--",e);
            }
        }
        /**
         * 消费 topic exchange queueB 消息
         * @param message
         */
        @RabbitListener(queues = "queueD")
        public void consumerQueueD(Object message){
            try {
                log.info("----------------------------queueD收到消息:{}",message);
            }catch (Exception e){
                log.info("--",e);
            }
        }
    
  • 发送消息

    • AmqpTemplate 直接注入AmqpTemplate 就可以发送消息,

    • amqpTemplate.convertAndSend(“topic”,“topic.a”,"–topic.a-- -----测试消息");

      第一个参数topic 表示要发送到那个exchange

      第二个参数"topic.a表示消息的路由key,

      第三个参数表示消息的内容,可以是String,也可以是object,注意需要实现序列化接口

    @Autowired
        private AmqpTemplate amqpTemplate;
        @Test
        public void testSendTopic(){
            //这里,第一条消息会发送到 queueA和queueB ,而下面两条只会发送到queueB
            amqpTemplate.convertAndSend("topic","topic.a","--topic.a--  -----测试消息");
            amqpTemplate.convertAndSend("topic","topic.b","--topic.b--  -----测试消息");
            amqpTemplate.convertAndSend("topic","topic.c","--topic.c--  -----测试消息");
        }
    @Test
        public void testSendFanout(){
            //这里,消息会发送到所有绑定的queue
            amqpTemplate.convertAndSend("fanout","fanout.a","--topic.a--  -----测试消息");
            amqpTemplate.convertAndSend("fanout","fanout.b","--topic.b--  -----测试消息");
            amqpTemplate.convertAndSend("fanout","fanout.c","--topic.c--  -----测试消息");
        }
    

消息消费的手动ack

ack就是消息消费完成之后,消费端给服务器一个回复,告诉服务器这条消息消费完成,服务器就会从对应的queue上面删除消费完成的消息,如果客户端没有完成ack,服务端会在这条消息加一个标示unackd,当unackd过多解释就行影响我们的消息的消费

消息的消费默认是客户端自动ack的,某些业务场景可能需要我们手动确认消息的消费,比如如果我们的消息消费失败之后,客户端发送一个nack给服务器,服务器就会吧这条消息重新回到ready状态,之后就可以重新去消费这条消息

配置消息手动ack

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    listener:
      simple:
        acknowledge-mode: manual

手动ack消息

  • channel.basicAck(tag,false) 手动的确认消息的消费成功,注意第二个个参数如果为true就会吧前面没有ack的消息一起ack了
  • channel.basicNack(tag,false,true)消息消费失败,手手动发送消息消费失败
    • 出现异常,或者消息消费失败 Nack 之后消息就会重新回到ready状态,可以重新进行消费
    • 第三个参数设为false,rabbit会在nack到达某个阈值时把这条消息放到死信队列中Dead Letter,
    • 死信队列也是一个队列,也可以通过客户端来消息,来对异常的消息做特殊处理
 /**
     * 消费 topic exchange queueB 消息
     *  channel client注册的通道
     *  tag amqp_deliveryTag 消息的tag
     * @param message
     */
    @RabbitListener(queues = "queueD")
    public void consumerQueueD(Object message, Channel channel,@Header(AmqpHeaders.DELIVERY_TAG) long tag){
        try {
            log.info("----------------------------queueD收到消息:{}",message);
            //手动确认消息的消费,注意第二个个参数如果为true就会吧前面没有ack的消息一起ack了
            channel.basicAck(tag,false);
        }catch (Exception e){
            log.info("--",e);
            //出现异常,或者消息消费失败 Nack 之后消息就会重新回到ready状态,可以重新进行消费
            //第三个参数设为false,rabbit会在nack到达某个阈值时把这条消息放到死信队列中Dead Letter,
            //死信队列也是一个队列,也可以通过客户端来消息
            try {
                channel.basicNack(tag,false,true);
            } catch (IOException e1) {
                log.info("--",e);
            }
        }
    }

未完待续…

你可能感兴趣的:(spring-boot,spring,rabbitMQ)