3、RabbitMq高级特性

1、消息的可靠性投递方案

  1. 消息落库,对消息状态进行标识(存储到消息数据库上。对状态进行修改。适合并发量不高的情况下)
  2. 消息延迟投递,做二次确认,回调检查(推荐使用。可以减少多次DB的存储)

消费幂等性:

利用Redis的原子性去实现

 

confirm确认消息

生产者(指定消息投递模式,并设置消息应答监听)

public class ProductConfirem {

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();

        //指定我们的消息投递模式:消息确认模式********************
        channel.confirmSelect();

        String exchange="confirem_muke";
        String rokey="confirem.save";
        channel.basicPublish(exchange,rokey,null,"这是个消息".getBytes());

        //添加一个消息确认监听********************
        channel.addConfirmListener(new ConfirmListener() {
            /**
             *
             * @param l 第一个参数为这个消息的唯一标签
             * @param b 是否批量的
             * @throws IOException
             */
            @Override
            public void handleAck(long l, boolean b) throws IOException {
                System.out.println("应答成功!"+l+"--->"+b);
            }

            @Override
            public void handleNack(long l, boolean b) throws IOException {
                System.out.println("应答失败!");
            }
        });
    }
}

消费者(只需要消费消息就可以)

public class ConsumerConfirem {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();

        String queuename="confirm_queue";
        String exchange="confirem_muke";
        //声明队列,声明交换机,并将队列绑定到交换机上
        channel.queueDeclare(queuename,true,false,false,null);
        channel.exchangeDeclare(exchange,"topic",true);
        channel.queueBind(queuename,exchange,"confirem.#");

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body,"UTF-8"));
            }
        };

        channel.basicConsume(queuename,true,consumer);
    }
}

 

Return返回消息机制(多适用于没有找到路由key或者exchange)

如果设置return返回消息机制,就必须把Mandatory设置成true,监听器就会接收到路由不可达的消息。然后进行处理。如果为false了。那么broker端会自动删除消息。

生产者

public class ProductSend {
    /**
     * 测试消息发送失败调用return机制
     * 1、首先消费者声明交换机的时,定义rokingkey为send.#。生产者故意写成aaa.save.这样就绝对不会路由到这个交换机上
     * 2、定义returnLisener
     * 3、channel.basicPublish()中的第三个参数Mandatory,必须设置成true,才会在发送失败后,触发return机制
     */
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        String exchangeName="test_send_exchange";
        String roketingkey="aaa.save";

        //添加监听,只要发送的消息失败,就会调用这个方法。
        //但是必须在channel.basicPublish()的第三个参数设置成true,才会起作用
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
                System.out.println("--------------------------------------------");
                System.out.println("replyCode---->"+replyCode);
                System.out.println("replyText---->"+replyText);
                System.out.println("exchange---->"+exchange);
                System.out.println("routingKey---->"+routingKey);
                System.out.println("basicProperties---->"+basicProperties);
                System.out.println("bytes---->"+bytes);
            }
        });
        //必须为true,才会在消息发送失败后,触发上面的return机制
        boolean mandatory=true;
        channel.basicPublish(exchangeName,roketingkey,mandatory,null,"这是测试send消息".getBytes());
    }
}

消费者(消费者没有什么可改变的。按照以前的方式写)

public class ConsumerSend {

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();

        String exchangeName="test_send_exchange";
        String queueName="test_send_queue";
        channel.queueDeclare(queueName,true,false,false,null);
        channel.exchangeDeclare(exchangeName,"topic");
        channel.queueBind(queueName,exchangeName,"send.#");
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("--->"+new String(body,"utf-8"));
            }
        };
        channel.basicConsume(queueName,true,consumer);
    }
}

 

消费端限流

为了防止大量消息在同一时间传给消费端。造成消费端故障。

生产者

public class ProductXl {
    private static final String EXCHANGE_NAME="muke_xl_exchange";
    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        String key="topic.send";
        for (int i = 0; i < 5; i++) {
            channel.basicPublish(EXCHANGE_NAME,key,null,"消费端限流得到消息".getBytes());
        }
        channel.close();
        connection.close();
    }
}

消费者(设置接收个数,接收消息后给生产者应答。改自动应答为手动应答)

public class ConsumerXl {
    private static final String EXCHANGE_NAME="muke_xl_exchange";
    private static final String QUEUE_NAME="muke_xl_queue";
    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,"topic",true,false,false,null);
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"topic.#");

        /**
         * 第一个参数:限制消息大小
         * 第二个参数:每次接收消息个数
         * 第三个参数:不应用到channel级别,应用到整个consumer级别
         */
        channel.basicQos(0,1, false);
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("----->"+new String(body,"utf-8"));
                /**
                 * 消费完消息后,给生产者回应ack
                 * envelope.getDeliveryTag()代表当前消息数
                 * false代表不批量签收
                 */
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        //必须改成false,变成手动应答
        boolean autoack=false;
        channel.basicConsume(QUEUE_NAME,autoack,consumer);
    }
}

消息重回队列

生产者

