RabbitMQ学习教程二(交换机,死信队列)

Posted @黑夜中的一盏明灯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RabbitMQ学习教程二(交换机,死信队列)相关的知识,希望对你有一定的参考价值。

一:RabbitMQ的交换机

1.交换机的作用和概念
发布订阅模式:一个消息被消费两次

RoutingKey:交换机和队列之间进行绑定所填写的信息。
RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。
生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来
自生产者的消息,另一方面将它们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列还是说把他们到许多队列中还是说应该丢弃它们。这就的由交换机的类型来决定。

无名exchange(无名交换机):之前的代码中使用的是默认交换机,通常用空字符串进行标识。

第一个参数是交换机的名称。空字符串表示默认或无名称交换机:消息能路由发送到队列中其实是由 routingKey(bindingkey)绑定 key 指定的,如果它存在的话。

2.fanout交换机
fanout交换机是将接收到的所有消息广播到拥有相同的routingKey的队列中。
使用fanout交换机会出现生产者发送一条消息,如果routingKey相同,所有的消费者都会收到这一条消息。
生产者:

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

    public static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws IOException, TimeoutException 
        Channel channel = RabbitmqUtils.getChannel();

        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext())
            String message = scanner.next();
            channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
            System.out.println("生成这发出消息");
        

    



/**
 * 消息接收
 */
public class ReceiveLogs01 
    // 交换机的名称
    public static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws IOException, TimeoutException 
        Channel channel = RabbitmqUtils.getChannel();

        //声明一个交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        //声明一个队列 临时队列
        /**
         * 生成一个临时队列、队列的名称是随机的
         * 当消费者断开与队列的连接的时候 队列就自动删除
         */
        String queueName = channel.queueDeclare().getQueue();
        /**
         * 绑定交换机和队列
         */
        channel.queueBind(queueName,EXCHANGE_NAME,"");
        System.out.println("ReceiveLogs01等待接收消息,把接收到消息打印在屏幕上....");
        DeliverCallback deliverCallback = ( consumerTag, messag)->
            System.out.println("ReceiveLogs01控制台打印接受到的消息:" +new String(messag.getBody(),"UTF-8"));
        ;
        channel.basicConsume(queueName,true,deliverCallback,consumerTag->);

    


/**
 * 消息接收
 */
public class ReceiveLogs02 
    // 交换机的名称
    public static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws IOException, TimeoutException 
        Channel channel = RabbitmqUtils.getChannel();

        //声明一个交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        //声明一个队列 临时队列
        /**
         * 生成一个临时队列、队列的名称是随机的
         * 当消费者断开与队列的连接的时候 队列就自动删除
         */
        String queueName = channel.queueDeclare().getQueue();
        /**
         * 绑定交换机和队列
         */
        channel.queueBind(queueName,EXCHANGE_NAME,"");
        System.out.println("ReceiveLogs02等待接收消息,把接收到消息打印在屏幕上....");
        DeliverCallback deliverCallback = ( consumerTag, messag)->
            System.out.println("ReceiveLogs02控制台打印接受到的消息:" +new String(messag.getBody(),"UTF-8"));
        ;
        channel.basicConsume(queueName,true,deliverCallback,consumerTag->);

    



3.direct交换机
direct交换机:消息只去到它绑定的routingKey 队列中去

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

    public static final String DIRECT_LOGS = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException 
        Channel channel = RabbitmqUtils.getChannel();
//        HashMap<String, String> bindingKeyMap = new HashMap<>();
//        bindingKeyMap.put("ingo","普通的info信息");
//        bindingKeyMap.put("warning","警告warning信息");
//        bindingKeyMap.put("error","错误error消息");
//        for(Map.Entry<String, String> bindingKeyEntry: bindingKeyMap.entrySet())
//            String bindingKey = bindingKeyEntry.getKey();
//            String message = bindingKeyEntry.getValue();
//            channel.basicPublish(DIRECT_LOGS,bindingKey, null,
//                    message.getBytes("UTF-8"));
//            System.out.println("生产者发出消息:" + message);
//        
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext())
            String message = scanner.next();
            channel.basicPublish(DIRECT_LOGS,"error",null,message.getBytes("UTF-8"));
            System.out.println("生成这发出消息");
        

    



public class ReceiveLogsDirect01 

    public static final String DIRECT_LOGS = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException 
        Channel channel = RabbitmqUtils.getChannel();
        //声明一个交换机
        channel.exchangeDeclare(DIRECT_LOGS, BuiltinExchangeType.DIRECT);
        // 声明一个队列
        channel.queueDeclare("console",false,false,false,null);

        channel.queueBind("console",DIRECT_LOGS,"info");
        channel.queueBind("console",DIRECT_LOGS,"warning");

        DeliverCallback deliverCallback = ( consumerTag,  message)->
            System.out.println("ReceiveLogsDirect01接收到消息是"+ new String(message.getBody(),"UTF-8"));
        ;
        //接收消息
        channel.basicConsume("console",true,deliverCallback,(consumerTag -> ));



    


public class ReceiveLogsDirect02 

    public static final String DIRECT_LOGS = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException 
        Channel channel = RabbitmqUtils.getChannel();
        //声明一个交换机
        channel.exchangeDeclare(DIRECT_LOGS, BuiltinExchangeType.DIRECT);
        // 声明一个队列
        channel.queueDeclare("disk",false,false,false,null);

        channel.queueBind("disk",DIRECT_LOGS,"error");


        DeliverCallback deliverCallback = ( consumerTag,  message)->
            System.out.println("ReceiveLogsDirect01接收到消息是"+ new String(message.getBody(),"UTF-8"));
        ;
        //接收消息
        channel.basicConsume("disk",true,deliverCallback,(consumerTag -> ));



    


