RabbitMQ代码编写(二) - 自学笔记

发布确认

1. 目的

在生产者发送消息时,整个消息发送过程是:生产者发送消息到队列。以下情况下会导致消息丢失:

1) RabbitMQ宕机——【解决方法】队列持久化[磁盘上才能达到持久化目标]

2)发送消息到队列时,消费者接收消息后处理失败或者消费者宕机,由于消费者已经接收消息,所以队列中默认会删除消息,防止消息重复消费。但是由于消费者宕机,没有消费成功,造成消息确实——【解决方法】设置要求队列中的消息必须持久化。

3) 传送到队列,还未来得及保存到磁盘上,就宕机。——【解决方法】发布确认

2. 单个发布确认

同步确认发布方式,发布一个消息后只有它被确认发布,后续消息才能继续发布。发布速度慢。

关键代码:channel.confirmSelect()// 开启发布确认方法

boolean flag = channel.waitForConfirms();//单个消息就马上进行发布确认

//单个确认
    public static void publishMessageIndividually()throws Exception{
        Channel channel = RabbitMqUtils.getChannel();

        //队列的声明
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);

        //开启发布确认
        channel.confirmSelect();
        //开始时间
        long begin = System.currentTimeMillis();

        //批量发消息
        for(int i =0;i<MESSAGE_COUNT;i++){
            String message = i+"";
            channel.basicPublish("",queueName,null,message.getBytes());
            //单个消息就马上进行发布确认
            boolean flag  = channel.waitForConfirms();
            if(flag){
                System.out.println("消息发送成功");
            }
        }
        //结束时间
        long end = System.currentTimeMillis();
        System.out.println("发布"+MESSAGE_COUNT+"个单独确认消息,耗时"+(end-begin)+"ms");

    }

3. 批量发布确认

当发生故障导致发布出现问题时,不知道哪个消息出问题了,但是速度比单个快。

//批量发布确认
    public static void publishMessageBatch()throws Exception{
        Channel channel = RabbitMqUtils.getChannel();

        //队列的声明
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);

        //开启发布确认
        channel.confirmSelect();
        //开始时间
        long begin = System.currentTimeMillis();

        //批量确认消息大小
        int batchSize = 100;

        //批量发消息 批量确认消息
        for(int i =0;i<MESSAGE_COUNT;i++){
            String message = i+"";
            channel.basicPublish("",queueName,null,message.getBytes());

            //判断达到100条消息的时候,批量确认一次
            if(i%batchSize ==0){
                //单个消息就马上进行发布确认
//                channel.waitForConfirms();
                boolean flag  = channel.waitForConfirms();
                if(flag){
                    System.out.println("消息发送成功");
                }
            }

        }

        //结束时间
        long end = System.currentTimeMillis();
        System.out.println("发布"+MESSAGE_COUNT+"个批量确认消息,耗时"+(end-begin)+"ms");

    }

4. 异步确认发布

性能高,效率好,并且知道哪个消息丢失。

原理:

利用函数回调达到消息可靠性,消息发送后,队列存储为map方式,key代表消息序号,value代表消息内容,如果为确认消息调用回调nackCallback方法,确认收到回调调用ackCallback方法。

channel.addConfirmListener(ackCallback,nackCallback);//异步通知

 //异步发布确认
    public static void publishMessageAsync()throws Exception{
        Channel channel = RabbitMqUtils.getChannel();

        //队列的声明
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);

        //开启发布确认
        channel.confirmSelect();
        //开始时间
        long begin = System.currentTimeMillis();

        //消息确认成功回调函数
        ConfirmCallback ackCallback = (deliveryTag,multiple)->{
            System.out.println("确认的消息"+deliveryTag);
        };
        //消息确认失败回调函数(消息的标记,是否为批量确认)
        ConfirmCallback nackCallback = (deliveryTag,multiple)->{
            System.out.println("未确认消息"+deliveryTag);
        };
        //准备消息的监听器,(
        // 监听哪些消息成功了
        // 哪些消息失败了)
        channel.addConfirmListener(ackCallback,nackCallback);//异步通知

        //批量发消息 异步确认消息
        for(int i =0;i<MESSAGE_COUNT;i++){
            String message = i+"";
            channel.basicPublish("",queueName,null,message.getBytes());
        }

        //结束时间
        long end = System.currentTimeMillis();
        System.out.println("发布"+MESSAGE_COUNT+"个异步确认消息,耗时"+(end-begin)+"ms");

    }

5. 如何处理异步未确认消息

把未确认的消息放到一个基于内存的能被发布线程访问的队列,比如ConcurrentLinkedQueue[并发链路式队列]在confirm callbacks 与发布线程之间进行消息传递。

关键原理,将所有的序号和消息保存到一个线程安全有序的一个哈希表中,此数据结构需要满足以下三种条件:1.轻松的将序号与消息关联;2.轻松的批量删除条目,只要能给到序号;3.支持高并发

ConcurrentSkipListMap<Long,String> outstandingConfirms = new ConcurrentSkipListMap<>();

