RabbitMQ 安装部署(New)

RabbitMQ 安装部署(New)

elang环境与MQ版本一定要对应,否则无法启动,Rabbit版本与插件版本一定要对应,负责无法加载插件

# RabbitMQ 安装部署(New)

> elang环境与MQ版本一定要对应,否则无法启动,Rabbit版本与插件版本一定要对应,负责无法加载插件

|                                   |            版本信息             |
| :-------------------------------: | :-----------------------------: |
|              centos               |              7.3.0              |
|              erlang               | Version: 23.0.2,Release: 2.el7 |
|             RabbitMQ              |             3.8.0-1             |
| rabbitmq_delayed_message_exchange |               3.8               |

## 安装脚本步骤:

1. 卸载erlang

   ```shell
   service rabbitmq-server stop  --停止服务
   yum list | grep erlang          --查询当前环境    
   yum -y remove erlang-*          --卸载 
   yum remove erlang.x86_64      --移除
   ```

2. 卸载rabbitMq

   ```shell
   yum list | grep rabbitmq                --当前是否安装
   yum -y remove rabbitmq-server.noarch    --移除安装文件
   ```

3. 安装erlang

   ```shell
   yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel
   wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
   rpm -Uvh erlang-solutions-1.0-1.noarch.rpm
   yum -y install epel-release
   sudo yum install erlang
   ```

   ```shell
   [root@nginx-1 mq]# yum info erlang           --校验版本信息
   Loaded plugins: fastestmirror
   Loading mirror speeds from cached hostfile
   Installed Packages
   Name        : erlang
   Arch        : x86_64
   Version     : 23.0.2
   Release     : 2.el7
   Size        : 0.0  
   Repo        : installed
   From repo   : erlang-solutions
   Summary     : General-purpose programming language and runtime environment
   URL         : http://www.erlang.org
   License     : ERPL
   Description : Erlang is a general-purpose programming language and runtime
               : environment. Erlang has built-in support for concurrency, distribution
               : and fault tolerance. Erlang is used in several large telecommunication
               : systems from Ericsson.
   ```

4. 安装rabbitMq

   ```shell
   wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.0/rabbitmq-server-3.8.0-1.el7.noarch.rpm
   yum -y install socat    --安装socat依赖
   rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc  --导入什么签名
   rpm -ivh rabbitmq-server-3.8.0-1.el7.noarch.rpm
   ```

5. 部署插件

   ```shell
   rabbitmq-delayed-message-exchange        --插件名称
   https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/tag/v3.8.0  --下载地址,选择3.8
   ```

   RabbitMQ的有些插件没有集成在初始的安装中,它们需要额外安装,这些文件的后缀为`.ez`,安装时需要将`.ez`文件拷贝到安装的插件目录。以下是不同系统中默认安装的插件目录路径:

   |          |                           插件目录                           |
   | :------: | :----------------------------------------------------------: |
   |  Linux   |  /usr/lib/rabbitmq/lib/rabbitmq_server-${*version*}/plugins  |
   | Windows  | C:\Program Files\RabbitMQ\rabbitmq_server-*version*\plugins(安装rabbitmq的目录) |
   | Homebrew |         /usr/local/Cellar/rabbitmq/*version*/plugins         |

   ```shell
   rabbitmq-plugins enable rabbitmq_delayed_message_exchange   --启用插件
   rabbitmq-plugins disable rabbitmq_delayed_message_exchange  --弃用插件
   ```

6. 授权用户访问

   ```shell
   rabbitmq-plugins enable rabbitmq_management  --启用网页插件
   添加用户:rabbitmqctl add_user admin admin
   添加权限:rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
   修改用户角色rabbitmqctl set_user_tags admin administrator
   然后就可以远程访问了,然后可直接配置用户权限等信息。
   ```

7. 启动命令脚本

   ```shell
   service rabbitmq-server restart  --重启
   service rabbitmq-server stop     --停止  
   ```

   

## Spring搭建延时队列-注解方式

> 此处应用于Spring、SpringBoot等开源框架

1. 添加依赖

   ```java
   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
   </dependency>
   ```

2. 定义工厂链接信息

   ```java
   package com.soul.home.lws.system.conf;
   
   import com.soul.home.lws.conf.properties.AutoConfigRabbitMqProperties;
   import com.soul.home.lws.conf.properties.AutoConfigTomcatProperties;
   import com.soul.home.lws.system.mq.MQRepublishMessageRecoverer;
   import org.springframework.amqp.AmqpException;
   import org.springframework.amqp.core.AcknowledgeMode;
   import org.springframework.amqp.core.DirectExchange;
   import org.springframework.amqp.rabbit.annotation.Queue;
   import org.springframework.amqp.rabbit.annotation.RabbitListener;
   import org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean;
   import org.springframework.amqp.rabbit.connection.*;
   import org.springframework.amqp.rabbit.core.RabbitTemplate;
   import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
   import org.springframework.amqp.support.converter.SimpleMessageConverter;
   import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.boot.context.properties.EnableConfigurationProperties;
   import org.springframework.context.annotation.Bean;
   import org.springframework.context.annotation.Configuration;
   import org.springframework.retry.RetryContext;
   import org.springframework.retry.backoff.BackOffContext;
   import org.springframework.retry.backoff.BackOffInterruptedException;
   import org.springframework.retry.backoff.BackOffPolicy;
   import org.springframework.retry.backoff.ExponentialBackOffPolicy;
   import org.springframework.retry.policy.SimpleRetryPolicy;
   import org.springframework.retry.support.RetryTemplate;
   
   import java.io.IOException;
   import java.util.concurrent.TimeoutException;
   
   /**
    * @author gaoguofan
    * @date 2020/6/15
    */
   @Configuration
   @EnableConfigurationProperties(AutoConfigRabbitMqProperties.class)
   public class AutoConfigurationRabbitMqConfig {
   
       @Autowired
       AutoConfigRabbitMqProperties autoConfigRabbitMqProperties;
   
       @Bean
       public ConnectionFactory connectionFactory() {
           com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = new com.rabbitmq.client.ConnectionFactory();
           rabbitConnectionFactory.setHost(autoConfigRabbitMqProperties.getHost());
           rabbitConnectionFactory.setPort(autoConfigRabbitMqProperties.getPort());
           rabbitConnectionFactory.setUsername(autoConfigRabbitMqProperties.getUserName());
           rabbitConnectionFactory.setPassword(autoConfigRabbitMqProperties.getPassword());
           rabbitConnectionFactory.setRequestedChannelMax(autoConfigRabbitMqProperties.getRequestedChannelMax());
           ConnectionFactory connectionFactory = new AbstractConnectionFactory(rabbitConnectionFactory) {
               @Override
               public Connection createConnection() throws AmqpException {
                   try {
                       return new SimpleConnection(rabbitConnectionFactory.newConnection(), 100000);
                   } catch (Exception e) {
                       e.printStackTrace();
                   }
                   return null;
               }
           };
           return connectionFactory;
       }
   
       // 消费者消费消息,首先进入重试逻辑中,如果有异常则根据重试机制重试
       // 若超过重试机制,则执行下面StatelessRetryOperationsInterceptorFactoryBean中的messageRecoverer类方法
       // 所以,手动Ack的情况下,此处才是策略中的重新发送回mq消息队列中的正确做法
       // 而在ErrorHandler中,发生异常只能作为记录信息存在,不能依靠其业务进行重新发送,原因如下:
       // 默认都会有重试,假定重试三次,三次都出异常,那么ErrorHandler中的业务程序将会运行三次 --- 雪崩
       @Bean
       public RetryTemplate retryTemplate() {
           RetryTemplate retryTemplate = new RetryTemplate();
           SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
           simpleRetryPolicy.setMaxAttempts(1);
           // retry Policy 指数退避策略,必须使用指数,内网连接很快
           ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
           exponentialBackOffPolicy.setInitialInterval(500);
           exponentialBackOffPolicy.setMultiplier(10.0d);
           exponentialBackOffPolicy.setMaxInterval(10000);
           retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);
           retryTemplate.setRetryPolicy(simpleRetryPolicy);
           return retryTemplate;
       }
   
       @Bean
       public StatelessRetryOperationsInterceptorFactoryBean statelessRetryOperationsInterceptorFactoryBean(
               MQRepublishMessageRecoverer messageRecoverer) {
           StatelessRetryOperationsInterceptorFactoryBean interceptorFactoryBean =
                   new StatelessRetryOperationsInterceptorFactoryBean();
           interceptorFactoryBean.setMessageRecoverer(messageRecoverer);
           interceptorFactoryBean.setRetryOperations(retryTemplate());
           return interceptorFactoryBean;
       }
   
   
       @Bean()
       public RabbitTemplate rabbitTemplate() {
           RabbitTemplate rabbitTemplate = new RabbitTemplate();
           rabbitTemplate.setConnectionFactory(connectionFactory());
           return rabbitTemplate;
       }
   
       @Bean
       public SimpleMessageConverter simpleMessageConverter() {
           return new SimpleMessageConverter();
       }
   
   
   }
   
   ```