4.Topics交换机
direct交换机只能绑定一个,性能受限.
发送到类型是 topic 交换机的消息的 routing_key 不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。这些单词可以是任意单词,比如说:“stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”.这种类型的。当然这个单词列表最多不能超过 255 个字节。
规则是:


由于代码重复较多,只写了生产者,消费者可以根据上面改一下交换机类型和routingKey的值。

public class EmitLogTopic 
    public static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] args) throws IOException, TimeoutException 
        Channel channel = RabbitmqUtils.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        /**
         * Q1-->绑定的是
         * 中间带 orange 带 3 个单词的字符串(*.orange.*)
         * Q2-->绑定的是
         * 最后一个单词是 rabbit 的 3 个单词(*.*.rabbit)
         * 第一个单词是 lazy 的多个单词(lazy.#)
         *
         */
        Map<String, String> bindingKeyMap = new HashMap<>();
        bindingKeyMap.put("quick.orange.rabbit","被队列 Q1Q2 接收到");
        bindingKeyMap.put("lazy.orange.elephant","被队列 Q1Q2 接收到");
        bindingKeyMap.put("quick.orange.fox","被队列 Q1 接收到");
        bindingKeyMap.put("lazy.brown.fox","被队列 Q2 接收到");
        bindingKeyMap.put("lazy.pink.rabbit","虽然满足两个绑定但只被队列 Q2 接收一次");
        bindingKeyMap.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
        bindingKeyMap.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
        bindingKeyMap.put("lazy.orange.male.rabbit","是四个单词但匹配 Q2");
        for (Map.Entry<String, String> bindingKeyEntry : bindingKeyMap.entrySet()) 
            String bindingKey = bindingKeyEntry.getKey();
            String message = bindingKeyEntry.getValue();
            channel.basicPublish(EXCHANGE_NAME,bindingKey, null,
                    message.getBytes("UTF-8"));
            System.out.println("生产者发出消息" + message);
        

    


二:死信队列

1.死信队列
死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer 将消息投递到 broker 或者直接到 queue 里了,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入死信队列中.还有比如说: 用户在商城下单成功并点击去支付后在指定时间未支付时自动失效

2.死信实战
成为死信的三种条件:

  • 消息被拒绝

  • 队列达到最大长度

  • 消息TTL过期


生产者代码:

public class Producer 
    public static final String NORMAL_EXCHANGE = "normal_exchange";

    public static void main(String[] args) throws IOException, TimeoutException 
        Channel channel = RabbitmqUtils.getChannel();

        // 死信消息 设置TTL时间 单位是ms 10000ms=10s

        AMQP.BasicProperties properties =
                new AMQP.BasicProperties()
                        .builder()
                        .expiration("10000").build();
        for(int i=0 ;i < 11; i++)
            String message = "info" + i;
            channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
        

    


消费者:

/**
 * 死信队列
 *
 * 消费者1
 */
public class Consume1 
    //普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";
    // 死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";
    // 普通队列的名称
    public static final String NORMAL_QUEUE = "normal_queue";
    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException 
        Channel channel = RabbitmqUtils.getChannel();

        // 声明普通交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        // 声明死信交换机
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
        //声明死信队列
        channel.queueDeclare(DEAD_QUEUE,false,false,false,null);

        // 声明普通队列
        HashMap<String, Object> arguments = new HashMap<>();
        // 过期时间
//        arguments.put("x-message-ttl",100000);

        // 设置正常队列的最大长度
        arguments.put("x-max-length",6);
        //正常队列设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        // 设置死信routingKey
        arguments.put("x-dead-letter-routing-key","lisi");

        channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);


        // 绑定普通交换机和普通队列
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");
        //绑定死信交换机和死信队列
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
        System.out.println("等待接收消息");
        DeliverCallback deliverCallback = (consumerTag,  message)->
            String msg = new String(message.getBody(),"UTF-8");
            if(msg.equals("info5"))
                //消息被拒的处理情况
                System.out.println("Consumer01 接收到消息"+msg+"此消息是被拒绝的");
                channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
            else
                System.out.println("Consumer01 接收到消息"+msg);
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            

        ;
        // 开启手动应答
        channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,(consumerTag -> ));
    



死信消费者:

public class Consume2 

    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException 
        Channel channel = RabbitmqUtils.getChannel();
        
        System.out.println("等待接收消息");
        DeliverCallback deliverCallback = (consumerTag,  message)->
            System.out.println("Consumer02 接收到消息"+new String(message.getBody(),"UTF-8"));
        ;
        channel.basicConsume(DEAD_QUEUE,true以上是关于RabbitMQ学习教程二(交换机,死信队列)的主要内容,如果未能解决你的问题,请参考以下文章

RabbitMQ的死信队列和延时队列

浅谈RabbitMQ——死信队列与延迟队列

浅谈RabbitMQ——死信队列与延迟队列

浅谈RabbitMQ——死信队列与延迟队列

RabbitMQ之消息可靠性死信交换机惰性队列及集群

RabbitMQ之消息可靠性死信交换机惰性队列及集群