SpringBoot整合RabbitMQ學習記錄

RabbitMQ安裝過程在此就不說了,去官網下載安裝啓動運行即可
RabbitMQ下載地址
啓動後可進入後臺管理控制檯查看如下:
在這裏插入圖片描述

RabbitMQ簡介

RabbitMQ是一個開源的AMQP實現,服務器端用Erlang語言編寫,支持多種語言平臺的客戶端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等。用於在分佈式系統中存儲轉發消息,在易用性、擴展性、高可用性等方面表現不俗。

通常我們談到消息隊列, 會有三個概念: 消息生產者(Provider)、隊列(Queue)、消息消費者(Consumer),RabbitMQ 在這個基本概念上, 多做了一層抽象, 在消息生產者和隊列之間, 加入了交換器 (Exchange)。這樣消息生產者和隊列就沒有直接聯繫, 變成消息生產者把消息發送給交換器, 交換器根據調度策略再把消息發送給隊列。
在這裏插入圖片描述

  1. 左側P代表消息生產者,也就是往RabbitMQ發消息的程序。
  2. 中間即是RabbitMQ,其中包括交換機(Exchange)和隊列(Queue)。
  3. 右側C代表消費者,也就是往RabbitMQ拿消息的程序。

其中比較重要的概念有:虛擬主機(Virtual Host)交換機(Exchange)隊列(Queue)綁定(Binding)

  • 虛擬主機(Virtual Hosts):
    在上面已經說明如何爲一個用戶創建一個Virtual Host,一個虛擬主機持有一組交換機、隊列和綁定。在RabbitMQ當中,用戶只能在虛擬主機這個粒度上進行權限的控制。 如果需要禁止A組訪問B組的交換機/隊列/綁定,必須爲A和B分別創建一個虛擬主機。每一個RabbitMQ服務器都有一個默認的虛擬主機“/”。

  • 交換機(Exchange):
    交換機的功能主要是接收消息並且根據轉發策略轉發到對應的隊列,交換機不存儲消息,在啓用ack模式後,交換機找不到隊列會返回錯誤,這個ack模式後面再詳細討論。交換機有四種類型:Direct, topic, Headers and Fanout

  • 隊列(Queue):
    隊列用於存放消息的載體,一般是和交換機進行綁定,交換機根據轉發策略把消息轉發到隊列裏。

  • 綁定(Binding):
    也就是交換機需要和隊列相綁定,這其中如上圖所示,是多對多的關係。

交換機類型介紹

Direct Exchange:

在這裏插入圖片描述
direct 類型的行爲是”先匹配, 再投送”. 即在綁定時設定一個binding_key, 消息的routing_keybinding_key匹配時, 纔會被交換器投送到綁定的隊列中去.

Topic:

轉發消息主要是根據通配符。 在這種交換機下,隊列和交換機的綁定會定義一種路由模式,通配符就要在這種路由模式和路由鍵之間匹配後交換機才能轉發消息。

  1. 路由鍵必須是一串字符,用句號(.)隔開,比如說 topic.message,或者 topic.message.detail 等。
  2. 路由模式必須包含一個星號(),主要用於匹配路由鍵指定位置的一個單詞,比如說,一個路由模式是這樣:topic.,那麼就只能匹配路由鍵是:topic.message、topic.other等,第一個單詞是 topic,第二個單詞可以是任意一個單詞。 井號(#)就表示一個或者多個單詞,例如一個匹配模式是topic.#,那麼可以匹配到例如:topic.message、topic.message.detail等,以topic.開頭的路由鍵都可以匹配到。

Fanout:

Fanout類型類似於消息廣播,不管路由鍵或者是路由模式,會把消息發給綁定給它的全部隊列,如果配置了routing_key會被忽略。

Headers:

設置header attribute參數類型的交換機

show you code

本文是基於Springboot-1.5.15整合RabbitMQ來進行講解,在真實工作中,生產者和消費者一般是在不同的項目裏,各自負責不同的職責,這裏爲了模擬真實環境,創建兩個不同的項目進行演示。創建兩個maven項目,消息生產者mq-rabbit-provider和消息消費者mq-rabbit-consumer,兩個項目的pom.xml文件添加相同依賴:

<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>


mq-rabbit-provider項目的application.properties內容如下:

server.port=8080
spring.application.name=springboot-rabbitmq-provider
 
spring.rabbitmq.host=10.211.55.3
spring.rabbitmq.port=5672
spring.rabbitmq.username=root
spring.rabbitmq.password=root
#RabbitMQ的虛擬host
spring.rabbitmq.virtual-host=CalonHost

mq-rabbit-consumer項目的application.properties內容如下:


server.port=9090
spring.application.name=springboot-rabbitmq-consumer
 
spring.rabbitmq.host=10.211.55.3
spring.rabbitmq.port=5672
spring.rabbitmq.username=root
spring.rabbitmq.password=root
#RabbitMQ的虛擬host
spring.rabbitmq.virtual-host=CalonHost

這裏只是端口和應用名不同,其他都一樣。

接下來分別介紹Direct、Topic、Fanout等3種不同交換機的使用例子。

Direct Exchange

在mq-rabbit-provider項目建一個配置類DirectRabbitConfig.java,配置交換機、隊列、BindingKey=CalonDirectRouting的綁定關係,代碼如下:

@Configuration
public class DirectRabbitConfig {
 
    //隊列
	@Bean
    public Queue CalonDirectQueue() {
        return new Queue("CalonDirectQueue",true);
    }
	
    //Direct交換機
	@Bean
    DirectExchange CalonDirectExchange() {
        return new DirectExchange("CalonDirectExchange");
    }
	
    //綁定
	@Bean
    Binding bindingDirect() {
        return BindingBuilder.bind(CalonDirectQueue()).to(CalonDirectExchange()).with("CalonDirectRouting");
    }
}


創建一個實體類User.java,這裏說明一下,該實體類是消息的主體,所以必須實現Serializable接口,否則在消息消費者項目讀取消息時會報錯,代碼如下:

package mq.rabbit.entity;
 
import java.io.Serializable;
 
public class User implements Serializable{
	
	private static final long serialVersionUID = 1L;
	
	private String id;
	private String username;
	private String password;
	private String type;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public User() {
		super();
	}
	public User(String id, String username, String password, String type) {
		super();
		this.id = id;
		this.username = username;
		this.password = password;
		this.type = type;
	}
}

下面創建一個Controller,利用http請求進行調試,CalonDirectExchange是上面配置的交換機標識,CalonDirectRouting就是上面綁定好的queue名字,由於上面已經配置好交換機和隊列的綁定關係,這兩個組合就可以知道消息最終是發送到隊列CalonDirectQueue裏面去了,Controller類的代碼如下:

@Controller
public class SendController {
 
	@Autowired
	private RabbitTemplate template;
	
	@GetMapping("/sendDirect")
	private @ResponseBody String sendDirect(String message) throws Exception {
		User user = new User(UUID.randomUUID().toString(), message, "123456", "sendDirect");
		template.convertAndSend("CalonDirectExchange", "CalonDirectRouting", user);
		return "OK,sendDirect:" + message;
	}
}

啓動mq-rabbit-provider項目,在瀏覽器輸入:

http://localhost:8080/sendDirect?message=123

再去RabbitMQ的web管理後臺查看,你會發現在Queue裏找到剛剛添加的那個隊列,後面的數字就是消息數量有變化,說明消息已經存儲進去了:
在這裏插入圖片描述

把mq-rabbit-provider項目裏的User類和DirectRabbitConfig類複製到mq-rabbit-consumer項目,User類用於讀取消息時接收消息對象,DirectRabbitConfig可以不復制,但是如果RabbitMQ裏還沒有被監聽的隊列時會報錯,複製過來是爲了讓RabbitMQ裏還沒有被監聽的隊列時自動創建該隊列,防止報錯。
創建隊列監聽類DirectReceiver.java,代碼如下:

package mq.rabbit.receiver;
 
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
 
import mq.rabbit.entity.User;
 
@Component
@RabbitListener(queues = "CalonDirectQueue")//CalonDirectQueue爲隊列名稱
public class DirectReceiver {
	
	@RabbitHandler
    public void process(User user) {
        System.out.println("DirectReceiver消費者收到消息  : " + user.getId()+","+user.getUsername()+","+user.getPassword()+","+user.getType());
    }
 
}

啓動mq-rabbit-consumer項目,就會收到之前發送到CalonDirectQueue隊列的消息了,繼續調用上面的請求/sendDirect,消息消費者會繼續收到消息。

Topic Exchange

在mq-rabbit-provider項目建一個配置類TopicRabbitConfig.java,配置交換機、隊列、BindingKey的綁定關係,代碼如下:

package mq.rabbit.config;
 
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;
 
/**
 * Topic Exchange類型交換機
 * @author calon
 *
 */
@Configuration
public class TopicRabbitConfig {
 
    public final static String first = "topic.first";
    public final static String second = "topic.second";
 
    @Bean
    public Queue firstQueue() {
        return new Queue(TopicRabbitConfig.first);
    }
 
    @Bean
    public Queue secondQueue() {
        return new Queue(TopicRabbitConfig.second);
    }
 
    @Bean
    TopicExchange exchange() {
        return new TopicExchange("topicExchange");
    }
 
    //綁定topic.first隊列到routingKey爲topic.first,只有topic.first的routingKey消息才發送到此隊列
    @Bean
    Binding bindingExchangeMessage() {
        return BindingBuilder.bind(firstQueue()).to(exchange()).with(first);
    }
 
    //綁定topic.second隊列到topic.#,凡是topic.開頭的routingKey消息都發送到此隊列
    @Bean
    Binding bindingExchangeMessage2() {
        return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");
    }
    
}

Topic Exchange類型的交換機是基於模糊匹配規則,所以這裏創建兩個Queue,分別綁定到兩個BindingKey:topic.first和topic.#,用來測試消息進到哪個隊列裏。

在SendController類裏添加兩個request,代碼如下:

@Controller
public class SendController {
 
	@Autowired
	private RabbitTemplate template;
	
	@GetMapping("/sendTopicFirst")
	private @ResponseBody String sendTopicFirst(String message) {
		User user = new User(UUID.randomUUID().toString(), message, "123456", "sendTopicFirst");
		template.convertAndSend("topicExchange", "topic.first", user);
		return "OK,sendTopicFirst:" + message;
	}
	
	@GetMapping("/sendTopicSecond")
	private @ResponseBody String sendTopicSecond(String message) {
		User user = new User(UUID.randomUUID().toString(), message, "123456", "sendTopicSecond");
		template.convertAndSend("topicExchange", "topic.second", user);
		return "OK,sendTopicSecond:" + message;
	}
}

當我們調用/sendTopicFirst請求時,交換機爲topicExchange,routingKey爲topic.first,按照上面bindingKey的配置,可以匹配到topic.first和topic.#規則,對應的隊列是topic.first和topic.second,所以一條消息進到兩個隊列裏。

當調用/sendTopicSecond請求時,交換機爲topicExchange,routingKey爲topic.second,匹配到topic.#規則,對應的隊列是topic.second,所以消息進到topic.second隊列裏,除了#匹配規則,大家可以自行試試星號(*)這個匹配規則,*符號是匹配一個單詞的。

把mq-rabbit-provider項目裏的TopicRabbitConfig類複製到mq-rabbit-consumer項目,分別創建TopicFirstReceiver和TopicSecondReceiver消息監聽類,代碼如下:

package mq.rabbit.receiver;
 
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import mq.rabbit.entity.User;
 
@Component
@RabbitListener(queues = "topic.first")
public class TopicFirstReceiver {
	
	@RabbitHandler
    public void process(User user) {
		System.out.println("TopicFirstReceiver消費者收到消息  : " + user.getId()+","+user.getUsername()+","+user.getPassword()+","+user.getType());
    }
}


package mq.rabbit.receiver;
 
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import mq.rabbit.entity.User;
 
@Component
@RabbitListener(queues = "topic.second")
public class TopicSecondReceiver {
	
	@RabbitHandler
    public void process(User user) {
		System.out.println("TopicSecondReceiver消費者收到消息  : " + user.getId()+","+user.getUsername()+","+user.getPassword()+","+user.getType());
    }
 
}

啓動mq-rabbit-consumer項目,會發現分別接收到各自監聽的隊列的消息。

Fanout Exchang

在mq-rabbit-provider項目建一個配置類FanoutRabbitConfig.java,配置交換機、隊列的綁定關係,代碼如下:

package mq.rabbit.config;
 
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;
 
@Configuration
public class FanoutRabbitConfig {
	
	@Bean
    public Queue AMessage() {
        return new Queue("fanout.A");
    }
 
    @Bean
    public Queue BMessage() {
        return new Queue("fanout.B");
    }
 
    @Bean
    public Queue CMessage() {
        return new Queue("fanout.C");
    }
 
    @Bean
    FanoutExchange fanoutExchange() {
        return new FanoutExchange("fanoutExchange");
    }
 
    @Bean
    Binding bindingExchangeA() {
        return BindingBuilder.bind(AMessage()).to(fanoutExchange());
    }
 
    @Bean
    Binding bindingExchangeB() {
        return BindingBuilder.bind(BMessage()).to(fanoutExchange());
    }
 
    @Bean
    Binding bindingExchangeC() {
        return BindingBuilder.bind(CMessage()).to(fanoutExchange());
    }
}

這裏創建三個隊列fanout.A、fanout.B、fanout.C,都綁定到FanoutExchange交換機fanoutExchange上。

在SendController類添加一個請求/sendFanout,代碼如下:

package mq.rabbit.controller;
 
import java.util.UUID;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.databind.ObjectMapper;
import mq.rabbit.entity.User;
 
@Controller
public class SendController {
 
	@Autowired
	private RabbitTemplate template;
	
	@GetMapping("/sendFanout")
	private @ResponseBody String sendFanout(String message) {
		User user = new User(UUID.randomUUID().toString(), message, "123456", "sendFanout");
		template.convertAndSend("fanoutExchange", null, user);
		return "OK,sendFanout:" + message;
	}
	
}


當調用/sendFanout請求時,在RabbitMQ的web管理界面看到三個隊列fanout.A、fanout.B、fanout.C都有一條消息,在Fanout交換機裏,如果有設置BindingKey,Fanout交換機會忽略已設置的BindingKey,把消息發送到綁定該交換機的所有隊列裏。

把mq-rabbit-provider項目裏的FanoutRabbitConfig類複製到mq-rabbit-consumer項目,分別創建FanoutReceiverA、FanoutReceiverB和FanoutReceiverC類,代碼如下:

package mq.rabbit.receiver;
 
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import mq.rabbit.entity.User;
 
@Component
@RabbitListener(queues = "fanout.A")
public class FanoutReceiverA {
 
    @RabbitHandler
    public void process(User user) {
    	System.out.println("FanoutReceiverA消費者收到消息  : " + user.getId()+","+user.getUsername()+","+user.getPassword()+","+user.getType());
    }
 
}

package mq.rabbit.receiver;
 
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import mq.rabbit.entity.User;
 
@Component
@RabbitListener(queues = "fanout.B")
public class FanoutReceiverB {
 
    @RabbitHandler
    public void process(User user) {
    	System.out.println("FanoutReceiverB消費者收到消息  : " + user.getId()+","+user.getUsername()+","+user.getPassword()+","+user.getType());
    }
 
}

package mq.rabbit.receiver;
 
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import mq.rabbit.entity.User;
 
@Component
@RabbitListener(queues = "fanout.C")
public class FanoutReceiverC {
 
    @RabbitHandler
    public void process(User user) {
    	System.out.println("FanoutReceiverC消費者收到消息  : " + user.getId()+","+user.getUsername()+","+user.getPassword()+","+user.getType());
    }
 
}

上面也可以在一個類裏寫3個方法來進行對隊列的監聽,不同的地方在於把@RabbitListener移到方法上即可。

啓動mq-rabbit-consumer,即可收到隊列的消息。

RabbitMQ消息的確認機制

在使用RabbitMQ的時候,我們可以通過消息持久化操作來解決因爲服務器的異常奔潰導致的消息丟失,除此之外我們還會遇到一個問題,當消息的生產者在將消息發送出去之後,消息到底有沒有正確到達服務器?如果不進行特殊配置的話,默認情況下發布消息是不會返回任何信息給生產者的,也就是生產者是不知道消息有沒有正確到達消息服務器,同理,消息消費者在接收消息後,如果在執行業務邏輯過程出現異常崩潰等情況,會導致消息丟失,所以我們需要對消息的發送和消費進行確認,確保消息能夠被正確的存儲和消費。RabbitMQ爲我們提供了兩種方式:1、事務機制;2、確認機制。下面介紹消息確認機制。

生產者消息確認機制:

先把例子跑起來,下面再做詳細介紹。在mq-rabbit-provider項目的application.properties文件添加以下屬性:

#確認消息已發送到交換機(Exchange)
spring.rabbitmq.publisher-confirms=true
#確認消息已發送到隊列(Queue)
spring.rabbitmq.publisher-returns=true

在mq-rabbit-provider項目創建配置類RabbitConfig.java,代碼如下:

package mq.rabbit.config;
 
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class RabbitConfig {
	
	@Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        rabbitTemplate.setMandatory(true);//必須設置爲true,才能讓下面的ReturnCallback函數生效
 
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("=======ConfirmCallback=========");
                System.out.println("correlationData = " + correlationData);
                System.out.println("ack = " + ack);
                System.out.println("cause = " + cause);
                System.out.println("=======ConfirmCallback=========");
            }
        });
 
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("--------------ReturnCallback----------------");
                System.out.println("message = " + message);
                System.out.println("replyCode = " + replyCode);
                System.out.println("replyText = " + replyText);
                System.out.println("exchange = " + exchange);
                System.out.println("routingKey = " + routingKey);
                System.out.println("--------------ReturnCallback----------------");
            }
        });
 
        return rabbitTemplate;
    }
 
}

