消息確認機制
消息發送出去後,如果想確定消息發送的狀態信息,發送成功或者發送失敗,rabbitmq中有一套完整的檢測機制來檢測這類問題,我們只需要調用即可,具體的檢測機制我們後面再討論,暫且看一下怎麼用代碼先實現:
public class Send {
private static final String EXCHANGE_NAME = "test_exchange_confirm";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
//send a msg
String msg = "hello comfirm";
String routingKey = "confirm.save";
channel.basicPublish(EXCHANGE_NAME, routingKey, null, msg.getBytes());
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long l, boolean b) throws IOException {
System.out.println(" ack");
}
@Override
public void handleNack(long l, boolean b) throws IOException {
System.out.println("no ack");
}
});
System.out.println("send:"+msg);
// channel.close();
// connection.close(); 這裏需要註釋,否則就收不到ack
}
}
消息發送成功後,發送端發送成功後會收到ack,發送失敗後會收到 no ack
消費端寫法和之前的沒什麼區別,我們這次用topic的路由機制來實現一下:
public class ReceiveOne {
private static final String QUEUE_NAME = "receive1_queue";
private static final String EXCHANGE_NAME = "test_exchange_confirm";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicQos(1);
String routingKey = "confirm.#"; //看上一節的topic路由機制
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, routingKey);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [ConsumerOne is] Received '" + message + "'");
};
boolean autoAck = true; //開啓自動應答
channel.basicConsume(QUEUE_NAME, autoAck, deliverCallback, consumerTag -> { });
}
}
Return 機制
return Listener用於處理一些不可路由的消息(在某些情況下,如果我們發送消息的時候,當前的exchange或者routeKey路由不到的時候,這個時候如果我們需要監聽這種不可到達的消息,就要使用Return Listener)
消費端自定義監聽:
生產者:
public class Produces {
private static final String EXCHANGE_NAME = "test_undedine_consumer";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
String routingKey = "consumer.save";
String msg = "Hello undefine consumer";
for(int i = 0; i<5; i++){
channel.basicPublish(EXCHANGE_NAME,routingKey,true,null,msg.getBytes());
}
}
消費者:
public class Consumers {
private static final String QUEUE_NAME = "test_consumer_queue";
private static final String EXCHANGE_NAME = "test_undedine_consumer";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
String routineKey = "consumer.#";
channel.exchangeDeclare(EXCHANGE_NAME,"topic",true,false,null);
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,routineKey);
channel.basicConsume(QUEUE_NAME,true,new Myconsumer(channel));
}
}
自定義內容:
public class Myconsumer extends DefaultConsumer {
public Myconsumer(Channel channel) {
super(channel);
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("---------");
System.out.println(consumerTag);
}
}
消費端限流
生產者:(和上面的沒什麼區別)
public class Produces {
private static final String EXCHANGE_NAME = "test_limit_flu";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
String routingKey = "qos.save";
String msg = "limit fluency";
for(int i = 0; i<5; i++){
channel.basicPublish(EXCHANGE_NAME,routingKey,true,null,msg.getBytes());
}
}
}
消費者:
public class Consumers {
private static final String QUEUE_NAME = "test_qos";
private static final String EXCHANGE_NAME = "test_limit_flu";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
String routineKey = "qos.#";
channel.exchangeDeclare(EXCHANGE_NAME,"topic",true,false,null);
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,routineKey);
boolean autoAck = false;
channel.basicQos(0,1,false);
channel.basicConsume(QUEUE_NAME,autoAck,new Myconsumer(channel));
}
}
區別在自定義處理裏面:
public class Myconsumer extends DefaultConsumer {
private Channel channel;
public Myconsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("---------");
System.out.println(consumerTag);
//channel.basicAck(envelope.getDeliveryTag(),false);
//這裏之所以是false,前面我們設置的是 channel.basicQos(0,1,false); 表示只接受一個,如果改爲1以上,就要相應的改爲true
}
}
當我們把channel.basicAck(envelope.getDeliveryTag(),false);
這一段屏蔽時,我們在後臺看到的是:
總共是五條消息,只有一條能被消費,其他的四條需要等這條消費完後發ack響應才能,繼續向前傳遞。屏蔽的那段代碼就是手動響應ACK。
重回隊列
死信隊列
當一個消息在隊列中沒人去消費它,它就會被重新publish到另外一個Exchange,這個Exchange就是死信隊列。
死信隊列有哪幾種情況?
- 當消息被拒絕(basic.nack),並且拒絕重回隊列
- TTL過期了
- 隊列達到最大的長度
下面用代碼實現一下:
生產端:
public class Produces {
private static final String EXCHANGE_NAME = "test_dlx";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
String routingKey = "dlx.save";
String msg = "hello dlx";
channel.basicPublish(EXCHANGE_NAME,routingKey,true,null,msg.getBytes());
}
}
消費端:
public class Consumers {
private static final String QUEUE_NAME = "test_dlx.queue";
private static final String EXCHANGE_NAME = "test_dlx";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
String routineKey = "dlx.#";
channel.exchangeDeclare(EXCHANGE_NAME,"topic",true,false,null);
// 下面是配置死信隊列
Map<String,Object> arg = new HashMap<>();
arg.put("x-dead-letter-exchange","dlx.exchange");
channel.queueDeclare(QUEUE_NAME,true,false,false,arg); //這裏表明了路由失敗的話,轉發到死信隊列上
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,routineKey);
// 死信隊列的聲明
channel.exchangeDeclare("dlx.exchange","topic",true,false,null);
channel.queueDeclare("dlx.queue",true,false,false,null);
channel.queueBind("dlx.queue","dlx.exchange","#");
boolean autoAck = true;
channel.basicQos(0,1,false);
channel.basicConsume(QUEUE_NAME,autoAck,new Myconsumer(channel));
}
}
這裏需要說明的是我們定義了一個死信隊列 “dlx.queue”,和dlx.exchange綁定在一起,也就是說消息沒人消費就會路由到死信隊列中去。
public class Myconsumer extends DefaultConsumer {
private Channel channel;
public Myconsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// channel.basicAck(envelope.getDeliveryTag(),false);
//這裏之所以是false,前面我們設置的是 channel.basicQos(0,1,false); 表示只接受一個,如果改爲1以上,就要相應的改爲true
System.out.println(new String(body));
}
}
可以看到上面的test_dlx.queue後面有個DLX的標記,也就是說,它裏面的無人消費的消息都會直接轉到dlx.queue中。我們可以在dlx.queue中設置監聽來處理這些消息。