原生rabbitMq实现

原生实现rabbitMQ

  1. 为什么要使用rabbitMQ消息队列

我们模拟这样一个场景,假如你在网上购物商城买了东西,你支付过程5s、发送短信通知5s、邮件通知等时间就更长了,你整个支的过程我们模拟这样一个场景,假如你在网上购物商城买了东西,你支付过程5s、发送短信通知5s、邮件通知等时间就更长了,你整个支付的过程就和很长了,因为代码是耦合在一起,但是如果你使用消息中间系统的话,我们支付过程就很短了,因为消息通知都是异步的,由消息系实现。

  1. 实现原生rabbitMQ流程

创建连接工厂–>给工厂设置连接属性(地址,端口、账号密码等)–>由工厂得到连接
–>通过连接得到信道–>由信道设置交换器->信道发布消息

生产者 :

//创建连接工厂,设置工厂属性
ConnectionFactory factory = new ConnectionFactory();   
factory.setHost("127.0.0.1");        
factory.setUsername("guest");
factory.setPassword("guest");
  //声明路由键和交换器
 private final static String EXCHANGE_NAME = "direct_cc_confirm_1";   
  //路由键
 private final static String ROUTE_KEY = "error";
 Connection connection = factory.newConnection();  
 Channel channel = connection.createChannel();    
 //通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); 
 //信道发布消息
channel.basicPublish(EXCHANGE_NAME,ROUTE_KEY,null,message.getBytes());
//关闭连接
 channel.close();
connection.close();

消费者:

// 在信道声明交换器前步骤相同,在以后就是不同了
channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
        System.out.println("Waiting message.......");

        // 创建队列消费者 设置一个监听器监听消费消息 
        final Consumer consumerB = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
             String message = new String(body, "UTF-8");
             System.out.println( "Received ["+ envelope.getRoutingKey() + "] "+message);
            }
        };
        //消费者自动确认:autoAck参数为true
        channel.basicConsume(queueName, true, consumerB);
    }

以上只有一个生产者和消费者只有一个路由键,但是如果生产者由很多,消费者只有一个那就会只接受路由键匹配的生产者发送的消息

生产者:

       //定义一组路由键
        String[]routingKeys = {"error","info","warning"};
        for(int i=0;i<3;i++){
        	//路由键
            String routingKey = routingKeys[i];            
            //要发送的消息
            String message = "Hello world_"+(i+1);
            /**
             * 发送消息到交换器上
             * 参数1:交换器的名字
             * 参数2:路由键
             * 参数3:BasicProperties
             * 参数4:要发送的消息
             */
            channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes());
            System.out.println("Sent "+routingKey+":"+message);
        }

消费者:

//只匹配生产者由error路由键发送的消息
 String routingKey = "error";
        
        //7.队列通过路由键绑定到交换器上
        channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
        
        System.out.println("Waiting message.......");

        //8.设置一个监听器监听消费消息
        Consumer consumerB = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String message = new String(body,"UTF-8");
                System.out.println("Accept:"+envelope.getRoutingKey()+":"+message);
            }
        };        
        //9.自动确认:autoAck参数为flase
        channel.basicConsume(queueName,flase,consumerB);
    }
//消费者如果要接受所有信息的话
 //5.声明随机队列
        String queueName = channel.queueDeclare().getQueue();
        
        //6.定义一组路由键消费所有日志
        String[]routingKeys = {"error","info","warning"};
        
        //7.队列通过路由键绑定到交换器上
        for(String routingKey:routingKeys){
            //队列和交换器的绑定
            channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
        }
        System.out.println("Waiting message.......");

        //8.设置一个监听器监听消费消息
        Consumer consumerA = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                AMQP.BasicProperties properties, byte[] body)throws IOException {
                String message = new String(body,"UTF-8");
                System.out.println("Accept:"+envelope.getRoutingKey()+":"+message);
            }
        };
        channel.basicConsume(queueName,flase,consumerA);
  1. rabbbitMQ自动确认机制

自动确认机制分为同步确认和异步确认,就是在发送方发了消息,消费者返回ack,才确认这个消息为发送成功,否则就是发送失败,unacked.

在这里插入图片描述