在批量发消息时记录所有要发的消息:

outstandingConfirms.put(channel.getNextPublishSeqNo(),message);

在确认发布时,通过是否批量进行操作,将哈希表中对应的数据去除:

if(multiple){
                ConcurrentNavigableMap<Long,String> confirmed =outstandingConfirms.headMap(deliveryTag);
                confirmed.clear();
            }else{
                outstandingConfirms.remove(deliveryTag);
            }

6. 总结

单独发布消息:同步等待确认,简单,吞吐量有限

批量发布消息:批量同步等待确认,简单,合理吞吐量,一旦出现问题很难推断出是哪条消息出了问题。

异步处理:最佳性能和资源使用,在出现错误的情况下可以很好控制,但是实现稍微困难

交换机

发布/订阅模式流程图

1. 概念

1)基本概念:RabbitMQ生产者生产的消息不会直接发送到队列,消息先发送到交换机中。

2)类型:直接(direct)主题(topic)标题(headers)扇出(fanout)无名(default)

2. 临时队列

不带有持久化的队列

创建临时队列方式:channel.queueDeclare().getQueue();

3. 绑定

交换机和队列通过routingkey绑定

4. fanout交换机

将所有接收到的消息广播到它知道的所有队列中。

1)消费者代码

package com.test.rabbitmq.five;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.test.rabbitmq.utils.RabbitMqUtils;
import com.test.rabbitmq.utils.SleepUtils;

/*
* 消息的接收
* */
public class ReceiveLogs1 {

    //交换机的名称
    public static final String EXCHANGE_NAME = "logs";
    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getChannel();

        //声明一个交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");

        //声明一个临时队列 队列名称随机
        String queuename = channel.queueDeclare().getQueue();

        //绑定交换机与队列
        channel.queueBind(queuename,EXCHANGE_NAME,"");
        System.out.println("等待接收消息,把接收到的消息打印到屏幕上……");


        DeliverCallback deliverCallback = (consumerTag, message)->{
            System.out.println("ReceiveLogs1控制台打印接收到的消息:"+new String (message.getBody(),"UTF-8"));
        };
        channel.basicConsume(queuename,true,deliverCallback,consumerTag -> {});
    }
}

2)生产者代码

package com.test.rabbitmq.five;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.test.rabbitmq.utils.RabbitMqUtils;

import java.util.Scanner;

/*
* 发消息 交换机
* */
public class EmitLog {

    //交换机的名称
    public static final String EXCHANGE_NAME = "logs";
    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getChannel();

        //声明一个交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");

        //声明一个临时队列 队列名称随机
        String queuename = channel.queueDeclare().getQueue();

        Scanner scanner  = new Scanner(System.in);

        while (scanner.hasNext()){
            String message = scanner.next();
            channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes());
            System.out.println("生产者发送消息"+message);
        }

    }
}

5. 直接交换机 (direct)

路由交换机:与扇出交换机区别,routingkey一致。

6. 主题交换机 (topic)

当接受的日志类型有A,B某个队列只想接收A消息,那么direct 就做不到了,只能使用topic类型

1)Topic 要求

routing_key 用【.】隔开的不多于255字节的字符串,【*】可以代替一个单词,【#】代替0个单词或多个单词

e.g. Q1绑定的是中间带orange带3个单词的字符(*.orange.*)

当一个队列绑定键是#,就有点像fanout了

如果绑定键没有#和*,就是direct了

2)消费者

package com.test.rabbitmq.seven;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.test.rabbitmq.utils.RabbitMqUtils;

/*
* 声明主题交换机
* 消费者C1
* */
public class ReveiveLogsTopic1 {

    //交换机的名称
    public static final String EXCHANGE_NAME = "topic_logs";
    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getChannel();

        //声明一个交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        //声明一个临时队列 队列名称随机
        String queuename = channel.queueDeclare("Q1",false,false,false,null).getQueue();

        //绑定交换机与队列
        channel.queueBind(queuename,EXCHANGE_NAME,"*.orange.*");
        System.out.println("C1等待接收消息……");
        DeliverCallback deliverCallback = (consumerTag, message)->{
            System.out.println("ReveiveLogsTopic1控制台打印接收到的消息:"+new String (message.getBody(),"UTF-8")+" 绑定键:"+message.getEnvelope().getRoutingKey());
        };
        channel.basicConsume(queuename,true,deliverCallback,consumerTag -> {});
    }
}

3)生产者

package com.test.rabbitmq.seven;

import com.rabbitmq.client.Channel;
import com.test.rabbitmq.utils.RabbitMqUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/*
* topic 生产者 交换机
* */

public class EmitLogTopic {

    //交换机的名称
    public static final String EXCHANGE_NAME = "topic_logs";
    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getChannel();

        String routingkey ="quick.orange.rabbit";
        String message ="被Q1Q2接收";
        channel.basicPublish(EXCHANGE_NAME,routingkey,null,message.getBytes());
        System.out.println("生产者发出消息"+message);

    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值