RabbitMQ 安裝部署(New)& 延時隊列使用

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

    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
    Windows C:\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天。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章