RabbitMQ生產者是依賴兩個回調函數來實現確認的,分別是ConfirmCallback和ConfirmCallback,如上面的代碼。按以下4種情況進行回調:

1、消息找不到交換機(Exchange)時回調ConfirmCallback,返回ack=false,代碼如下:

=======ConfirmCallback=========
correlationData = null
ack = false
cause = channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'CalonDirectExchange1' in vhost 'calonHost', class-id=60, method-id=40)
=======ConfirmCallback=========
2018-08-30 09:59:37.892 ERROR 55704 --- [0.211.55.3:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'CalonDirectExchange1' in vhost 'calonHost', class-id=60, method-id=40)

2、消息找到交換機(Exchange)但找不到隊列(Queue)時回調ConfirmCallback和ReturnCallback,返回ack=true,replyCode = 312,replyText = NO_ROUTE,代碼如下:

--------------ReturnCallback----------------
message = (Body:'[B@bf8af5b(byte[179])' MessageProperties [headers={}, timestamp=null, messageId=null, userId=null, receivedUserId=null, appId=null, clusterId=null, type=null, correlationId=null, correlationIdString=null, replyTo=null, contentType=application/x-java-serialized-object, contentEncoding=null, contentLength=0, deliveryMode=null, receivedDeliveryMode=PERSISTENT, expiration=null, priority=0, redelivered=null, receivedExchange=null, receivedRoutingKey=null, receivedDelay=null, deliveryTag=0, messageCount=null, consumerTag=null, consumerQueue=null])
replyCode = 312
replyText = NO_ROUTE
exchange = CalonDirectExchange
routingKey = CalonDirectRouting1
--------------ReturnCallback----------------
=======ConfirmCallback=========
correlationData = null
ack = true
cause = null
=======ConfirmCallback=========

