当前位置:   article > 正文

Rabbitmq的一些笔记_rabbitmq basicack

rabbitmq basicack

目录

一些基本概念: 

消息队列三大功能:

MQ的四大核心概念:

其他的一些:

 基础代码:

生产者部分:

消费者部分:

工作队列:

消息应答:

自动应答:

手动应答:

消息自动重新入队:

代码部分:

RabbitMQ持久化:

队列的持久化:

消息持久化:

发布确认: 

不公平发布:

预取值:

发布确认原理:

单个发布确认:

批量发布确认:

异步发布确认:

异步发布确认中未确认消息如何处理: 

交换机:

死信队列:

概念:

产生死信的来源/原因:

代码:

延迟队列:


一些基本概念: 

  • 消息队列三大功能:

  1. 流量消峰:超过极限之后,后续的访问人员需要等待;可以避免宕机,但是需要更多的时间;
  2. 应用解耦:可以使系统之间解耦,一个系统调用别的系统的时候不会因为被调用的系统故障而一起发生故障;这样在调用的时候,会通过队列去访问别的系统,那么需要调用的系统只需要将这样的一个请求交给了队列则就完成了他的操作,后续的出错不会影响它。
  3. 异步处理:可以使得模块之间调用的时候,调用的模块不再需要等待被调用的模块执行结束,而是被调用的模块执行结束以后由队列去通知调用的模块;
  • MQ的四大核心概念:

  1. 生产者;
  2. 交换机:交换机和队列是绑定的关系,一个交换机可以绑定多个队列;
  3. 队列:交换机和队列是MQ的重要组成部分;队列和消费者一一对应;
  4. 消费者;
  • 其他的一些:

  1. 生产者和交换机、队列(broker缓存代理)之间通过connection中的chennel(信道)连接。

 基础代码:

  • 生产者部分:

  1. /**
  2. * 生产者
  3. */
  4. public class Producer {
  5. public static final String Queue_Name="hello";
  6. public static void main(String[] args) throws IOException, TimeoutException {
  7. //创建连接工厂
  8. ConnectionFactory connectionFactory = new ConnectionFactory();
  9. //工厂ip 连接mq
  10. connectionFactory.setHost("172.20.10.6");
  11. connectionFactory.setUsername("ljw");
  12. connectionFactory.setPassword("666666");
  13. Connection connection = connectionFactory.newConnection();
  14. //连接需要通过信道发送消息
  15. Channel channel = connection.createChannel();
  16. //通过信道获取队列
  17. //1.队列名、队列中的消息是否需要持久化---默认存内存,持久化后存磁盘;
  18. //2.是否进行消息共享,是否可以被多个消费者共享,true:不共享,false:共享;
  19. //3.是否自动删除
  20. channel.queueDeclare(Queue_Name,false,false,false,null);
  21. String message="hello world";
  22. //1.交换机
  23. //2.路由的key
  24. channel.basicPublish("",Queue_Name,null,message.getBytes(StandardCharsets.UTF_8));
  25. System.out.println("消息发送完毕");
  26. }
  27. }
  • 消费者部分:

  1. /**
  2. * 消费者接收消息
  3. */
  4. public class Consumer {
  5. public static final String Queue_Name = "hello";
  6. //接收消息
  7. public static void main(String[] args) throws IOException, TimeoutException {
  8. //创建连接工厂
  9. ConnectionFactory connectionFactory = new ConnectionFactory();
  10. //工厂ip 连接mq
  11. connectionFactory.setHost("172.20.10.6");
  12. connectionFactory.setUsername("ljw");
  13. connectionFactory.setPassword("666666");
  14. Connection connection = connectionFactory.newConnection();
  15. //连接需要通过信道接受消息
  16. Channel channel = connection.createChannel();
  17. //声明 接收消息
  18. DeliverCallback deliverCallback = (var1, var2) -> {
  19. //将byte数组转化为String类型
  20. String message = new String(var2.getBody(), StandardCharsets.UTF_8);;
  21. System.out.println(message);
  22. };
  23. //声明 取消消息
  24. CancelCallback cancelCallback=(var1)->{
  25. System.out.println("消费被中断");
  26. };
  27. //1.消费哪个队列
  28. //2.消费成功以后是否自动应答
  29. //3.消费者收到消息的回调
  30. //4.消费者取消消费的回调
  31. channel.basicConsume(Queue_Name, true,deliverCallback, cancelCallback);
  32. }
  33. }

