RabbitMq 高级特性

本文深入探讨消息队列的高级特性,包括100%投递成功的保障机制、消息幂等性、业务高峰期避免重复消费策略、Confirm确认与Return消息机制、自定义消费者、消息ACK与重回队列、消息限流、TTL消息及死信队列。通过实例代码,解析生产者与消费者端的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.消息是如何保障100% 得投递成功?

2.消息的幂等性

3.海量订单产生的业务高峰期,如何避免消息的重复消费问题?

4.Confirm确认消息、Return 返回消息

5.自定义消费者

6.消息的ACK与重回队列

7.消息的限流

8.TTL消息

9.死信队列(任何一个MQ都有死信队列的概念)


                                                                         如何保障100% 的投递成功

什么是生产端的可靠性投递?

       --1.保障消息的成功发送;2.保障MQ节点的成功接收;3.发送端收到MQ节点(Broker)确认应答;4.完善的消息补偿机制

生产端-可靠系投递(一)

     BAT/TMD 互联网大厂的解决方案:

               1.消息落库(1.将要发送的消息入库持久化,2.发出消息),对消息状态进行打标(没有应答的消息做轮询,设置次数的临界值);

               2.消息的延迟投递,做二次确认,回调检查;

 第一种消息落库的方式 ,在高并发情况下是否合适呢?

           不合适,做两次入库持久化操作不适宜;

           消息的延迟投递(少做一次DB的存储,做异步消息补偿),做二次确认,回调检查 方式:Step1 消息落库后发送消息,Step2  第二条消息延迟检查发送


                                                                                    幂等性概念

幂等性是什么?

        借鉴数据库乐观锁机制:

        比如我们执行一条更新库存的SQL语句update t_reps set count-1, version = version+1 where version = 1;

        如果count 值只剩1了,并发情况下都来执行语句,可能会出现负数的情况,所以加一个version,并且version+1 利用这种情况保证乐观锁,保证幂等性)

       幂等性: 执行一个语句无论多少次,其结果都是唯一的,相同的,这个就是幂等性的保障。

 

消费端-幂等行保障:在海量订单产生的业务高峰期,如何避免消息的重复消费问题?

      消费端实现幂等性,就意味着,我们的消息永远不会消费多次,即使我们收到了多条一样的消息(代码可能会执行多次,但是消费只会一次)

业界主流的幂等性操作:

      1.唯一ID +指纹码 机制,利用数据库主键去重(最普遍方式)

             select count(1) from t_order where id = 唯一ID + 指纹码(时间戳或内部的业务规则的唯一码)

             好处:实现简单;坏处:高并发情况下数据库写入的性能瓶颈;解决方案:跟进ID进行分库分表进行算法路由

      2.利用Redis的原子性去实现

              使用Redis进行幂等,需要考虑的问题:

                         第一:我们是否进行数据落库,如果落库的话,关键解决的问题是数据库的缓存如何做到原子性?(落库数据与缓存数据的一致性)

                        第二:如果不进行落库,那么存储到缓存中,如何设置定时同步策略?


                                                                                 Confirm 确认消息

理解Confirm消息确认机制:

   消息的确认是指生产者投递消息后,如果Broker收到消息,则会给我们生产者一个应答。

   生产者进行接收应答,用来确认这条消息是否正常的发送到Broker,这种方式也是消息的可靠性投递的核心保障

   确认机制流程图(待补充):

   第一步:在channel上开启确认模式:channel.confirmSelect();

   第二步:在channel上添加监听:addConfirmListener,监听成功和失败返回结果,根据具体的结果对消息进行重新发送,或记录日志等后续处理!

/**
 * Created by LIUYAO823 on 2019-9-3 15:00
 * 生产者
 */
public class Producer {

    public static void main(String[] args) throws Exception{
        //1.创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");

        //2.通过ConnectionFactory 创建Connection
        Connection connection =connectionFactory.newConnection();

        //3.通过Connection创建Channel(核心通信)
        Channel channel = connection.createChannel();
        //4.指定消息的确认模式
        channel.confirmSelect();
        String exchangename = "test_confirm_exchange";
        String routingkey="confirm_save";

        String Massage = "hello mq test message confirm";
        //5.生产消息
        System.out.println("生产者......");
        channel.basicPublish(exchangename,routingkey,null,Massage.getBytes());

        //6.添加一个确认监听
        channel.addConfirmListener(new ConfirmListener() {
            //如果ack 和Nack 都收不到 可靠性透体问题, 网络出现闪断, 可能需要做定时任务,做消息的补偿
            @Override
            public void handleAck(long l, boolean b) throws IOException {
                System.out.println("------ok ack-----------");
            }

            @Override
            public void handleNack(long l, boolean b) throws IOException {
                //磁盘写满了,queue容量到达上线,MQ出现异常,会出现Nack
                System.out.println("-----------no ok ack-----------");
            }
        });

        //7.不做关闭, 监听不到
          /*channel.close();
          connection.close();*/

    }
}
/**
 * Created by LIUYAO823 on 2019-9-3 15:01
 * 消费者
 */