3、消息既找不到交換機(Exchange)又找不到隊列(Queue)時回調ConfirmCallback,返回ack=false,代碼如下:

=======ConfirmCallback=========
correlationData = null
ack = false
cause = channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'CalonDirectExchange1' in vhost 'calonHost', class-id=60, method-id=40)
=======ConfirmCallback=========
2018-08-30 10:03:22.204 ERROR 55704 --- [0.211.55.3:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'CalonDirectExchange1' in vhost 'calonHost', class-id=60, method-id=40)

4、消息成功發送回調ConfirmCallback,返回ack=true,代碼如下:

=======ConfirmCallback=========
correlationData = null
ack = true
cause = null
=======ConfirmCallback=========

消費者消息確認機制:

在mq-rabbit-consumer項目的DirectRabbitConfig配置類進行消息消費確認機制的配置,代碼如下:

package mq.rabbit.config;
 
import org.springframework.amqp.core.AcknowledgeMode;
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.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import mq.rabbit.receiver.DirectAckReceiver;
 
@Configuration
public class DirectRabbitConfig {
	
	@Bean
    public Queue CalonDirectQueue() {
        return new Queue("CalonDirectQueue",true);
    }
	
	@Bean
    DirectExchange CalonDirectExchange() {
        return new DirectExchange("CalonDirectExchange");
    }
	