综上,可以将获取信道的过程封装到工具类里面:

  1. public class RabbitUtils {
  2. public Channel getChannel() throws IOException, TimeoutException {
  3. //创建连接工厂
  4. ConnectionFactory connectionFactory = new ConnectionFactory();
  5. //工厂ip 连接mq
  6. connectionFactory.setHost("172.20.10.6");
  7. connectionFactory.setUsername("ljw");
  8. connectionFactory.setPassword("666666");
  9. Connection connection = connectionFactory.newConnection();
  10. //连接需要通过信道接受消息
  11. Channel channel = connection.createChannel();
  12. return channel;
  13. }
  14. }


工作队列:

  • 概念:避免立即执行资源密集型任务,将这些任务封装成消息在后台执行,可以由多个线程一起处理。那么多个线程操作的时候需要保证一条消息只能被处理一次,各个线程轮询工作。
  • 测试的时候可以设置允许并行操作,模拟多个线程---同时跑两遍; 

  • 此时需要生产者发送大量消息:
    1. /**
    2. * 生产者
    3. */
    4. public class Producer {
    5. public static final String QUEUE_NAME="hello";
    6. public static void main(String[] args) throws IOException, TimeoutException {
    7. Channel channel = RabbitUtils.getChannel();
    8. //通过信道获取队列
    9. //1.队列名、队列中的消息是否需要持久化---默认存内存,持久化后存磁盘;
    10. //2.是否进行消息共享,是否可以被多个消费者共享,true:不共享,false:共享;
    11. //3.是否自动删除
    12. channel.queueDeclare(QUEUE_NAME,false,false,false,null);
    13. String message="hello world";
    14. //1.交换机
    15. //2.路由的key
    16. Scanner scanner = new Scanner(System.in);
    17. while (scanner.hasNext()){
    18. scanner=new Scanner(System.in);
    19. channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
    20. System.out.println("消息发送完毕");
    21. }
    22. }
    23. }

    运行会发现,消费者是轮询消费多条消息的;