同步实现:

  channel.confirmSelect();

        //发布消息的交换器上
        for(int i=0;i<2;i++){
            String msg = "Hello "+(i+1);
            channel.basicPublish(EXCHANGE_NAME,ROUTE_KEY,null,msg.getBytes());
            //等待RabbitMQ返回消息确认消息已送达RabbitMQ服务器
            if (channel.waitForConfirms()){
                System.out.println("发送方同步确认: "+ROUTE_KEY+":"+msg);
            }
        }

异步实现

 //将信道设置为发送方确认
        channel.confirmSelect();

        //信道被关闭监听器 用于RabbitMQ服务器断线重连
        //channel.addShutdownListener();

        /**
         * 生产者异步确认监听
         * 参数deliveryTag代表了当前channel唯一的投递
         * 参数multiple:false
         * 
         */
        channel.addConfirmListener(new ConfirmListener() {
        	//RabbitMQ服务器确认收到消息
            public void handleAck(long deliveryTag, boolean multiple)
                    throws IOException {
                System.out.println("RabbitMQ服务器确认收到消息Ack deliveryTag="+deliveryTag
                        +"multiple:"+multiple);
            }

            //RabbitMQ服务器由于自己内部出现故障没有收到消息
            public void handleNack(long deliveryTag, boolean multiple)
                    throws IOException {
                System.out.println("RabbitMQ服务没有收到消息Ack deliveryTag="+deliveryTag
                        +"multiple:"+multiple);
            }
        });

        //生产者异步返回监听 这里和发布消息时的mandatory参数有关
        //参数mandatory:mandatory=true,投递消息时无法找到一个合适的队列,把消息返回给生产者,mandatory=false 丢弃消息(缺省)
        channel.addReturnListener(new ReturnListener() {
            public void handleReturn(int replyCode, String replyText,
                                     String exchange, String routingKey,
                                     AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body);
                System.out.println("replyText:"+replyText);
                System.out.println("exchange:"+exchange);
                System.out.println("routingKey:"+routingKey);
                System.out.println("msg:"+msg);
            }
        });
  //后面是进行发布操作

//9.消费者自动确认:autoAck参数为true
channel.basicConsume(queueName, true, consumerB);