	@Bean
    Binding bindingDirect() {
        return BindingBuilder.bind(CalonDirectQueue()).to(CalonDirectExchange()).with("CalonDirectRouting");
    }
 
	@Autowired
	private CachingConnectionFactory connectionFactory;
	@Autowired
	private DirectAckReceiver directAckReceiver;//消息接收處理類
 
	@Bean
	public SimpleMessageListenerContainer simpleMessageListenerContainer() {
		SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
		container.setConcurrentConsumers(1);
		container.setMaxConcurrentConsumers(1);
		container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默認是自動確認,這裏改爲手動確認消息
 
		container.setQueues(CalonDirectQueue());
		container.setMessageListener(directAckReceiver);
		return container;
	}
 
}

在mq-rabbit-consumer項目新建消息監聽類DirectAckReceiver.java,用於處理消息的確認操作,代碼如下:

package mq.rabbit.receiver;
 
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rabbitmq.client.Channel;
import mq.rabbit.entity.User;
 
@Component
public class DirectAckReceiver implements ChannelAwareMessageListener {
 
	@Autowired
	private ObjectMapper objectMapper;
 
	@Override
	public void onMessage(Message message, Channel channel) throws Exception {
		long deliveryTag = message.getMessageProperties().getDeliveryTag();
		try {
			byte[] body = message.getBody();
			User user = objectMapper.readValue(body, User.class);
			System.out.println("DirectAckReceiver消費者收到消息  : " + user.getId()+","+user.getUsername()+","+user.getPassword()+","+user.getType());
			channel.basicAck(deliveryTag, true);
//			channel.basicReject(deliveryTag, true);//爲true會重新放回隊列
		} catch (Exception e) {
			channel.basicReject(deliveryTag, false);
			e.printStackTrace();
		}
	}
 
}