消息应答:

  • 如果没有消息应答那么rabbitmq一旦向消费者传递了一条信息,就会立刻将该条消息标记为删除,那么如果一旦有一个消费者挂掉了,那么就会丢失消息;那么为了保证消息在发送过程中不丢失,就产生了消息应答机制,即:消费者在接收到消息并且处理消息之后,会告诉rabbitmq处理好了,此时rabbitmq可以将消息删除了
  • 自动应答:

  1. 消息发送以后立即被认为已经传送成功这种情况下,如果消息在接收到之前,消费者的channel关闭了,那么消息就会丢失;另一方面,这种情况没有对传递消息的数量进行限制,那么可能会导致消费者来不及处理消息,导致消息积压内存耗尽,最终使得消费者线程被操作系统杀死。所以这种应答方式仅适用于消费者可以高效并以某种速率处理这些消息的情况下。

  • 手动应答:

  1. Channel.basicAck(用于肯定确认):消费者已经接收到消息并且成功处理了消息,rabbitmq可以丢弃该条消息了。
  2. Channel.basicNack(用于否认确认)
  3. Channel.basicReject(用于否认确认):比Channel.basicNack少一个参数--是否批量处理,表示不处理该消息直接拒绝,rabbitmq可以丢弃该条消息了。
  4. 批量处理:如果此时channel中有多条未应答消息,批量处理为true的话,那么这多条消息都会收到消息应答,如果批量处理为false的话,那么只有最新的一条未应答消息收到消息应答。批量处理的话有可能导致后续消息处理失败,但是rabbitmq已经收到应答从而导致消息的丢失,所以不推荐使用批量处理。
  • 消息自动重新入队:

  1. 如果消费者由于某些原因失去连接(如channel关闭了),导致消息未发送ack确认,那么rabbitmq将会知道消息没有完全处理,就会将消息重新排队,此时如果有其他消费者可以处理,那么将会重新分发给其他消费者,这样就不会导致消息的丢失。
  • 代码部分:

  1. 生产者:
    1. public class Producer {
    2. //队列名称
    3. private static final String QUEUE_NAME = "ack_queue";
    4. public static void main(String[] args) throws IOException, TimeoutException {
    5. Channel channel = RabbitUtils.getChannel();
    6. //申明队列
    7. channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    8. Scanner scanner = new Scanner(System.in);
    9. String message = "hello world";
    10. while (scanner.hasNext()) {
    11. scanner = new Scanner(System.in);
    12. channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
    13. System.out.println("消息已发送");
    14. }
    15. }
    16. }
  2. 消费者:
    1. public class Consumer01 {
    2. //队列名称
    3. private static final String QUEUE_NAME = "ack_queue";
    4. public static void main(String[] args) throws IOException, TimeoutException {
    5. Channel channel = RabbitUtils.getChannel();
    6. System.out.println("C1等待接收消息处理时间较短");
    7. //这里写收到消息后如何消费
    8. DeliverCallback deliverCallback = (var1, var2) -> {
    9. try {
    10. Thread.sleep(10000L);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. String message = new String(var2.getBody(), StandardCharsets.UTF_8);
    15. System.out.println("接收到的消息是:"+message);
    16. //手动应答
    17. //1.消息的标记 tag -->envelope是属性
    18. //2.批量应答
    19. channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
    20. };
    21. //声明 取消消息
    22. CancelCallback cancelCallback=(var1)->{
    23. System.out.println("消费被中断");
    24. };
    25. //关闭自动应答,采用手动应答
    26. boolean autoAck=false;
    27. channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,cancelCallback);
    28. }
    29. }
    1. public class Consumer02 {
    2. //队列名称
    3. private static final String QUEUE_NAME = "ack_queue";
    4. public static void main(String[] args) throws IOException, TimeoutException {
    5. Channel channel = RabbitUtils.getChannel();
    6. System.out.println("C2等待接收消息处理时间较长");
    7. //这里写收到消息后如何消费
    8. DeliverCallback deliverCallback = (var1, var2) -> {
    9. try {
    10. Thread.sleep(300000L);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. String message = new String(var2.getBody(), StandardCharsets.UTF_8);
    15. System.out.println("接收到的消息是:"+message);
    16. //手动应答
    17. //1.消息的标记 tag -->envelope是属性
    18. //2.批量应答
    19. channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
    20. };
    21. //声明 取消消息
    22. CancelCallback cancelCallback=(var1)->{
    23. System.out.println("消费被中断");
    24. };
    25. //关闭自动应答,采用手动应答
    26. boolean autoAck=false;
    27. channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,cancelCallback);
    28. }
    29. }

    测试后发现,如果在消息处理的过程中某个消费者挂掉了,那么该条消息会重新入队并且很快分给其他消费者;并且如果开启手动应答以后,会在手动应答执行以后才将消息从队列里删除。


RabbitMQ持久化:

  • 以上是保证了传给消费者进行处理的过程中消息不丢失,那么如果是rabbitmq服务停掉以后,生产者发来消息,这个消息如何保证不丢失呢那么也就是将队列和消息标记为持久化
  • 队列的持久化

  1. 将队列申明中的durable参数改为true,那么也就是开启持久化;如果没有开启持久化,那么重启mq以后该队列就不存在了,但是持久化的仍旧存在。

  2. 特别注意:如果是已经创建了没有开启持久化的队列,那么需要将其删除重新创建持久化的队列,否则会报错;
    1. //申明队列
    2. //第二个参数即durable,true则为开启持久化
    3. channel.queueDeclare(QUEUE_NAME, true, false, false, null);

    (开启持久化的队列在feature一列会显示“D”) 

  • 消息持久化

  1. 是在生产者发布消息的时候开启持久化,也就是在props参数加上开启持久化属性(MessageProperties.PERSISTENT_TEXT_PLAIN)。

  2. 特别注意:将消息标记为持久化并不能完全保证不会丢失消息,这里会存在消息刚准备保存在磁盘的时候,但还没有储存完就宕机了,消息在缓存的一个间隔点,也就是没有真正的写进磁盘,如果需要更强的持久化策略,需要参考发布确认。
    1. //设置开启消息持久化(MessageProperties.PERSISTENT_TEXT_PLAIN)
    2. channel.basicPublish("",QUEUE_NAME,
    3. MessageProperties.PERSISTENT_TEXT_PLAIN,
    4. message.getBytes(StandardCharsets.UTF_8));

