1.ack和限流
ack也就是消息確認簽收,分爲自動簽收和手動簽收。之前的交換機demo中:channel.basicConsume(queueName,true, consumer); 第二個參數就是自動簽收,如果我們要手動簽收則需要改成false,再去消息處理中手動簽收即可
當我們消息隊列中已經積壓了大量消息的時候。這個時候消費者才啓動,,如果是自動簽收的話,就會導致大量消息湧入,可能回到服務剛啓動就宕機。這個時候就可以限制消息數量,使用手動簽收。處理完這一批,再處理下一批。
使用手動簽收,我們還可以在拿到消息,進行不同的業務處理,比如如果消息信息有問題,那就不簽收,移除當前隊列,或者放到其他地方去處理之類的
RabbitMQUtils類的代碼在上一節中:RabbitMQ(1)---基本概念及簡單demo
ack:手動簽收消息:
package com.nijunyang.rabbitmq.ack; import com.nijunyang.rabbitmq.util.RabbitMQUtils; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.UUID; /** * Description: * Created by nijunyang on 2020/6/7 13:07 */ public class AckProducer { public static void main(String[] args) throws Exception { Connection connection = RabbitMQUtils.getConnection(); Channel channel = connection.createChannel(); String message = "hello rabbitMQ." + new Random().nextInt(100); String exchangeName = "ack.exchange"; String routingKey = "ack.key"; Map<String,Object> heads = new HashMap<>(); heads.put("userName", "zhangsan"); AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder() .deliveryMode(2)//消息持久化 .contentEncoding("UTF-8") .correlationId(UUID.randomUUID().toString()) .headers(heads)//存放頭信息 .build(); channel.basicPublish(exchangeName, routingKey, basicProperties, message.getBytes("utf-8")); channel.basicPublish(exchangeName, routingKey, null, message.getBytes("utf-8")); RabbitMQUtils.close(channel, connection); } }
package com.nijunyang.rabbitmq.ack; import com.nijunyang.rabbitmq.util.RabbitMQUtils; import com.rabbitmq.client.*; import org.springframework.util.StringUtils; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * Description: * Created by nijunyang on 2020/6/7 13:07 */ public class AckConsumer { public static void main(String[] args) throws IOException, TimeoutException { Connection connection = RabbitMQUtils.getConnection(); Channel channel = connection.createChannel(); String exchangeName = "ack.exchange"; String exchangeType = "direct"; String routingKey = "ack.key"; String queueName = "ack.queue"; channel.exchangeDeclare(exchangeName, exchangeType); channel.queueDeclare(queueName,true,false,false,null); channel.queueBind(queueName, exchangeName, routingKey); Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { try { Object userName = properties.getHeaders().get("userName"); if (!StringUtils.isEmpty(userName)) { //用發送時候放的 頭信息模擬業務問題 String message = new String(body, "UTF-8"); System.out.println(message); //手動簽收消息 channel.basicAck(envelope.getDeliveryTag(),false); } else { throw new RuntimeException(); } }catch (Exception e) { //requeue參數 true 重回隊列,,false不重回隊列, 或者做其他處理 channel.basicNack(envelope.getDeliveryTag(),false,false); } } }; //autoAck參數 true:開啓自動簽收,false:關閉自動簽收功能 channel.basicConsume(queueName,false, consumer); } }
限流的話只需要多一個限制:channel.basicQos(0,5,false); 每次只會處理5條消息,簽收完了,在處理後面的
package com.nijunyang.rabbitmq.limit; import com.nijunyang.rabbitmq.util.RabbitMQUtils; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.UUID; /** * Description: * Created by nijunyang on 2020/6/7 13:07 */ public class LimitProducer { public static void main(String[] args) throws Exception { Connection connection = RabbitMQUtils.getConnection(); Channel channel = connection.createChannel(); String exchangeName = "limit.exchange"; String routingKey = "limit.key"; for (int i = 0; i < 20; i++) { String message = "limit rabbitMQ." + i; channel.basicPublish(exchangeName, routingKey, null, message.getBytes("utf-8")); } RabbitMQUtils.close(channel, connection); } }
package com.nijunyang.rabbitmq.limit; import com.nijunyang.rabbitmq.util.RabbitMQUtils; import com.rabbitmq.client.*; import org.springframework.util.StringUtils; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * Description: * Created by nijunyang on 2020/6/7 13:07 */ public class LimitConsumer { public static void main(String[] args) throws IOException, TimeoutException { Connection connection = RabbitMQUtils.getConnection(); Channel channel = connection.createChannel(); String exchangeName = "limit.exchange"; String exchangeType = "direct"; String routingKey = "limit.key"; String queueName = "limit.queue"; channel.exchangeDeclare(exchangeName, exchangeType); channel.queueDeclare(queueName,true,false,false,null); channel.queueBind(queueName, exchangeName, routingKey); /** * 限流設置: * prefetchSize:每條消息大小的設置 * prefetchCount:標識每次推送多少條消息 * global:false標識channel級別的 true:標識消費的級別的 */ channel.basicQos(0,5,false); Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); System.out.println(message); //手動簽收消息, 否則就會一直阻塞了 channel.basicAck(envelope.getDeliveryTag(), false); } }; //autoAck參數 true:開啓自動簽收,false:關閉自動簽收功能 channel.basicConsume(queueName,false, consumer); } }
2.消息投遞確認,開啓這個模式之後 消息投遞了之後 不能關閉連接,因爲監聽是綁定在channel上面的
開啓消息投遞確認模式,,在消息發送者上面綁定一個監聽,消息投遞成功或者失敗回調對應方法。
package com.nijunyang.rabbitmq.confirm; import com.nijunyang.rabbitmq.util.RabbitMQUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.ConfirmListener; import com.rabbitmq.client.Connection; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * Description: * Created by nijunyang on 2020/6/7 19:53 */ public class ConfirmProducer { public static void main(String[] args) throws Exception { Connection connection = RabbitMQUtils.getConnection(); Channel channel = connection.createChannel(); //設置消息投遞模式(確認模式) channel.confirmSelect(); /** * 消息確認監聽綁定 */ channel.addConfirmListener(new ConfirmListener() { @Override public void handleAck(long deliveryTag, boolean multiple) throws IOException { System.out.println("消息投遞成功"); } @Override public void handleNack(long deliveryTag, boolean multiple) throws IOException { System.out.println("消息投遞失敗"); } }); String exchangeName = "confirm.exchange"; String routingKey = "confirm.key"; for (int i = 0; i < 20; i++) { String message = "limit rabbitMQ." + i; channel.basicPublish(exchangeName, routingKey, null, message.getBytes("utf-8")); } //設置了消息投遞確認就不能關閉channel和連接了 // RabbitMQUtils.close(channel, connection); } }
3.不可達消息處理:有些消息發送之後,由於設置的原因,不能正常的路由到隊列上面。
和消息投遞確認差不多,只不過是在生產者的channel上面綁定一個ReturnListener(channel.addReturnListener(new RetrunListener())),然後投遞消息的時候使用這個方法channel.basicPublish(exchangeName,routingKey,true,null, message.getBytes()),相比之前的投遞方式多了一個布爾類型的mandatory參數。如果true那麼就會調用的綁定的ReturnListener,實現的方法,如果是false那麼就會直接刪除這個消息。
4.死信隊列。專門用來接收沒有消費的消息的隊列。消息發送到正常隊列上面但是沒有被消費,就會被轉發到死信隊列上面。所以說死信隊列是和一個正常隊列綁定的。消息變成死信的幾種情況:1.消息被拒絕(basicNack basicReject)並且重回隊裏設置的false,2.消息設置了過期時間,時間到了也沒有被消費,3.隊列已經達到最大長度,後面進來的消息直接轉到死信隊列。死信隊列也是一個正常的交換機和隊列。
package com.nijunyang.rabbitmq.deadqueue; import com.nijunyang.rabbitmq.util.RabbitMQUtils; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.ConfirmListener; import com.rabbitmq.client.Connection; import java.io.IOException; /** * Description: * Created by nijunyang on 2020/6/7 20:48 */ public class DeadQueueProducer { public static void main(String[] args) throws Exception { Connection connection = RabbitMQUtils.getConnection(); Channel channel = connection.createChannel(); String exchangeName = "normal.exchange"; String routingKey = "normal.key"; //設置消息的過期時間10s AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder() .deliveryMode(2) .expiration("10000") .build(); for (int i = 0; i < 20; i++) { String message = "dead rabbitMQ." + i; channel.basicPublish(exchangeName, routingKey, basicProperties, message.getBytes("utf-8")); } RabbitMQUtils.close(channel, connection); } }
package com.nijunyang.rabbitmq.deadqueue; import com.nijunyang.rabbitmq.util.RabbitMQUtils; import com.rabbitmq.client.*; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * Description: * Created by nijunyang on 2020/6/7 20:51 */ public class DeadQueueConsumer { public static void main(String[] args) throws Exception{ Connection connection = RabbitMQUtils.getConnection(); Channel channel = connection.createChannel(); //聲明正常的隊列 String normalExchangeName = "normal.exchange"; String exchangeType = "direct"; String normalQueueName = "normal.queue"; String routingKey = "normal.key"; channel.exchangeDeclare(normalExchangeName, exchangeType); //申明死信隊列 String deadExchangeName = "dead.exchange"; String deadExchangeType = "topic"; String deadQueueName = "dead.queue"; Map<String, Object> queueArgs = new HashMap<>(); //正常隊列上綁定死信隊列信息 queueArgs.put("x-dead-letter-exchange", deadExchangeName); queueArgs.put("x-max-length", 4); //隊列的最大長度 channel.queueDeclare(normalQueueName,true,false,false, queueArgs); channel.queueBind(normalQueueName, normalExchangeName, routingKey); //聲明死信隊列 channel.exchangeDeclare(deadExchangeName, deadExchangeType); channel.queueDeclare(deadQueueName,true,false,false,null); channel.queueBind(deadQueueName, deadExchangeName,"#"); Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); System.out.println(message); channel.basicNack(envelope.getDeliveryTag(),false,false); //拒籤 } }; channel.basicConsume(normalQueueName, false, consumer); } }
可有看到所有的消息最後都轉到了 死信隊列中去了。這個模式還可以用於延遲隊列。只需要設置正常隊列消息的過期時間,然後轉到死信隊列,,消費者監聽消費死信隊列,就可以實現延時隊列了。
5.單播消費模式,首先我們要明確一點消費者最終都是從隊列中拿到消息消費的,我們將多個消費者都綁定到同一個隊列上面去,這個時候,隊列消息只會被一個消費者消費,不會重複讓每個消費者都消費。
6.多播消費模式,和單播消費差不多,這個時候我們需要申明多個隊列綁定同一個交換機,這樣交換機的信息就會發到多個隊列上面,這樣通過同一個交換機將同一條消息發送到不同的隊列上面去了,也就實現了讓不同的消費者消費了同一條消息了。
package com.nijunyang.rabbitmq.deadqueue;
import com.nijunyang.rabbitmq.util.RabbitMQUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Description:
* Created by nijunyang on 2020/6/7 20:51
*/
public class DeadQueueConsumer {
public static void main(String[] args) throws Exception{
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
//聲明正常的隊列
String normalExchangeName = "normal.exchange";
String exchangeType = "direct";
String normalQueueName = "normal.queue";
String routingKey = "normal.key";
channel.exchangeDeclare(normalExchangeName, exchangeType);
//申明死信隊列
String deadExchangeName = "dead.exchange";
String deadExchangeType = "topic";
String deadQueueName = "dead.queue";
Map<String, Object> queueArgs = new HashMap<>();
//正常隊列上綁定死信隊列信息
queueArgs.put("x-dead-letter-exchange", deadExchangeName);
queueArgs.put("x-max-length", 4); //隊列的最大長度
channel.queueDeclare(normalQueueName,true,false,false, queueArgs);
channel.queueBind(normalQueueName, normalExchangeName, routingKey);
//聲明死信隊列
channel.exchangeDeclare(deadExchangeName, deadExchangeType);
channel.queueDeclare(deadQueueName,true,false,false,null);
channel.queueBind(deadQueueName, deadExchangeName,"#");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(message);
channel.basicNack(envelope.getDeliveryTag(),false,false); //拒籤
}
};
channel.basicConsume(normalQueueName, false, consumer);
}
}