消息接收確認模式有以下3種:

  • AcknowledgeMode.NONE:不確認
  • AcknowledgeMode.AUTO:自動確認
  • AcknowledgeMode.MANUAL:手動確認

默認情況下是自動確認,如果消費端消費邏輯拋出異常,也就是消費端沒有處理成功這條消息,那麼就相當於丟失了消息,在實際應用中,我們希望每條消息都能夠被正確消費而不是出現丟失的情況,上面代碼是開啓手動確認模式,下面看看手動確認都有哪幾種方式:

  • 成功確認:void basicAck(long deliveryTag, boolean multiple) throws IOException;
    deliveryTag:該消息的index
    multiple:是否批量. true:將一次性ack所有小於deliveryTag的消息。
    消費者成功處理後,調用channel.basicAck(message.getMessageProperties().getDeliveryTag(), false)方法對消息進行確認。

  • 失敗確認:void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;
    deliveryTag:該消息的index。
    multiple:是否批量. true:將一次性拒絕所有小於deliveryTag的消息。
    requeue:是否重新入隊列。

  • 拒絕確認:void basicReject(long deliveryTag, boolean requeue) throws IOException;
    deliveryTag:該消息的index。
    requeue:被拒絕的是否重新入隊列。
    channel.basicNack 與 channel.basicReject 的區別在於basicNack可以批量拒絕多條消息,而basicReject一次只能拒絕一條消息。

這裏要注意一點的是,無論如何,必須對消息進行確認操作,如果不調用相關函數進行確認,則RabbitMQ會認爲該程序處理能力弱,不會再發送消息到該監聽程序。


代碼參考-mq-rabbit-provider.git

代碼參考-mq-rabbit-consumer.git


全文參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章