3. 定义exchange&queue

   ```java
   package com.soul.home.lws.system.mq;
   
   import org.springframework.amqp.core.CustomExchange;
   import org.springframework.amqp.core.DirectExchange;
   import org.springframework.amqp.core.Queue;
   import org.springframework.amqp.core.QueueBuilder;
   import org.springframework.context.annotation.Bean;
   import org.springframework.context.annotation.Configuration;
   
   import java.util.HashMap;
   import java.util.Map;
   
   /**
    * durable属性和auto-delete属性可以同时生效;
    * durable属性和exclusive属性会有性质上的冲突,两者同时设置时,仅exclusive属性生效;
    * auto_delete属性和exclusive属性可以同时生效;
    * 除此之外,
    * Queue的“Exlusive owner”对应的是connection而不是channel;
    * Consumer存在于某个channel上的;
    * @author gaoguofan
    * @date 2020/6/15
    */
   @Configuration
   public class MQMessageQueuesConfig {
   
       private Boolean exclusive = false;
       private Boolean autoDelete = false;
       private Boolean durable = true;
   
   
       @Bean(MQMessageQueueNames.DELAY_EXCHANGE_NAME)
       public DirectExchange directExchange() {
           DirectExchange directExchange = new DirectExchange(MQMessageQueueNames.DELAY_EXCHANGE_NAME, true, false);
           return directExchange;
       }
   
       @Bean(MQMessageQueueNames.DEAD_LETTER_EXCHANGE)
       public DirectExchange deadLetterExchange() {
           DirectExchange directExchange = new DirectExchange(MQMessageQueueNames.DEAD_LETTER_EXCHANGE, true, false);
           return directExchange;
       }
   
   
       @Bean(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE)
       public CustomExchange customExchange() {
           Map<String, Object> args = new HashMap<>();
           args.put("x-delayed-type", "direct");
           return new CustomExchange(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE, "x-delayed-message", true, false, args);
       }
   
   
       /**
        * 声明延时队列C 不设置TTL
        * 并绑定到对应的死信交换机
        * @return
        */
       @Bean("delayQueueC")
       public Queue delayQueueC(){
           Map<String, Object> args = new HashMap<>(3);
           // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
           args.put("x-dead-letter-exchange", MQMessageQueueNames.DEAD_LETTER_EXCHANGE);
           // x-dead-letter-routing-key  这里声明当前队列的死信路由key
           args.put("x-dead-letter-routing-key", MQMessageQueueNames.DEAD_LETTER_QUEUEC_ROUTING_KEY);
           return QueueBuilder.durable(MQMessageQueueNames.DELAY_QUEUEC_NAME).withArguments(args).build();
       }
   
   
       /**
        * 声明死信队列C 用于接收延时任意时长处理的消息
        * @return
        */
       @Bean("deadLetterQueueC")
       public Queue deadLetterQueueC(){
           return new Queue(MQMessageQueueNames.DEAD_LETTER_QUEUEC_NAME);
       }
   
   
       @Bean(MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME)
       public Queue autoDelayMQPlugs() {
           return new Queue(MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME);
       }
   
   
   }
   
   ```

4. 进行绑定

   ```java
   package com.soul.home.lws.system.mq;
   
   import org.springframework.amqp.core.*;
   import org.springframework.beans.factory.annotation.Qualifier;
   import org.springframework.context.annotation.Bean;
   import org.springframework.context.annotation.Configuration;
   
   /**
    * @author gaoguofan
    * @date 2020/6/15
    */
   @Configuration
   public class MQMessageQueueKeyBindConfg {
   
   
       /**
        * 声明延时列C绑定关系
        * @param queue
        * @param exchange
        * @return
        */
       @Bean
       public Binding delayBindingC(@Qualifier("delayQueueC") Queue queue,
                                    @Qualifier(MQMessageQueueNames.DELAY_EXCHANGE_NAME) DirectExchange exchange){
           return BindingBuilder.bind(queue).to(exchange).with(MQMessageQueueNames.DELAY_QUEUEC_ROUTING_KEY);
       }
   
       /**
        * 声明死信队列C绑定关系
        * @param queue
        * @param exchange
        * @return
        */
       @Bean
       public Binding deadLetterBindingC(@Qualifier("deadLetterQueueC") Queue queue,
                                         @Qualifier(MQMessageQueueNames.DEAD_LETTER_EXCHANGE) DirectExchange exchange){
           return BindingBuilder.bind(queue).to(exchange).with(MQMessageQueueNames.DEAD_LETTER_QUEUEC_ROUTING_KEY);
       }
   
   
       /**
        *  插件延时队列绑定关系
        * @param queue
        * @param customExchange
        * @return
        */
       @Bean
       public Binding bindingNotify(@Qualifier(MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME) Queue queue,
                                    @Qualifier(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE) CustomExchange customExchange) {
           return BindingBuilder.bind(queue).to(customExchange).with(MQMessageQueueNames.DELAYED_PLUGS_ROUTING_KEY).noargs();
       }
   
   
   }
   
   ```

5. 定义生产者

   ```java
   package com.soul.home.lws.system.controler;
   
   import com.rabbitmq.client.AMQP;
   import com.soul.home.lws.route.conf.Api;
   import com.soul.home.lws.sql.dto.BaseDto;
   import com.soul.home.lws.system.mq.ExpirationMessagePostProcessor;
   import com.soul.home.lws.system.mq.MQMessageQueueNames;
   import com.soul.home.lws.system.mq.MQMessageQueuesConfig;
   import org.springframework.amqp.rabbit.core.RabbitTemplate;
   import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.web.bind.annotation.GetMapping;
   import org.springframework.web.bind.annotation.PostMapping;
   import org.springframework.web.bind.annotation.RestController;
   
   import java.util.HashMap;
   import java.util.Map;
   
   /**
    * @author gaoguofan
    * @date 2020/6/15
    */
   @RestController
   public class RabbitMessageController {
   
       @Autowired
       RabbitTemplate rabbitTemplate;
   
       @GetMapping(value = "ts")
       public BaseDto sendMsg(Integer ttl) {
           rabbitTemplate.convertAndSend(MQMessageQueueNames.DELAY_EXCHANGE_NAME,
                   MQMessageQueueNames.DELAY_QUEUEC_ROUTING_KEY, "HELLO" + ttl,
                   new ExpirationMessagePostProcessor(ttl, null));
           return new BaseDto(0, null);
       }
   
   
       @GetMapping(value = "ts2")
       public BaseDto sendMsg2(Integer ttl) {
           Map<String, Object> headers = new HashMap<>();
           headers.put("x-delay", ttl);
           AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder().headers(headers);
           rabbitTemplate.convertAndSend(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE,
                   MQMessageQueueNames.DELAYED_PLUGS_ROUTING_KEY, "HELLO" + ttl,
                   new ExpirationMessagePostProcessor(ttl, headers));
           return new BaseDto(0, null);
       }
   
   
   }
   
   ```