发布确认: 

  • 不公平发布:

  1. 轮询分发相当于是一种公平发布,在这种情况下,如果一个消费者处理消息特别快,一个消费者处理消息特别慢,那么处理消息快的消费者大部分时间是空闲的,而处理消息慢的消费者则是一直处于工作状态,所以这样的发布方式不是非常合理,那么为了避免这种情况,我们可以在每个消费者端设置参数channel.basicQos(int prefetchCount=1)(默认是0,也就是轮询分发),从而变为不公平发布,也就相当于能者多劳,也就是一个消费者只能同一时刻只能处理一个消息,要是目前的消息还没处理好,就不会分给他新的消息注意:此设置需要在手动应答的时候才生效

  • 预取值:

  1. 也就是指定每个消费者分到几条消息,分配合理可以提高效率;
    1. //consumer01
    2. int prefetchCount=3;
    3. channel.basicQos(prefetchCount);
    1. //consumer02
    2. int prefetchCount=3;
    3. channel.basicQos(prefetchCount);

  • 发布确认原理:

  1. 发布确认就是存在磁盘上完成以后,mq告知生产者
  2. 开启发布确认就可以保证持久化,真的存储在磁盘上(前提是开启了队列、消息的持久化)。
  3. 开启发布确认:需要在生产者的channel上调用channel.confirmSelect()
    1. //开启发布确认
    2. channel.confirmSelect();
  • 单个发布确认:

  1. 这是一种同步发布确认的方式,发布消息后只有被确认了才会发布下一条;如果指定时间内没有被确认,那么就会抛出异常;缺点:发布速度特别慢。
    1. //单个确认
    2. public static void publishMessageIndividually() throws IOException, TimeoutException, InterruptedException {
    3. Channel channel = RabbitUtils.getChannel();
    4. //通过uuid获取一个随机的队列名
    5. String queueName = UUID.randomUUID().toString();
    6. channel.queueDeclare(queueName, true, false, false, null);
    7. //开启发布确认
    8. channel.confirmSelect();
    9. //开始时间
    10. long begin = System.currentTimeMillis();
    11. //批量发消息
    12. for (int i=0;i<MESSAGE_COUNTS;i++){
    13. String message=i+"";
    14. channel.basicPublish("",queueName, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes(StandardCharsets.UTF_8));
    15. //单个消息就发布确认
    16. boolean flag = channel.waitForConfirms();
    17. if (flag){
    18. System.out.println("发布成功");
    19. }
    20. }
    21. //结束时间
    22. long end = System.currentTimeMillis();
    23. System.out.println("发布"+MESSAGE_COUNTS+"个单独发布确认消息用时:"+(end - begin)+"ms");
    24. }
  2. 单个确认发布耗时如图:

  • 批量发布确认:

  1. 发布一批消息然后一起确认,提高了吞吐量;缺点:如果发布出现问题,就不知道是哪个消息出现的问题。
    1. //批量确认
    2. public static void publishMessageBatch() throws IOException, TimeoutException, InterruptedException {
    3. Channel channel = RabbitUtils.getChannel();
    4. //通过uuid获取一个随机的队列名
    5. String queueName = UUID.randomUUID().toString();
    6. channel.queueDeclare(queueName, true, false, false, null);
    7. //开启发布确认
    8. channel.confirmSelect();
    9. //开始时间
    10. long begin = System.currentTimeMillis();
    11. //设置批量确认消息的数量
    12. int batchCount = 100;
    13. //批量发消息
    14. for (int i = 1; i <= MESSAGE_COUNTS; i++) {
    15. String message = i + "";
    16. channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
    17. //判断每到达100条的时候,批量确认一次
    18. if (i % batchCount == 0) {
    19. boolean flag = channel.waitForConfirms();
    20. if (flag) {
    21. System.out.println("发布成功");
    22. }
    23. }
    24. }
    25. //结束时间
    26. long end = System.currentTimeMillis();
    27. System.out.println("发布" + MESSAGE_COUNTS + "个批量发布确认消息用时:" + (end - begin) + "ms");
    28. }

    代码如图

  2. 批量确认发布耗时如图:

  • 异步发布确认:

  1. 这是由broker利用回调函数达到可靠性传递,不再由生产者进行确认,但是生产者和需要进行监听(channel.addConfirmListener(),两个参数的,这样可以监听成功和失败的),监听和发布是异步的。
    1. //异步发布确认
    2. public static void publishMessageAsync() throws IOException, TimeoutException, InterruptedException {
    3. Channel channel = RabbitUtils.getChannel();
    4. //通过uuid获取一个随机的队列名
    5. String queueName = UUID.randomUUID().toString();
    6. channel.queueDeclare(queueName, true, false, false, null);
    7. //开启发布确认
    8. channel.confirmSelect();
    9. //开始时间
    10. long begin = System.currentTimeMillis();
    11. //设置批量确认消息的数量
    12. int batchCount = 100;
    13. //消息确认成功 回调函数
    14. ConfirmCallback ackCallback=(var1,var3)->{
    15. System.out.println("已确认消息:"+var1);
    16. };
    17. //消息确认失败,回调函数
    18. ConfirmCallback nackCallback=(var1,var3)->{
    19. System.out.println("未确认消息:"+var1);
    20. };
    21. //准备消息监听器,发送的时候就开始监听--->异步的
    22. channel.addConfirmListener(ackCallback,nackCallback);
    23. //批量发消息
    24. for (int i = 1; i <= MESSAGE_COUNTS; i++) {
    25. String message = i + "";
    26. channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
    27. }
    28. //结束时间
    29. long end = System.currentTimeMillis();
    30. System.out.println("发布" + MESSAGE_COUNTS + "个异步发布确认消息用时:" + (end - begin) + "ms");
    31. }
  2. 批量确认发布耗时如图:
  • 异步发布确认中未确认消息如何处理: 

  1. 需要将未确认消息放到一个基于内存,能被发布线程访问的队列。比如用队列ConcurrentLinkedQueue在确认回调函数(confirmcallback)与发布线程之间进行消息传递。
  2. 需要先记录所有的消息,再将已经确认的删除,留下的就是未确认的。
    1. //异步发布确认
    2. public static void publishMessageAsync() throws IOException, TimeoutException, InterruptedException {
    3. Channel channel = RabbitUtils.getChannel();
    4. //通过uuid获取一个随机的队列名
    5. String queueName = UUID.randomUUID().toString();
    6. channel.queueDeclare(queueName, true, false, false, null);
    7. //开启发布确认
    8. channel.confirmSelect();
    9. /**线程安全有序的一个哈希表,适用于高并发的情况
    10. * 1.将消息和序号进行关联
    11. * 2.通过给到序号从而删除消息
    12. * 3.支持高并发
    13. */
    14. ConcurrentSkipListMap<Long,String> outstandingConfirms=new ConcurrentSkipListMap<>();
    15. //设置批量确认消息的数量
    16. int batchCount = 100;
    17. //消息确认成功 回调函数
    18. ConfirmCallback ackCallback = (var1, var3) -> {
    19. //批量确认
    20. if (var3){
    21. //2.删除掉已经确认的消息 剩下未确认的消息
    22. ConcurrentNavigableMap<Long, String> longStringConcurrentNavigableMap
    23. = outstandingConfirms.headMap(var1);
    24. longStringConcurrentNavigableMap.clear();
    25. }else {
    26. //单条确认
    27. outstandingConfirms.remove(var1);
    28. }
    29. System.out.println(outstandingConfirms);
    30. System.out.println("已确认消息:" + var1);
    31. };
    32. //消息确认失败,回调函数
    33. ConfirmCallback nackCallback = (var1, var3) -> {
    34. //3.可以在这里打印未确认的消息
    35. System.out.println("未确认消息:" + var1);
    36. };
    37. //准备消息监听器,发送的时候就开始监听--->异步的
    38. channel.addConfirmListener(ackCallback, nackCallback);
    39. //开始时间,因为是异步的,所以放在发送上面开始记录;
    40. long begin = System.currentTimeMillis();
    41. //批量发消息
    42. for (int i = 1; i <= MESSAGE_COUNTS; i++) {
    43. String message = i + "";
    44. //1.记录所有发布的消息
    45. outstandingConfirms.put(channel.getNextPublishSeqNo(),message);
    46. channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
    47. }
    48. //结束时间
    49. long end = System.currentTimeMillis();
    50. System.out.println("发布" + MESSAGE_COUNTS + "个异步发布确认消息用时:" + (end - begin) + "ms");
    51. }

  • 交换机:

  1. 交换机可以将生产者的消息发给多个队列中的消息再发给不同的消费者(但是同一个消息还是只能消费一次);
  2. 生产者只可以将消息发送到交换机;
  3. 临时队列:创建队列的时候不传declare的参数;
    channel.queueDeclare().getQueue();
  4. 交换机和队列通过路由键进行绑定

    1. public class Producer {
    2. private final static String EXCHANGE_NAME = "log";
    3. public static void main(String[] args) throws IOException, TimeoutException {
    4. Channel channel = RabbitUtils.getChannel();
    5. /**
    6. * 1.交换机名称
    7. * 2.交换机类型
    8. */
    9. channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
    10. Scanner scanner = new Scanner(System.in);
    11. while (scanner.hasNext()) {
    12. String message = scanner.next();
    13. channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
    14. System.out.println("生产者已成功发送消息:" + message);
    15. scanner = new Scanner(System.in);
    16. }
    17. }
    18. }
    1. public class Consumer01 {
    2. private final static String EXCHANGE_NAME = "log";
    3. public static void main(String[] args) throws IOException, TimeoutException {
    4. Channel channel = RabbitUtils.getChannel();
    5. /**
    6. * 1.交换机名称
    7. * 2.交换机类型
    8. */
    9. channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
    10. //声明一个临时队列
    11. String queue = channel.queueDeclare().getQueue();
    12. /**
    13. * 绑定交换机和队列
    14. * 1.队列名称
    15. * 2.交换机名称
    16. * 3.路由键(fanout型可以不需要路由键,因为所有绑定的都会收到)
    17. */
    18. channel.queueBind(queue, EXCHANGE_NAME, "");
    19. DeliverCallback deliverCallback = (var1, var2) -> {
    20. String message = new String(var2.getBody(), StandardCharsets.UTF_8);
    21. System.out.println(message);
    22. };
    23. CancelCallback cancelCallback = (var1) -> {
    24. System.out.println("消费被中断");
    25. };
    26. channel.basicConsume(queue, true, deliverCallback, cancelCallback);
    27. }
    28. }
    1. public class Consumer02 {
    2. private final static String EXCHANGE_NAME="log";
    3. public static void main(String[] args) throws IOException, TimeoutException {
    4. Channel channel = RabbitUtils.getChannel();
    5. /**
    6. * 1.交换机名称
    7. * 2.交换机类型
    8. */
    9. channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
    10. //声明一个临时队列
    11. String queue = channel.queueDeclare().getQueue();
    12. /**
    13. * 绑定交换机和队列
    14. * 1.队列名称
    15. * 2.交换机名称
    16. * 3.路由键(fanout型可以不需要路由键,因为所有绑定的都会收到)
    17. */
    18. channel.queueBind(queue,EXCHANGE_NAME,"");
    19. DeliverCallback deliverCallback = (var1, var2) -> {
    20. String message = new String(var2.getBody(), StandardCharsets.UTF_8);
    21. System.out.println(message);
    22. };
    23. CancelCallback cancelCallback = (var1) -> {
    24. System.out.println("消费被中断");
    25. };
    26. channel.basicConsume(queue,true,deliverCallback,cancelCallback);
    27. }
    28. }

    此时一条消息就可以被两个消费者所消费;


  • 死信队列:

  • 概念:

  1. 死信也就是无法被消费的信息,一般是指生产者将消息投递到了broker或者是queue中了,但是由于某些原因消费者无法消费这些消息。如果没有对这些消息进行处理,那么它们就会变成死信。

  • 产生死信的来源/原因:

  1. 消息TTL(存活时间)过期;

  2. 队列达到最大长度;

  3. 消息被拒绝(手动应答的是拒绝应答/否定应答);

  • 代码:

  1. 情况1:消息TTL(存活时间)过期:
    1. /**
    2. * 死信队列
    3. */
    4. public class Producer {
    5. private final static String NORMAL_EXCHANGE_NAME = "normal_exchange";
    6. public static void main(String[] args) throws IOException, TimeoutException {
    7. Channel channel = RabbitUtils.getChannel();
    8. //设置过期时间
    9. AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
    10. for (int i = 0; i < 11; i++) {
    11. String message = "info:" + i;
    12. channel.basicPublish(NORMAL_EXCHANGE_NAME, "zhangsan",properties,message.getBytes(StandardCharsets.UTF_8));
    13. System.out.println("生产者已成功发送消息:" + message);
    14. }
    15. }
    16. }
    1. //是在这个消费者中声明的交换机,所以应该先执行这个消费者
    2. public class Consumer01 {
    3. private final static String NORMAL_EXCHANGE_NAME = "normal_exchange";
    4. private final static String DEAD_EXCHANGE_NAME = "dead_exchange";
    5. private final static String NORMAL_QUEUE_NAME = "normal-queue";
    6. private final static String DEAD_QUEUE_NAME = "dead-queue";
    7. public static void main(String[] args) throws IOException, TimeoutException {
    8. Channel channel = RabbitUtils.getChannel();
    9. /**
    10. * 1.交换机名称
    11. * 2.交换机类型
    12. */
    13. channel.exchangeDeclare(NORMAL_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
    14. channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
    15. //声明队列
    16. //普通队列需要设arguments参数,这样才能转发给死信交换机
    17. HashMap<String, Object> arguments = new HashMap();
    18. //过期时间,也可以在生产者那里设置
    19. //arguments.put("x-message-ttl",10000);
    20. //正常队列设置对应的死信交换机
    21. arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
    22. arguments.put("x-dead-letter-routing-key", "lisi");
    23. //还要设置死信的路由键
    24. channel.queueDeclare(NORMAL_QUEUE_NAME, false, false, false, arguments);
    25. ///
    26. //死信队列
    27. channel.queueDeclare(DEAD_QUEUE_NAME, false, false, false, null);
    28. /**
    29. * 绑定交换机和队列
    30. * 1.队列名称
    31. * 2.交换机名称
    32. * 3.路由键
    33. */
    34. channel.queueBind(NORMAL_QUEUE_NAME, NORMAL_EXCHANGE_NAME, "zhangsan");
    35. channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, "lisi");
    36. DeliverCallback deliverCallback = (var1, var2) -> {
    37. String message = new String(var2.getBody(), StandardCharsets.UTF_8);
    38. System.out.println(message);
    39. };
    40. CancelCallback cancelCallback = (var1) -> {
    41. System.out.println("消费被中断");
    42. };
    43. channel.basicConsume(NORMAL_QUEUE_NAME, true, deliverCallback, cancelCallback);
    44. }
    45. }
    1. //这个消费者是用来消费死信队列中的消息的
    2. public class Consumer02 {
    3. private final static String DEAD_QUEUE_NAME = "dead-queue";
    4. public static void main(String[] args) throws IOException, TimeoutException {
    5. Channel channel = RabbitUtils.getChannel();
    6. DeliverCallback deliverCallback = (var1, var2) -> {
    7. String message = new String(var2.getBody(), StandardCharsets.UTF_8);
    8. System.out.println(message);
    9. };
    10. CancelCallback cancelCallback = (var1) -> {
    11. System.out.println("消费被中断");
    12. };
    13. channel.basicConsume(DEAD_QUEUE_NAME, true, deliverCallback, cancelCallback);
    14. }
    15. }
  2. 情况2:队列达到最大长度: 
    1. //设置队伍最大长度
    2. //在队列声明的arguments参数中加入
    3. //这是指消息堆积了6条的情况,如果消息处理的够快就不会堆积,也就不会分给死信
    4. arguments.put("x-max-length",6);

  3. 情况3:消息被拒绝:

    1. //手动应答,拒绝符合条件的消息
    2. DeliverCallback deliverCallback = (var1, var2) -> {
    3. String message = new String(var2.getBody(), StandardCharsets.UTF_8);
    4. System.out.println(message);
    5. if (message.equals("info:5")){
    6. System.out.println("拒绝此消息:"+message);
    7. channel.basicReject(var2.getEnvelope().getDeliveryTag(),false);
    8. }else {
    9. System.out.println("接受此消息"+message);
    10. channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
    11. }
    12. };
    13. CancelCallback cancelCallback = (var1) -> {
    14. System.out.println("消费被中断");
    15. };
    16. channel.basicConsume(NORMAL_QUEUE_NAME, false, deliverCallback, cancelCallback);
    17. }


  • 延迟队列:

  • 概念:

  1. 队列中的消息需要到了指定时间以后或者之前取出和处理,延迟队列也就是存放这种消息的队列。
  2. 需要整合springboot;
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/127036
推荐阅读
相关标签
  

闽ICP备14008679号