<think>嗯,用户想了解如何使用RabbitMQ实现延迟队列或延迟消息的最佳实践和方案。首先,我需要回忆一下RabbitMQ的基本机制以及实现延迟消息的常见方法。根据之前的引用内容,用户提供的参考信息提到了两种主要方法:利用TTL+死信队列的方式,以及使用RabbitMQ的插件。 首先,我需要确认用户提到的两种方法的具体步骤。第一种方法是通过设置消息的过期时间(TTL),然后当消息过期后,转发到死信交换机,进而进入死信队列,消费者监听这个队列来处理延迟消息。这种方法的关键点在于如何设置TTL,可以是队列级别的或者消息级别的。队列级别的TTL会影响所有进入该队列的消息,而消息级别的则可以针对每条消息设置不同的过期时间。不过,这里需要注意,如果队列中存在多条消息,RabbitMQ只会检查队列头部的消息是否过期,这可能导致后面的消息即使过期了也不会被及时处理,造成延迟不准确的问题。因此,这种方法可能在需要不同延迟时间的场景下不够灵活,或者在高负载时出现延迟不准的情况。 第二种方法是使用RabbitMQ的官方插件rabbitmq_delayed_message_exchange。这个插件允许创建一个延迟交换机,消息发送到该交换机时可以指定延迟时间,交换机会在延迟结束后将消息路由到相应的队列。这种方法相比死信队列的方式更灵活,每条消息可以有不同的延迟时间,并且避免了消息堆积导致延迟不准的问题。不过,用户需要确保他们的RabbitMQ服务器已经安装并启用了该插件,这可能需要一定的运维操作。 接下来,用户可能需要知道这两种方法的优缺点比较。比如,死信队列的方式是RabbitMQ原生支持的,不需要额外安装插件,但可能在处理不同延迟时间的消息时效率不高。而插件方式更灵活,但需要维护插件的安装和兼容性。另外,还需要考虑消息的可靠性,比如消息在延迟过程中如果RabbitMQ节点崩溃,如何保证消息不丢失,可能需要持久化队列和交换机,以及消息的持久化设置。 另外,根据引用内容中的代码示例,用户可能还需要具体的实现步骤,比如如何在声明队列时设置x-message-ttl参数,或者如何配置死信交换机。例如,在Java中使用Spring AMQP时,如何通过注解或配置类来声明这些队列和交换机。同时,使用插件时,如何声明x-delayed-type的交换机,并在发送消息时设置延迟头。 还需要考虑性能优化方面,比如消息堆积时的处理,是否需要对队列进行分区或者使用优先级队列。此外,错误处理机制也很重要,比如如何处理消费者处理失败的情况,是否需要重试机制或者死信队列的再次处理。 总结来说,回答应该分步骤说明两种方法的具体实现,比较它们的优缺点,并给出最佳实践的建议,比如在需要不同延迟时间的场景下推荐使用插件,而在简单固定延迟的场景下可以使用死信队列。同时,提醒用户注意消息的可靠性和性能问题,确保配置正确,并参考官方文档进行实施。</think>### RabbitMQ实现延迟队列的两种方案 #### 一、基于TTL+死信队列方案 1. **核心原理** 通过设置消息的生存时间(TTL),将过期消息自动转发到死信队列实现延迟效果,包含两个关键步骤: - 定义带有TTL的普通队列,绑定死信交换机 - 消费者监听死信队列处理延迟消息 2. **具体实现步骤** ```python # 声明普通队列(设置TTL和死信路由规则) channel.queue_declare( queue='order_queue', arguments={ 'x-message-ttl': 60000, # 队列级别TTL=60秒 'x-dead-letter-exchange': 'dlx_exchange', # 死信交换机 'x-dead-letter-routing-key': 'dead_route' # 死信路由键 } ) # 声明死信队列 channel.queue_declare(queue='dead_queue') channel.queue_bind(exchange='dlx_exchange', queue='dead_queue', routing_key='dead_route') # 发送消息到普通队列 channel.basic_publish( exchange='', routing_key='order_queue', body=message, properties=pika.BasicProperties( expiration='30000' # 消息级别TTL=30秒(优先级高于队列TTL) ) ) ``` *注意:队列级别TTL会覆盖消息级别TTL[^3]* 3. **适用场景** - 固定延迟时间的场景(如统一15分钟订单超时) - 不需要高精度延迟控制的场景 4. **缺点** - 队列堆积时可能出现延迟不准确 - 无法动态设置不同延迟时间 #### 二、基于延迟消息插件方案 1. **环境准备** 安装`rabbitmq_delayed_message_exchange`插件: ```bash rabbitmq-plugins enable rabbitmq_delayed_message_exchange ``` 2. **声明延迟交换机** ```python # 声明自定义交换机类型为x-delayed-message channel.exchange_declare( exchange='delayed_exchange', exchange_type='x-delayed-message', arguments={'x-delayed-type': 'direct'} ) # 绑定业务队列 channel.queue_declare(queue='delayed_queue') channel.queue_bind(exchange='delayed_exchange', queue='delayed_queue', routing_key='delay_route') ``` 3. **发送延迟消息** ```python headers = {'x-delay': 5000} # 设置5秒延迟 channel.basic_publish( exchange='delayed_exchange', routing_key='delay_route', body=message, properties=pika.BasicProperties(headers=headers) ) ``` 4. **优势对比** | 特性 | 死信队列方案 | 插件方案 | |--------------------|----------------------|----------------------| | 延迟精度 | 低 | 高 | | 动态延迟时间 | 不支持 | 支持 | | 消息堆积影响 | 可能延迟不准确 | 无影响 | | 系统依赖性 | 无需插件 | 需要安装插件 | #### 三、最佳实践建议 1. **消息可靠性保障** - 启用持久化:交换机、队列、消息都需设置`durable=True` - 添加生产端确认机制(Publisher Confirms) - 消费端开启手动应答(Manual Acknowledgement) 2. **性能优化** - 当使用插件方案时,建议将`x-delayed-type`设置为与路由匹配的类型(如direct/topic) - 对于超过24小时的长延迟需求,建议结合数据库记录+定时任务补偿 3. **错误处理** ```java // Spring Boot示例:死信队列消费者 @RabbitListener(queues = "dead_queue") public void handleFailedMessage(Message message, Channel channel) { try { // 业务处理逻辑 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { // 记录异常消息到数据库 channel.basicNack(deliveryTag, false, false); } } // 引用[3]示例扩展 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值