6. 定义消费者

   ```java
   package com.soul.home.lws.system.mq;
   
   import com.soul.home.lws.system.mq.listener.MemberChargeNoticeListener;
   import org.springframework.amqp.core.AcknowledgeMode;
   import org.springframework.amqp.rabbit.annotation.RabbitListener;
   import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
   import org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean;
   import org.springframework.amqp.rabbit.connection.ConnectionFactory;
   import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
   import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
   import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.context.annotation.Bean;
   import org.springframework.context.annotation.Configuration;
   import org.springframework.messaging.handler.annotation.Headers;
   import org.springframework.messaging.handler.annotation.Payload;
   
   import java.util.Map;
   
   /**
    * @author gaoguofan
    * @date 2020/6/15
    */
   @Configuration
   public class MQMessageListenerContainerConfig {
   
       @Autowired
       ConnectionFactory connectionFactory;
       @Autowired
       MQErrorHandler mqErrorHandler;
       @Autowired
       StatelessRetryOperationsInterceptorFactoryBean retryOperationsInterceptorFactoryBean;
   
       @Autowired
       MemberChargeNoticeListener memberChargeNoticeListener;
   
   
       @Bean
       public SimpleMessageListenerContainer simpleMessageListenerContainer() {
           SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
           simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
           simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
   //        simpleMessageListenerContainer.setQueueNames(MQMessageQueueNames.DEAD_LETTER_QUEUEC_NAME);
           simpleMessageListenerContainer.setQueueNames(MQMessageQueueNames.DEAD_LETTER_QUEUEC_NAME, MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME);
           simpleMessageListenerContainer.setMessageListener(memberChargeNoticeListener);
           simpleMessageListenerContainer.setConcurrentConsumers(5);
           simpleMessageListenerContainer.setMaxConcurrentConsumers(5);
           simpleMessageListenerContainer.setAdviceChain(retryOperationsInterceptorFactoryBean.getObject());
           simpleMessageListenerContainer.setErrorHandler(mqErrorHandler);
           return simpleMessageListenerContainer;
       }
   
   
   //    /**
   //     * 使用注解 @RabbitListener 自定义消费者情况部署
   //     * @EnableRabbit
   //     * @RabbitListener(queues = MQMessageQueueNames.NULL)
   //     * @param connectionFactory
   //     * @return
   //     */
   //    @Bean
   //    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
   //        //SimpleRabbitListenerContainerFactory发现消息中有content_type有text就会默认将其转换成string类型的
   //        SimpleRabbitListenerContainerFactory simpleMessageListenerContainer = new SimpleRabbitListenerContainerFactory();
   //        simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
   //        simpleMessageListenerContainer.setConcurrentConsumers(5);
   //        simpleMessageListenerContainer.setMaxConcurrentConsumers(5);
   //        simpleMessageListenerContainer.setAdviceChain(retryOperationsInterceptorFactoryBean.getObject());
   //        simpleMessageListenerContainer.setErrorHandler(mqErrorHandler);
   //        simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
   //        return simpleMessageListenerContainer;
   //    }
   
   
   //    /**
   //     * 队列调度器
   //     * @param body
   //     * @param headers
   //     */
   //    @RabbitListener(queues = MQMessageQueueNames.NULL)
   //    public void handleMessage(@Payload String body, @Headers Map<String,Object> headers){
   //
   //    }
   
   }
   
   ```

   ```java
   package com.soul.home.lws.system.mq.listener;
   
   import java.util.Map;
   
   import org.apache.log4j.Logger;
   import org.springframework.amqp.core.Message;
   import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
   import org.springframework.amqp.support.converter.MessageConversionException;
   import org.springframework.amqp.support.converter.MessageConverter;
   import org.springframework.amqp.support.converter.SimpleMessageConverter;
   import org.springframework.beans.factory.annotation.Autowired;
   
   import com.rabbitmq.client.Channel;
   import org.springframework.stereotype.Component;
   
   /**
    *
    */
   @Component
   public class MemberChargeNoticeListener implements ChannelAwareMessageListener {
   
       private long DELIVERIED_TAG = -1l;
   
       private static final Logger logger = Logger.getLogger(MemberChargeNoticeListener.class);
   
       @Autowired
       private SimpleMessageConverter msgConverter;
       
   
       @Override
       public void onMessage(Message message, Channel channel) throws Exception {
           Object obj = null;
           try {
               obj = msgConverter.fromMessage(message);
               logger.info(obj.toString());
           } catch (MessageConversionException e) {
   //            logger.error("convert MQ message error.", e);
           } finally {
               long deliveryTag = message.getMessageProperties().getDeliveryTag();
               if (deliveryTag != DELIVERIED_TAG) {
                   channel.basicAck(deliveryTag, false);
                   message.getMessageProperties().setDeliveryTag(DELIVERIED_TAG);
   //                logger.info("revice and ack msg: " + (obj == null ? message : new String((byte[]) obj)));
               }
           }
           if (obj == null) {
               return;
           }
           boolean flag = false;
           if (obj instanceof Map<?, ?>) {
               Map<?, ?> cpaMsg = (Map<?, ?>) obj;
   //            logger.info("cpaMsg : " + cpaMsg);
               String url = cpaMsg.get("url").toString();
               String storeId = cpaMsg.get("storeId").toString();
               String openId = cpaMsg.get("openId").toString();
               String storeName = cpaMsg.get("storeName").toString();
               String memberLevel = cpaMsg.get("memberLevel").toString();
               String chargeAmount = cpaMsg.get("chargeAmount").toString();
               String balanceAmount = cpaMsg.get("balanceAmount").toString();
               String chargeTime = cpaMsg.get("chargeTime").toString();
           } else {
   //            logger.warn("not a map msg, ingore it.");
           }
           if (!flag) {
   //            logger.error("hanler message " + obj + " failed, throw a exception, and it will be retried.");
               throw new RuntimeException("hanler message " + obj + " failed.");
           }
       }
   
   }
   
   ```