public class ProductDl {
    private static final String EXCHANGE_NAME="muke_dl_exchange";
    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        String key="topic.send";
        for (int i = 0; i < 5; i++) {
            Map<String,Object> header=new HashMap<>();
            header.put("num",i);
            String msg="消费失败重回队列---》"+i;
            //设置消息的附加属性,给消息加上header,我们更容易在消费者端进行判断
            AMQP.BasicProperties properties=new AMQP.BasicProperties().builder()
                    .contentEncoding("UTF-8")
                    .expiration("2000")
                    .headers(header)
                    .build();
            channel.basicPublish(EXCHANGE_NAME,key,properties,msg.getBytes());
        }
    }
}

消费者(重回队列必须是用basicNack,并将第三个参数设置成true。让当前消息重回队列中)

public class ConsumerDl {
    private static final String EXCHANGE_NAME="muke_dl_exchange";
    private static final String QUEUE_NAME="muke_dl_queue";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,"topic",true,false,false,null);
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"topic.#");

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("----->"+new String(body,"utf-8"));
                if((Integer)properties.getHeaders().get("num")==1){
                    /**
                     * **********重回队列是调用basicNack************
                     * 第三个参数,代表是否重回队列.重回队列也是重回队列尾部,按序执行
                     */
                    channel.basicNack(envelope.getDeliveryTag(),false,true);
                }else {
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        //必须改成手动应答
        boolean autoack=false;
        channel.basicConsume(QUEUE_NAME,autoack,consumer);
    }
}

 

TTL消息队列

TTL代表time to live的缩写,也就是生存时间。消息没被消费,在指定时间过期后自动清除(可以对消息设置过期时间。也可以设置对于某个队列中,只要进行这个队列的消息,在某个时间还没被消费,就会被过期删除)

 AMQP.BasicProperties properties=new AMQP.BasicProperties().builder()
                    .contentEncoding("UTF-8")
                //expiration就是设置过期时间。这是对于消息设置过期时间的
                    .expiration("2000")
                    .headers(header)
                    .build();

 

死信队列(DLX)

解释:当消息在一个队列中由于某个原因没被消费,这个消息就成为死信。变成死信之后,它能被重新publish到另一个exchange,而这个Exchange就称为死信队列。

DLX其实就是一个Exchange,它只是可以在任何队列进行绑定。当这个队列有 死信的时候,MQ就会自动将这个消息发送给这个Exchange,进行被路由到另一个队列中去。

成为死信的几种情况:

  • 消息被拒绝:设置basic.Nack,并且第三个参数中的requeue还设置成false,不能重回队列。这个消息就成为死信
  • 消息TTL过期
  • 队列达到最大长度

设置死信队列前提:

首先需要设置死信队列的exchange和queue,然后进行绑定:

Exchange:dlx.exchange

Queue:dlx.queue

RoutingKey:#        //代表所有死信消息都会被路由到这个队列上来

然后我们进行正常声明交换机、队列、绑定。只不过我们需要在队列上加一个参数:

argument.put("x-dead-letter-exchange","死信的exchange")

代码实现:在测试的时候,先启动consumer,把该声明的队列声明出来。然后关闭consumer端。启动product端,看管控台变化。是否10秒过后,消息没被消费,转而进入死信队列中。

生产者

public class ProductDlx {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        String exchangeName="test_dxl_exchange";
        String routingkey="dxl.save";

        String msg="正常队列发送消息";
        AMQP.BasicProperties properties=new AMQP.BasicProperties().builder()
                //设置10秒过期,如果10秒不被消费,就会跑到死信队列上去
                .expiration("10000")
                .build();
        channel.basicPublish(exchangeName,routingkey,properties,msg.getBytes());
        channel.close();
        connection.close();
    }
}

消费者(声明普通队列,声明死信队列。并将死信队列绑定到普通队列上)

public class ConsumerDlx {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        //这里是声明正常队列和exchange的信息。并不是死信队列的。要明确这一点
        String exchangeName="test_dxl_exchange";
        String routingkey="dxl.#";
        String queueName="test_dxl_queue";

        /**
         * *******new 一个hashmap,argument.put("x-dead-letter-exchange","死信的exchange")。
         * 这个后面的exchange名字就是真正死信队列的交换机。
         * 这个map必须放在正常queue上。千万不要放在exchange上
         */
        Map<String,Object> argument=new HashMap<>();
        argument.put("x-dead-letter-exchange","dxl_exchange");
        //***************将绑定的死信交换机,放在正常的queue上
        channel.queueDeclare(queueName,true,false,false,argument);
        channel.exchangeDeclare(exchangeName,"topic",true,false,null);
        channel.queueBind(queueName,exchangeName,routingkey);

        //声明死信交换机和死信队列.这里才是真正的死信队列声明。exchange名称必须跟上面绑定在正常queue上的exchange一样
        String dxl_exchangeName="dxl_exchange";
        String dxl_queueName="dxl_queue";
        channel.queueDeclare(dxl_queueName,true,false,false,null);
        channel.exchangeDeclare(dxl_exchangeName,"topic",true,false,null);
        channel.queueBind(dxl_queueName,dxl_exchangeName,"#");

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("--->" + new String(body, "GBK"));
            }
        };
        channel.basicConsume(queueName,true,consumer);
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值