public class Counsumer {

    public static void main(String[] args) throws Exception{
        //创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");

        //通过ConnectionFactory 创建Connection
        Connection connection =connectionFactory.newConnection();

        //通过Connection创建Channel(核心通信)
        Channel channel = connection.createChannel();

        //声明
        String exchangename = "test_confirm_exchange";
        String routingkey="confirm_save";
        String queuename = "test_confirm_queue";
        channel.exchangeDeclare(exchangename,"topic",true)  ;    //交换机
        channel.queueDeclare(queuename,true,false,false,null);//队列

        //绑定操作
        channel.queueBind(queuename,exchangename,routingkey );

        //创建一个消费者
        channel.basicConsume(queuename, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body)throws IOException{
                System.out.println("消费者consumerTag ["+consumerTag+"]envelope["+envelope+"]");
                //String routingKey = envelope.getRoutingKey();
                //String contentType = properties.getContentType();
                long deliveryTag = envelope.getDeliveryTag();
                channel.basicAck(deliveryTag, false);
            }
        });

    }
}

                                                                                 Retrun 消息机制

Retrun Listener 用于处理一些不可路由的消息!

我们的消息生产者,通过指定一个Exchange 和RoutingKey,把消息送达到某一个队列中去,然后我们的消费者监听队列,进行消费处理操作!但是在某些情况下,如果我们在发送消息的时候,当前的Exchange不再在或者指定的路由key路由不到,这个时候如果我们需要监听这种不可达的消息,就要是同Retrun Listener!

在基础API中有一个关键的配置项:

     Mandatory:如果为true,则监听器会收到路由不可达的消息,然后进行后续处理, 如果为false,那么broker端自动删除该消息。

Producer---->Nofind Exchange ----->Return Listener

/**
 * Created by LIUYAO823 on 2019-9-3 15:00
 * 生产者
 */
public class Producer {

    public static void main(String[] args) throws Exception{
        //1.创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");

        //2.通过ConnectionFactory 创建Connection
        Connection connection =connectionFactory.newConnection();

        //3.通过Connection创建Channel(核心通信)
        Channel channel = connection.createChannel();
        //4.指定消息的确认模式
        channel.confirmSelect();
        String exchangename = "test_return_exchange";
        String routingkey="return_save";
        String routingkeyError="returnError"; //做错误的路由测试Return监听

        String Massage = "hello mq test message return";
        //5.生产消息
        System.out.println("生产者......");
        channel.basicPublish(exchangename,
                routingkeyError,
                true,//Mandatory:如果为true,则监听器会收到路由不可达的消息,然后进行后续处理, 如果为false,那么broker端自动删除该消息。
                null,
                Massage.getBytes());

        //6.添加一个return 的监听
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(
                    int replycode,
                    String s,
                    String s1,
                    String s2,
                    AMQP.BasicProperties basicProperties,
                    byte[] bytes) throws IOException {
                System.out.println("------------handel return replycode["+replycode);
                System.out.println("------------handel return s1["+s1);
                System.out.println("------------handel return s2["+s2);
                System.out.println("------------handel return s3["+basicProperties);

            }
        });
        //7.不做关闭, 监听不到
          /*channel.close();
          connection.close();*/

    }
}
/**
 * Created by LIUYAO823 on 2019-9-3 15:01
 * 消费者
 */
public class Counsumer {

    public static void main(String[] args) throws Exception{
        //创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");

        //通过ConnectionFactory 创建Connection
        Connection connection =connectionFactory.newConnection();

        //通过Connection创建Channel(核心通信)
        Channel channel = connection.createChannel();

        //声明
        String queuename = "test_return_queue";
        String exchangename = "test_return_exchange";
        String routingkey="return_save";
        String routingkeyError="returnError";

        channel.exchangeDeclare(exchangename,"topic",true)  ;    //交换机
        channel.queueDeclare(queuename,true,false,false,null);//队列

        //绑定操作
        channel.queueBind(queuename,exchangename,routingkey );

        //创建一个消费者
        channel.basicConsume(queuename, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body)throws IOException{
                System.out.println("消费者consumerTag ["+consumerTag+"]envelope["+envelope+"]");
                //String routingKey = envelope.getRoutingKey();
                //String contentType = properties.getContentType();
                long deliveryTag = envelope.getDeliveryTag();
                channel.basicAck(deliveryTag, false);
            }
        });

    }
}

                                                                                       消费端自定义监听

自定义的Consumer

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值