7. 辅助Class列表

   - 消息头信息设置

     ```java
     package com.soul.home.lws.system.mq;
     
     import org.springframework.amqp.AmqpException;
     import org.springframework.amqp.core.Message;
     import org.springframework.amqp.core.MessagePostProcessor;
     import org.springframework.amqp.core.MessageProperties;
     import org.springframework.amqp.support.Correlation;
     
     import java.util.Map;
     
     /**
      * @author gaoguofan
      * @date 2020/6/15
      */
     public class ExpirationMessagePostProcessor implements MessagePostProcessor {
     
         private final Integer ttl; // 毫秒
     
         private Map<String, Object> headers;
     
         public ExpirationMessagePostProcessor(Integer ttl, Map<String, Object> headers) {
             this.ttl = ttl;
             this.headers = headers;
         }
     
     
         @Override
         public Message postProcessMessage(Message message) throws AmqpException {
             MessageProperties messageProperties = message.getMessageProperties();
             if (headers != null) {
                 for (String key : headers.keySet()) {
                     messageProperties.getHeaders().put(key, headers.get(key));
                 }
             }
             message.getMessageProperties().setExpiration(ttl.toString());
             return message;
         }
     
     }
     ```

   - 异常信息复制记录

     ```java
     package com.soul.home.lws.system.mq;
     
     import java.lang.reflect.Field;
     
     import org.apache.commons.lang.reflect.FieldUtils;
     import org.apache.log4j.Logger;
     import org.springframework.amqp.core.Message;
     import org.springframework.amqp.support.converter.MessageConverter;
     import org.springframework.amqp.support.converter.SimpleMessageConverter;
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.stereotype.Component;
     import org.springframework.util.ErrorHandler;
     
     @Component
     public class MQErrorHandler implements ErrorHandler {
     
         private static final Logger logger = Logger.getLogger(MQErrorHandler.class);
     
         //    @Autowired
     //    private RedisService redisService;
         @Autowired
         private SimpleMessageConverter msgConverter;
     
         @Override
         public void handleError(Throwable cause) {
             Field mqMsgField = FieldUtils.getField(MQListenerExecutionFailedException.class, "mqMsg", true);
             if (mqMsgField != null) {
                 try {
                     Message mqMsg = (Message) mqMsgField.get(cause);
                     Object msgObj = msgConverter.fromMessage(mqMsg);
                     logger.info(Thread.currentThread().getName() + "-" + "handle MQ msg: " + msgObj + " failed, record it to mq.", cause);
     //                redisService.zadd(App.MsgErr.MQ_MSG_ERR_RECORD_KEY, new Double(new Date().getTime()), msgObj.toString());
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             } else {
                 logger.error("An error occurred.", cause);
             }
         }
     
     }
     
     ```

   - Exception封装

     ```java
     package com.soul.home.lws.system.mq;
     
     import org.springframework.amqp.core.Message;
     import org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException;
     
     /**
      *
      */
     public class MQListenerExecutionFailedException extends ListenerExecutionFailedException {
     
         private static final long serialVersionUID = 1L;
     
         private Message mqMsg;
     
         public MQListenerExecutionFailedException(String msg, Throwable cause, Message mqMsg) {
             super(msg, cause, mqMsg);
         }
     
         public MQListenerExecutionFailedException(String msg, Message mqMsg, Throwable cause) {
             this(msg, cause, mqMsg);
             this.mqMsg = mqMsg;
         }
     
         public Message getMqMsg() {
             return mqMsg;
         }
     
     }
     
     ```

   - 重试机制失败后重新路由,MessageRecorve

     ```java
     package com.soul.home.lws.system.mq;
     
     import java.io.PrintWriter;
     import java.io.StringWriter;
     import java.util.Map;
     
     //import org.apache.log4j.Logger;
     //import org.springframework.amqp.rabbit.core.RabbitTemplate;
     //import org.springframework.amqp.support.converter.MessageConverter;
     //import org.springframework.beans.factory.annotation.Autowired;
     import org.apache.log4j.Logger;
     import org.springframework.amqp.rabbit.core.RabbitTemplate;
     import org.springframework.amqp.rabbit.retry.MessageRecoverer;
     import org.springframework.amqp.core.Message;
     import org.springframework.amqp.support.converter.SimpleMessageConverter;
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.beans.factory.annotation.Qualifier;
     import org.springframework.stereotype.Component;
     
     @Component
     public class MQRepublishMessageRecoverer implements MessageRecoverer {
         
         private static final Logger logger = Logger.getLogger(MQRepublishMessageRecoverer.class);
     //
         @Autowired
         @Qualifier("rabbitTemplate")
         private RabbitTemplate rabbitTemplate;
     //    
         @Autowired
         private SimpleMessageConverter msgConverter;
     
         @Override
         public void recover(Message message, Throwable cause) {
             Map<String, Object> headers = message.getMessageProperties().getHeaders();
             headers.put("x-exception-stacktrace", getStackTraceAsString(cause));
             headers.put("x-exception-message", cause.getCause() != null ? cause.getCause().getMessage() : cause.getMessage());
             headers.put("x-original-exchange", message.getMessageProperties().getReceivedExchange());
             headers.put("x-original-routingKey", message.getMessageProperties().getReceivedRoutingKey());
     //        this.rabbitTemplate.send(message.getMessageProperties().getReceivedExchange(), message.getMessageProperties().getReceivedRoutingKey(), message);
     //        logger.error("handler msg (" + msgConverter.fromMessage(message) + ") err, republish to mq.", cause);
         }
     
         private String getStackTraceAsString(Throwable cause) {
             StringWriter stringWriter = new StringWriter();
             PrintWriter printWriter = new PrintWriter(stringWriter, true);
             cause.printStackTrace(printWriter);
             return stringWriter.getBuffer().toString();
         }
     }
     
     ```

   - 静态属性配置表

     ```java
     package com.soul.home.lws.system.mq;
     
     /**
      * @author gaoguofan
      * @date 2020/6/15
      */
     public interface MQMessageQueueNames {
     
         // NULL-DEFUALT 延时队列
         public static final String NULL = "NULL";
         // 延时队列交换机
         public static final String DELAY_EXCHANGE_NAME = "delay.queue.business.exchange";
         // 死信队列交换机
         public static final String DEAD_LETTER_EXCHANGE = "delay.queue.deadletter.exchange";
         // rabbitmq_delayed_message_exchange 插件交换机
         public static final String DELAYED_PLUGS_EXCHANGE = "delay.queue.rabbit.plugs.delayed.exchange";
     
         // 延时队列队列名称
         public static final String DELAY_QUEUEC_NAME = "delay.queue.business.queue";
         // 延迟队列路由Key
         public static final String DELAY_QUEUEC_ROUTING_KEY = "delay.queue.demo.business.queue.routingkey";
     
         // rabbitmq_delayed_message_exchange 队列名称
         public static final String DELAYED_PLUGS_QUEUEC_NAME = "delay.queue.rabbit.plugs.delayed.queue";
         // rabbitmq_delayed_message_exchange 队列路由Key
         public static final String DELAYED_PLUGS_ROUTING_KEY = "delay.queue.rabbit.plugs.delayed.routingkey";
     
         // 死信队列队列名称
         public static final String DEAD_LETTER_QUEUEC_NAME = "delay.queue.deadletter.queue";
         // 死信队列路由Key
         public static final String DEAD_LETTER_QUEUEC_ROUTING_KEY = "delay.queue.deadletter.delay_anytime.routingkey";
     
     }
     
     ```

     

## Spring搭建延时队列-xml方式

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:rabbit="http://www.springframework.org/schema/rabbit"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/rabbit
    http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <!-- 连接服务配置 -->
    <rabbit:connection-factory id="connectionFactory"
        host="${rabbitmq.host}" port="${rabbitmq.port}" username="${rabbitmq.username}"
        password="${rabbitmq.password}" channel-cache-size="${rabbitmq.channel.cache.size}" />
        
    <bean id="ackManual"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
        <property name="staticField"
            value="org.springframework.amqp.core.AcknowledgeMode.MANUAL" />
    </bean>

    <bean id="mqErrorHandler" class="com.zefun.wechat.utils.MQErrorHandler"/>
    <bean id="msgConverter" class="org.springframework.amqp.support.converter.SimpleMessageConverter" />

    <!-- 创建rabbitAdmin 代理类 -->
    <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
    <rabbit:admin connection-factory="connectionFactory" />
    
    <bean id="retryOperationsInterceptorFactoryBean"
        class="org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean">
        <property name="messageRecoverer">
            <bean class="com.zefun.wechat.utils.MQRepublishMessageRecoverer"/>
        </property>
        <property name="retryOperations">
            <bean class="org.springframework.retry.support.RetryTemplate">
                <property name="backOffPolicy">
                    <bean
                        class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
                        <property name="initialInterval" value="500" />
                        <property name="multiplier" value="10.0" />
                        <property name="maxInterval" value="10000" />
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
        
    <rabbit:queue id="queue_member_charge_notice" name="${rabbitmq.wechat.template.notice.member.charge}" durable="true"
        auto-delete="false" exclusive="false" />    
                                 
    <!--路由设置 将队列绑定,属于direct类型 -->
            <rabbit:binding queue="queue_member_charge_notice" key="${rabbitmq.wechat.template.notice.member.charge}" />
    </rabbit:direct-exchange>
    
    <!-- 处理会员充值通知队列 -->
    <bean class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="acknowledgeMode" ref="ackManual" />
        <property name="queueNames" value="${rabbitmq.wechat.template.notice.member.charge}" />
        <property name="messageListener">
            <bean class="com.zefun.wechat.listener.MemberChargeNoticeListener" />
        </property>
        <property name="concurrentConsumers" value="${rabbitmq.concurrentConsumers}" />
        <property name="adviceChain" ref="retryOperationsInterceptorFactoryBean" />
        <property name="errorHandler" ref="mqErrorHandler" />
    </bean>
     
</beans>
```

## 实现区别

上述延迟队列中,我们使用了两种方式,分别是插件方式与原生方式。

原生方式中缺点:较短过期时间后插入,必须等待先前插入的较长过期时间job被消费。

插件方式缺点:有时候在网页中,无法看到存放好的队列数据信息。另外,在插件中的最大过期时间49天。

 
  
  
  
  

安装脚本步骤:

  1. 卸载erlang

    service rabbitmq-server stop  --停止服务
    yum list | grep erlang        --查询当前环境  
    yum -y remove erlang-*        --卸载 
    yum remove erlang.x86_64      --移除
  2. 卸载rabbitMq

    yum list | grep rabbitmq                --当前是否安装
    yum -y remove rabbitmq-server.noarch    --移除安装文件
  3. 安装erlang

    yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel
    wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
    rpm -Uvh erlang-solutions-1.0-1.noarch.rpm
    yum -y install epel-release
    sudo yum install erlang
    [root@nginx-1 mq]# yum info erlang           --校验版本信息
    Loaded plugins: fastestmirror
    Loading mirror speeds from cached hostfile
    Installed Packages
    Name        : erlang
    Arch        : x86_64
    Version     : 23.0.2
    Release     : 2.el7
    Size        : 0.0  
    Repo        : installed
    From repo   : erlang-solutions
    Summary     : General-purpose programming language and runtime environment
    URL         : http://www.erlang.org
    License     : ERPL
    Description : Erlang is a general-purpose programming language and runtime
                : environment. Erlang has built-in support for concurrency, distribution
                : and fault tolerance. Erlang is used in several large telecommunication
                : systems from Ericsson.
  4. 安装rabbitMq

    wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.0/rabbitmq-server-3.8.0-1.el7.noarch.rpm
    yum -y install socat    --安装socat依赖
    rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc  --导入什么签名
    rpm -ivh rabbitmq-server-3.8.0-1.el7.noarch.rpm
  5. 部署插件

    rabbitmq-delayed-message-exchange        --插件名称
    https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/tag/v3.8.0  --下载地址,选择3.8

    RabbitMQ的有些插件没有集成在初始的安装中,它们需要额外安装,这些文件的后缀为.ez,安装时需要将.ez文件拷贝到安装的插件目录。以下是不同系统中默认安装的插件目录路径:

     插件目录
    Linux/usr/lib/rabbitmq/lib/rabbitmq_server-${version}/plugins
    WindowsC:\Program Files\RabbitMQ\rabbitmq_server-version\plugins(安装rabbitmq的目录)
    Homebrew/usr/local/Cellar/rabbitmq/version/plugins
    rabbitmq-plugins enable rabbitmq_delayed_message_exchange   --启用插件
    rabbitmq-plugins disable rabbitmq_delayed_message_exchange  --弃用插件
  6. 授权用户访问

    rabbitmq-plugins enable rabbitmq_management  --启用网页插件
    添加用户:rabbitmqctl add_user admin admin
    添加权限:rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
    修改用户角色rabbitmqctl set_user_tags admin administrator
    然后就可以远程访问了,然后可直接配置用户权限等信息。
  7. 启动命令脚本

    service rabbitmq-server restart  --重启
    service rabbitmq-server stop     --停止  

     

Spring搭建延时队列-注解方式

此处应用于Spring、SpringBoot等开源框架

  1. 添加依赖

    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
  2. 定义工厂链接信息

    package com.soul.home.lws.system.conf;
    ​
    import com.soul.home.lws.conf.properties.AutoConfigRabbitMqProperties;
    import com.soul.home.lws.conf.properties.AutoConfigTomcatProperties;
    import com.soul.home.lws.system.mq.MQRepublishMessageRecoverer;
    import org.springframework.amqp.AmqpException;
    import org.springframework.amqp.core.AcknowledgeMode;
    import org.springframework.amqp.core.DirectExchange;
    import org.springframework.amqp.rabbit.annotation.Queue;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean;
    import org.springframework.amqp.rabbit.connection.*;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
    import org.springframework.amqp.support.converter.SimpleMessageConverter;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.retry.RetryContext;
    import org.springframework.retry.backoff.BackOffContext;
    import org.springframework.retry.backoff.BackOffInterruptedException;
    import org.springframework.retry.backoff.BackOffPolicy;
    import org.springframework.retry.backoff.ExponentialBackOffPolicy;
    import org.springframework.retry.policy.SimpleRetryPolicy;
    import org.springframework.retry.support.RetryTemplate;
    ​
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    ​
    /**
     * @author gaoguofan
     * @date 2020/6/15
     */
    @Configuration
    @EnableConfigurationProperties(AutoConfigRabbitMqProperties.class)
    public class AutoConfigurationRabbitMqConfig {
    ​
        @Autowired
        AutoConfigRabbitMqProperties autoConfigRabbitMqProperties;
    ​
        @Bean
        public ConnectionFactory connectionFactory() {
            com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = new com.rabbitmq.client.ConnectionFactory();
            rabbitConnectionFactory.setHost(autoConfigRabbitMqProperties.getHost());
            rabbitConnectionFactory.setPort(autoConfigRabbitMqProperties.getPort());
            rabbitConnectionFactory.setUsername(autoConfigRabbitMqProperties.getUserName());
            rabbitConnectionFactory.setPassword(autoConfigRabbitMqProperties.getPassword());
            rabbitConnectionFactory.setRequestedChannelMax(autoConfigRabbitMqProperties.getRequestedChannelMax());
            ConnectionFactory connectionFactory = new AbstractConnectionFactory(rabbitConnectionFactory) {
                @Override
                public Connection createConnection() throws AmqpException {
                    try {
                        return new SimpleConnection(rabbitConnectionFactory.newConnection(), 100000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return null;
                }
            };
            return connectionFactory;
        }
    ​
        // 消费者消费消息,首先进入重试逻辑中,如果有异常则根据重试机制重试
        // 若超过重试机制,则执行下面StatelessRetryOperationsInterceptorFactoryBean中的messageRecoverer类方法
        // 所以,手动Ack的情况下,此处才是策略中的重新发送回mq消息队列中的正确做法
        // 而在ErrorHandler中,发生异常只能作为记录信息存在,不能依靠其业务进行重新发送,原因如下:
        // 默认都会有重试,假定重试三次,三次都出异常,那么ErrorHandler中的业务程序将会运行三次 --- 雪崩
        @Bean
        public RetryTemplate retryTemplate() {
            RetryTemplate retryTemplate = new RetryTemplate();
            SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
            simpleRetryPolicy.setMaxAttempts(1);
            // retry Policy 指数退避策略,必须使用指数,内网连接很快
            ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
            exponentialBackOffPolicy.setInitialInterval(500);
            exponentialBackOffPolicy.setMultiplier(10.0d);
            exponentialBackOffPolicy.setMaxInterval(10000);
            retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);
            retryTemplate.setRetryPolicy(simpleRetryPolicy);
            return retryTemplate;
        }
    ​
        @Bean
        public StatelessRetryOperationsInterceptorFactoryBean statelessRetryOperationsInterceptorFactoryBean(
                MQRepublishMessageRecoverer messageRecoverer) {
            StatelessRetryOperationsInterceptorFactoryBean interceptorFactoryBean =
                    new StatelessRetryOperationsInterceptorFactoryBean();
            interceptorFactoryBean.setMessageRecoverer(messageRecoverer);
            interceptorFactoryBean.setRetryOperations(retryTemplate());
            return interceptorFactoryBean;
        }
    ​
    ​
        @Bean()
        public RabbitTemplate rabbitTemplate() {
            RabbitTemplate rabbitTemplate = new RabbitTemplate();
            rabbitTemplate.setConnectionFactory(connectionFactory());
            return rabbitTemplate;
        }
    ​
        @Bean
        public SimpleMessageConverter simpleMessageConverter() {
            return new SimpleMessageConverter();
        }
    ​
    ​
    }
    ​
  3. 定义exchange&queue

    package com.soul.home.lws.system.mq;
    ​
    import org.springframework.amqp.core.CustomExchange;
    import org.springframework.amqp.core.DirectExchange;
    import org.springframework.amqp.core.Queue;
    import org.springframework.amqp.core.QueueBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    ​
    import java.util.HashMap;
    import java.util.Map;
    ​
    /**
     * durable属性和auto-delete属性可以同时生效;
     * durable属性和exclusive属性会有性质上的冲突,两者同时设置时,仅exclusive属性生效;
     * auto_delete属性和exclusive属性可以同时生效;
     * 除此之外,
     * Queue的“Exlusive owner”对应的是connection而不是channel;
     * Consumer存在于某个channel上的;
     * @author gaoguofan
     * @date 2020/6/15
     */
    @Configuration
    public class MQMessageQueuesConfig {
    ​
        private Boolean exclusive = false;
        private Boolean autoDelete = false;
        private Boolean durable = true;
    ​
    ​
        @Bean(MQMessageQueueNames.DELAY_EXCHANGE_NAME)
        public DirectExchange directExchange() {
            DirectExchange directExchange = new DirectExchange(MQMessageQueueNames.DELAY_EXCHANGE_NAME, true, false);
            return directExchange;
        }
    ​
        @Bean(MQMessageQueueNames.DEAD_LETTER_EXCHANGE)
        public DirectExchange deadLetterExchange() {
            DirectExchange directExchange = new DirectExchange(MQMessageQueueNames.DEAD_LETTER_EXCHANGE, true, false);
            return directExchange;
        }
    ​
    ​
        @Bean(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE)
        public CustomExchange customExchange() {
            Map<String, Object> args = new HashMap<>();
            args.put("x-delayed-type", "direct");
            return new CustomExchange(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE, "x-delayed-message", true, false, args);
        }
    ​
    ​
        /**
         * 声明延时队列C 不设置TTL
         * 并绑定到对应的死信交换机
         * @return
         */
        @Bean("delayQueueC")
        public Queue delayQueueC(){
            Map<String, Object> args = new HashMap<>(3);
            // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
            args.put("x-dead-letter-exchange", MQMessageQueueNames.DEAD_LETTER_EXCHANGE);
            // x-dead-letter-routing-key  这里声明当前队列的死信路由key
            args.put("x-dead-letter-routing-key", MQMessageQueueNames.DEAD_LETTER_QUEUEC_ROUTING_KEY);
            return QueueBuilder.durable(MQMessageQueueNames.DELAY_QUEUEC_NAME).withArguments(args).build();
        }
    ​
    ​
        /**
         * 声明死信队列C 用于接收延时任意时长处理的消息
         * @return
         */
        @Bean("deadLetterQueueC")
        public Queue deadLetterQueueC(){
            return new Queue(MQMessageQueueNames.DEAD_LETTER_QUEUEC_NAME);
        }
    ​
    ​
        @Bean(MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME)
        public Queue autoDelayMQPlugs() {
            return new Queue(MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME);
        }
    ​
    ​
    }
    ​
  4. 进行绑定

    package com.soul.home.lws.system.mq;
    ​
    import org.springframework.amqp.core.*;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    ​
    /**
     * @author gaoguofan
     * @date 2020/6/15
     */
    @Configuration
    public class MQMessageQueueKeyBindConfg {
    ​
    ​
        /**
         * 声明延时列C绑定关系
         * @param queue
         * @param exchange
         * @return
         */
        @Bean
        public Binding delayBindingC(@Qualifier("delayQueueC") Queue queue,
                                     @Qualifier(MQMessageQueueNames.DELAY_EXCHANGE_NAME) DirectExchange exchange){
            return BindingBuilder.bind(queue).to(exchange).with(MQMessageQueueNames.DELAY_QUEUEC_ROUTING_KEY);
        }
    ​
        /**
         * 声明死信队列C绑定关系
         * @param queue
         * @param exchange
         * @return
         */
        @Bean
        public Binding deadLetterBindingC(@Qualifier("deadLetterQueueC") Queue queue,
                                          @Qualifier(MQMessageQueueNames.DEAD_LETTER_EXCHANGE) DirectExchange exchange){
            return BindingBuilder.bind(queue).to(exchange).with(MQMessageQueueNames.DEAD_LETTER_QUEUEC_ROUTING_KEY);
        }
    ​
    ​
        /**
         *  插件延时队列绑定关系
         * @param queue
         * @param customExchange
         * @return
         */
        @Bean
        public Binding bindingNotify(@Qualifier(MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME) Queue queue,
                                     @Qualifier(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE) CustomExchange customExchange) {
            return BindingBuilder.bind(queue).to(customExchange).with(MQMessageQueueNames.DELAYED_PLUGS_ROUTING_KEY).noargs();
        }
    ​
    ​
    }
    ​
  5. 定义生产者

    package com.soul.home.lws.system.controler;
    ​
    import com.rabbitmq.client.AMQP;
    import com.soul.home.lws.route.conf.Api;
    import com.soul.home.lws.sql.dto.BaseDto;
    import com.soul.home.lws.system.mq.ExpirationMessagePostProcessor;
    import com.soul.home.lws.system.mq.MQMessageQueueNames;
    import com.soul.home.lws.system.mq.MQMessageQueuesConfig;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    ​
    import java.util.HashMap;
    import java.util.Map;
    ​
    /**
     * @author gaoguofan
     * @date 2020/6/15
     */
    @RestController
    public class RabbitMessageController {
    ​
        @Autowired
        RabbitTemplate rabbitTemplate;
    ​
        @GetMapping(value = "ts")
        public BaseDto sendMsg(Integer ttl) {
            rabbitTemplate.convertAndSend(MQMessageQueueNames.DELAY_EXCHANGE_NAME,
                    MQMessageQueueNames.DELAY_QUEUEC_ROUTING_KEY, "HELLO" + ttl,
                    new ExpirationMessagePostProcessor(ttl, null));
            return new BaseDto(0, null);
        }
    ​
    ​
        @GetMapping(value = "ts2")
        public BaseDto sendMsg2(Integer ttl) {
            Map<String, Object> headers = new HashMap<>();
            headers.put("x-delay", ttl);
            AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder().headers(headers);
            rabbitTemplate.convertAndSend(MQMessageQueueNames.DELAYED_PLUGS_EXCHANGE,
                    MQMessageQueueNames.DELAYED_PLUGS_ROUTING_KEY, "HELLO" + ttl,
                    new ExpirationMessagePostProcessor(ttl, headers));
            return new BaseDto(0, null);
        }
    ​
    ​
    }
    ​
  6. 定义消费者

    package com.soul.home.lws.system.mq;
    ​
    import com.soul.home.lws.system.mq.listener.MemberChargeNoticeListener;
    import org.springframework.amqp.core.AcknowledgeMode;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
    import org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean;
    import org.springframework.amqp.rabbit.connection.ConnectionFactory;
    import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
    import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.messaging.handler.annotation.Headers;
    import org.springframework.messaging.handler.annotation.Payload;
    ​
    import java.util.Map;
    ​
    /**
     * @author gaoguofan
     * @date 2020/6/15
     */
    @Configuration
    public class MQMessageListenerContainerConfig {
    ​
        @Autowired
        ConnectionFactory connectionFactory;
        @Autowired
        MQErrorHandler mqErrorHandler;
        @Autowired
        StatelessRetryOperationsInterceptorFactoryBean retryOperationsInterceptorFactoryBean;
    ​
        @Autowired
        MemberChargeNoticeListener memberChargeNoticeListener;
    ​
    ​
        @Bean
        public SimpleMessageListenerContainer simpleMessageListenerContainer() {
            SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
            simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
            simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    //        simpleMessageListenerContainer.setQueueNames(MQMessageQueueNames.DEAD_LETTER_QUEUEC_NAME);
            simpleMessageListenerContainer.setQueueNames(MQMessageQueueNames.DEAD_LETTER_QUEUEC_NAME, MQMessageQueueNames.DELAYED_PLUGS_QUEUEC_NAME);
            simpleMessageListenerContainer.setMessageListener(memberChargeNoticeListener);
            simpleMessageListenerContainer.setConcurrentConsumers(5);
            simpleMessageListenerContainer.setMaxConcurrentConsumers(5);
            simpleMessageListenerContainer.setAdviceChain(retryOperationsInterceptorFactoryBean.getObject());
            simpleMessageListenerContainer.setErrorHandler(mqErrorHandler);
            return simpleMessageListenerContainer;
        }
    ​
    ​
    //    /**
    //     * 使用注解 @RabbitListener 自定义消费者情况部署
    //     * @EnableRabbit
    //     * @RabbitListener(queues = MQMessageQueueNames.NULL)
    //     * @param connectionFactory
    //     * @return
    //     */
    //    @Bean
    //    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
    //        //SimpleRabbitListenerContainerFactory发现消息中有content_type有text就会默认将其转换成string类型的
    //        SimpleRabbitListenerContainerFactory simpleMessageListenerContainer = new SimpleRabbitListenerContainerFactory();
    //        simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    //        simpleMessageListenerContainer.setConcurrentConsumers(5);
    //        simpleMessageListenerContainer.setMaxConcurrentConsumers(5);
    //        simpleMessageListenerContainer.setAdviceChain(retryOperationsInterceptorFactoryBean.getObject());
    //        simpleMessageListenerContainer.setErrorHandler(mqErrorHandler);
    //        simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
    //        return simpleMessageListenerContainer;
    //    }
    ​
    ​
    //    /**
    //     * 队列调度器
    //     * @param body
    //     * @param headers
    //     */
    //    @RabbitListener(queues = MQMessageQueueNames.NULL)
    //    public void handleMessage(@Payload String body, @Headers Map<String,Object> headers){
    //
    //    }
    ​
    }
    ​
    package com.soul.home.lws.system.mq.listener;
    ​
    import java.util.Map;
    ​
    import org.apache.log4j.Logger;
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
    import org.springframework.amqp.support.converter.MessageConversionException;
    import org.springframework.amqp.support.converter.MessageConverter;
    import org.springframework.amqp.support.converter.SimpleMessageConverter;
    import org.springframework.beans.factory.annotation.Autowired;
    ​
    import com.rabbitmq.client.Channel;
    import org.springframework.stereotype.Component;
    ​
    /**
     *
     */
    @Component
    public class MemberChargeNoticeListener implements ChannelAwareMessageListener {
    ​
        private long DELIVERIED_TAG = -1l;
    ​
        private static final Logger logger = Logger.getLogger(MemberChargeNoticeListener.class);
    ​
        @Autowired
        private SimpleMessageConverter msgConverter;
        
    ​
        @Override
        public void onMessage(Message message, Channel channel) throws Exception {
            Object obj = null;
            try {
                obj = msgConverter.fromMessage(message);
                logger.info(obj.toString());
            } catch (MessageConversionException e) {
    //            logger.error("convert MQ message error.", e);
            } finally {
                long deliveryTag = message.getMessageProperties().getDeliveryTag();
                if (deliveryTag != DELIVERIED_TAG) {
                    channel.basicAck(deliveryTag, false);
                    message.getMessageProperties().setDeliveryTag(DELIVERIED_TAG);
    //                logger.info("revice and ack msg: " + (obj == null ? message : new String((byte[]) obj)));
                }
            }
            if (obj == null) {
                return;
            }
            boolean flag = false;
            if (obj instanceof Map<?, ?>) {
                Map<?, ?> cpaMsg = (Map<?, ?>) obj;
    //          logger.info("cpaMsg : " + cpaMsg);
                String url = cpaMsg.get("url").toString();
                String storeId = cpaMsg.get("storeId").toString();
                String openId = cpaMsg.get("openId").toString();
                String storeName = cpaMsg.get("storeName").toString();
                String memberLevel = cpaMsg.get("memberLevel").toString();
                String chargeAmount = cpaMsg.get("chargeAmount").toString();
                String balanceAmount = cpaMsg.get("balanceAmount").toString();
                String chargeTime = cpaMsg.get("chargeTime").toString();
            } else {
    //          logger.warn("not a map msg, ingore it.");
            }
            if (!flag) {
    //          logger.error("hanler message " + obj + " failed, throw a exception, and it will be retried.");
                throw new RuntimeException("hanler message " + obj + " failed.");
            }
        }
    ​
    }
    ​
  7. 辅助Class列表

    • 消息头信息设置

      package com.soul.home.lws.system.mq;
      ​
      import org.springframework.amqp.AmqpException;
      import org.springframework.amqp.core.Message;
      import org.springframework.amqp.core.MessagePostProcessor;
      import org.springframework.amqp.core.MessageProperties;
      import org.springframework.amqp.support.Correlation;
      ​
      import java.util.Map;
      ​
      /**
       * @author gaoguofan
       * @date 2020/6/15
       */
      public class ExpirationMessagePostProcessor implements MessagePostProcessor {
      ​
          private final Integer ttl; // 毫秒
      ​
          private Map<String, Object> headers;
      ​
          public ExpirationMessagePostProcessor(Integer ttl, Map<String, Object> headers) {
              this.ttl = ttl;
              this.headers = headers;
          }
      ​
      ​
          @Override
          public Message postProcessMessage(Message message) throws AmqpException {
              MessageProperties messageProperties = message.getMessageProperties();
              if (headers != null) {
                  for (String key : headers.keySet()) {
                      messageProperties.getHeaders().put(key, headers.get(key));
                  }
              }
              message.getMessageProperties().setExpiration(ttl.toString());
              return message;
          }
      ​
      }
    • 异常信息复制记录

      package com.soul.home.lws.system.mq;
      ​
      import java.lang.reflect.Field;
      ​
      import org.apache.commons.lang.reflect.FieldUtils;
      import org.apache.log4j.Logger;
      import org.springframework.amqp.core.Message;
      import org.springframework.amqp.support.converter.MessageConverter;
      import org.springframework.amqp.support.converter.SimpleMessageConverter;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Component;
      import org.springframework.util.ErrorHandler;
      ​
      @Component
      public class MQErrorHandler implements ErrorHandler {
      ​
          private static final Logger logger = Logger.getLogger(MQErrorHandler.class);
      ​
          //    @Autowired
      //    private RedisService redisService;
          @Autowired
          private SimpleMessageConverter msgConverter;
      ​
          @Override
          public void handleError(Throwable cause) {
              Field mqMsgField = FieldUtils.getField(MQListenerExecutionFailedException.class, "mqMsg", true);
              if (mqMsgField != null) {
                  try {
                      Message mqMsg = (Message) mqMsgField.get(cause);
                      Object msgObj = msgConverter.fromMessage(mqMsg);
                      logger.info(Thread.currentThread().getName() + "-" + "handle MQ msg: " + msgObj + " failed, record it to mq.", cause);
      //                redisService.zadd(App.MsgErr.MQ_MSG_ERR_RECORD_KEY, new Double(new Date().getTime()), msgObj.toString());
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              } else {
                  logger.error("An error occurred.", cause);
              }
          }
      ​
      }
      ​
    • Exception封装

      package com.soul.home.lws.system.mq;
      ​
      import org.springframework.amqp.core.Message;
      import org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException;
      ​
      /**
       *
       */
      public class MQListenerExecutionFailedException extends ListenerExecutionFailedException {
      ​
          private static final long serialVersionUID = 1L;
      ​
          private Message mqMsg;
      ​
          public MQListenerExecutionFailedException(String msg, Throwable cause, Message mqMsg) {
              super(msg, cause, mqMsg);
          }
      ​
          public MQListenerExecutionFailedException(String msg, Message mqMsg, Throwable cause) {
              this(msg, cause, mqMsg);
              this.mqMsg = mqMsg;
          }
      ​
          public Message getMqMsg() {
              return mqMsg;
          }
      ​
      }
      ​
    • 重试机制失败后重新路由,MessageRecorve

      package com.soul.home.lws.system.mq;
      ​
      import java.io.PrintWriter;
      import java.io.StringWriter;
      import java.util.Map;
      ​
      //import org.apache.log4j.Logger;
      //import org.springframework.amqp.rabbit.core.RabbitTemplate;
      //import org.springframework.amqp.support.converter.MessageConverter;
      //import org.springframework.beans.factory.annotation.Autowired;
      import org.apache.log4j.Logger;
      import org.springframework.amqp.rabbit.core.RabbitTemplate;
      import org.springframework.amqp.rabbit.retry.MessageRecoverer;
      import org.springframework.amqp.core.Message;
      import org.springframework.amqp.support.converter.SimpleMessageConverter;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.stereotype.Component;
      ​
      @Component
      public class MQRepublishMessageRecoverer implements MessageRecoverer {
          
          private static final Logger logger = Logger.getLogger(MQRepublishMessageRecoverer.class);
      //
          @Autowired
          @Qualifier("rabbitTemplate")
          private RabbitTemplate rabbitTemplate;
      //  
          @Autowired
          private SimpleMessageConverter msgConverter;
      ​
          @Override
          public void recover(Message message, Throwable cause) {
              Map<String, Object> headers = message.getMessageProperties().getHeaders();
              headers.put("x-exception-stacktrace", getStackTraceAsString(cause));
              headers.put("x-exception-message", cause.getCause() != null ? cause.getCause().getMessage() : cause.getMessage());
              headers.put("x-original-exchange", message.getMessageProperties().getReceivedExchange());
              headers.put("x-original-routingKey", message.getMessageProperties().getReceivedRoutingKey());
      //      this.rabbitTemplate.send(message.getMessageProperties().getReceivedExchange(), message.getMessageProperties().getReceivedRoutingKey(), message);
      //      logger.error("handler msg (" + msgConverter.fromMessage(message) + ") err, republish to mq.", cause);
          }
      ​
          private String getStackTraceAsString(Throwable cause) {
              StringWriter stringWriter = new StringWriter();
              PrintWriter printWriter = new PrintWriter(stringWriter, true);
              cause.printStackTrace(printWriter);
              return stringWriter.getBuffer().toString();
          }
      }
      ​
    • 静态属性配置表

      package com.soul.home.lws.system.mq;
      ​
      /**
       * @author gaoguofan
       * @date 2020/6/15
       */
      public interface MQMessageQueueNames {
      ​
          // NULL-DEFUALT 延时队列
          public static final String NULL = "NULL";
          // 延时队列交换机
          public static final String DELAY_EXCHANGE_NAME = "delay.queue.business.exchange";
          // 死信队列交换机
          public static final String DEAD_LETTER_EXCHANGE = "delay.queue.deadletter.exchange";
          // rabbitmq_delayed_message_exchange 插件交换机
          public static final String DELAYED_PLUGS_EXCHANGE = "delay.queue.rabbit.plugs.delayed.exchange";
      ​
          // 延时队列队列名称
          public static final String DELAY_QUEUEC_NAME = "delay.queue.business.queue";
          // 延迟队列路由Key
          public static final String DELAY_QUEUEC_ROUTING_KEY = "delay.queue.demo.business.queue.routingkey";
      ​
          // rabbitmq_delayed_message_exchange 队列名称
          public static final String DELAYED_PLUGS_QUEUEC_NAME = "delay.queue.rabbit.plugs.delayed.queue";
          // rabbitmq_delayed_message_exchange 队列路由Key
          public static final String DELAYED_PLUGS_ROUTING_KEY = "delay.queue.rabbit.plugs.delayed.routingkey";
      ​
          // 死信队列队列名称
          public static final String DEAD_LETTER_QUEUEC_NAME = "delay.queue.deadletter.queue";
          // 死信队列路由Key
          public static final String DEAD_LETTER_QUEUEC_ROUTING_KEY = "delay.queue.deadletter.delay_anytime.routingkey";
      ​
      }
      ​

       

Spring搭建延时队列-xml方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:rabbit="http://www.springframework.org/schema/rabbit"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/rabbit
    http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.1.xsd">
​
    <!-- 连接服务配置 -->
    <rabbit:connection-factory id="connectionFactory"
        host="${rabbitmq.host}" port="${rabbitmq.port}" username="${rabbitmq.username}"
        password="${rabbitmq.password}" channel-cache-size="${rabbitmq.channel.cache.size}" />
        
    <bean id="ackManual"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
        <property name="staticField"
            value="org.springframework.amqp.core.AcknowledgeMode.MANUAL" />
    </bean>
​
    <bean id="mqErrorHandler" class="com.zefun.wechat.utils.MQErrorHandler"/>
    <bean id="msgConverter" class="org.springframework.amqp.support.converter.SimpleMessageConverter" />
​
    <!-- 创建rabbitAdmin 代理类 -->
    <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
    <rabbit:admin connection-factory="connectionFactory" />
    
    <bean id="retryOperationsInterceptorFactoryBean"
        class="org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean">
        <property name="messageRecoverer">
            <bean class="com.zefun.wechat.utils.MQRepublishMessageRecoverer"/>
        </property>
        <property name="retryOperations">
            <bean class="org.springframework.retry.support.RetryTemplate">
                <property name="backOffPolicy">
                    <bean
                        class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
                        <property name="initialInterval" value="500" />
                        <property name="multiplier" value="10.0" />
                        <property name="maxInterval" value="10000" />
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
        
    <rabbit:queue id="queue_member_charge_notice" name="${rabbitmq.wechat.template.notice.member.charge}" durable="true"
        auto-delete="false" exclusive="false" />    
                                 
    <!--路由设置 将队列绑定,属于direct类型 -->
            <rabbit:binding queue="queue_member_charge_notice" key="${rabbitmq.wechat.template.notice.member.charge}" />
    </rabbit:direct-exchange>
    
    <!-- 处理会员充值通知队列 -->
    <bean class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="acknowledgeMode" ref="ackManual" />
        <property name="queueNames" value="${rabbitmq.wechat.template.notice.member.charge}" />
        <property name="messageListener">
            <bean class="com.zefun.wechat.listener.MemberChargeNoticeListener" />
        </property>
        <property name="concurrentConsumers" value="${rabbitmq.concurrentConsumers}" />
        <property name="adviceChain" ref="retryOperationsInterceptorFactoryBean" />
        <property name="errorHandler" ref="mqErrorHandler" />
    </bean>
     
</beans>

 

实现区别

上述延迟队列中,我们使用了两种方式,分别是插件方式与原生方式。

原生方式中缺点:较短过期时间后插入,必须等待先前插入的较长过期时间job被消费。

插件方式缺点:有时候在网页中,无法看到存放好的队列数据信息。另外,在插件中的最大过期时间49天。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值