RabbitMQ 安装部署(New)& 延时队列使用

RabbitMQ 安装部署(New)

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

版本信息
centos7.3.0
erlangVersion: 23.0.2,Release: 2.el7
RabbitMQ3.8.0-1
rabbitmq_delayed_message_exchange3.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
    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.system.mq.error.MQRepublishMessageRecoverer;
    import com.soul.home.lws.system.mq.callback.MessageConfirmCallback;
    import com.soul.home.lws.system.mq.callback.MessageReturnCallback;
    import org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean;
    import org.springframework.amqp.rabbit.connection.*;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    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.backoff.ExponentialBackOffPolicy;
    import org.springframework.retry.policy.SimpleRetryPolicy;
    import org.springframework.retry.support.RetryTemplate;
    
    /**
     * @author gaoguofan
     * @date 2020/6/15
     */
    @Configuration
    @EnableConfigurationProperties(AutoConfigRabbitMqProperties.class)
    public class AutoConfigurationRabbitMqConfig {
    
        @Autowired
        MessageConfirmCallback confirmCallback;
        @Autowired
        MessageReturnCallback returnCallback;
    
        @Autowired
        AutoConfigRabbitMqProperties rabbitMqProperties;
    
        @Bean
        public ConnectionFactory connectionFactory() {
            com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = new com.rabbitmq.client.ConnectionFactory();
            rabbitConnectionFactory.useNio();
            rabbitConnectionFactory.setAutomaticRecoveryEnabled(true);
            rabbitConnectionFactory.setNetworkRecoveryInterval(10000);
    //        rabbitConnectionFactory.setNioParams(new NioParams().setNbIoThreads(4));
            rabbitConnectionFactory.setHost(rabbitMqProperties.getHost());
            rabbitConnectionFactory.setPort(rabbitMqProperties.getPort());
            rabbitConnectionFactory.setUsername(rabbitMqProperties.getUserName());
            rabbitConnectionFactory.setPassword(rabbitMqProperties.getPassword());
    //        Clients can be configured to allow fewer channels per connection.
            rabbitConnectionFactory.setRequestedChannelMax(rabbitMqProperties.getRequestedChannelMax());
            CachingConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitConnectionFactory);
            // this params server for Ack Exchange & Ack Exchange -> Queue,it shoule be seted true
            connectionFactory.setPublisherConfirms(true);
    //        connectionFactory.setCacheMode(CachingConnectionFactory.CacheMode.CHANNEL);
    //        connectionFactory.setChannelCacheSize(rabbitMqProperties.getChannelCacheSize());
            connectionFactory.setConnectionCacheSize(rabbitMqProperties.getChannelCacheSize());
            connectionFactory.setCacheMode(CachingConnectionFactory.CacheMode.CONNECTION);
    //        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;
        }
    
        @Bean
        public RetryTemplate retryTemplate() {
            RetryTemplate retryTemplate = new RetryTemplate();
            SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
            simpleRetryPolicy.setMaxAttempts(rabbitMqProperties.getMaxAttempts());
            // retry Policy 指数退避策略,必须使用指数,内网连接很快
            ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
            exponentialBackOffPolicy.setInitialInterval(rabbitMqProperties.getInitialInterval());
            exponentialBackOffPolicy.setMultiplier(rabbitMqProperties.getMultiplier());
            exponentialBackOffPolicy.setMaxInterval(rabbitMqProperties.getMaxInterval());
            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;
        }
    
        /**
         * 业务回调不同,可配置scope进行单独注入
         * @param connectionFactory
         * @return
         */
        @Bean
        public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
            RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
            // Ack Exchange
            rabbitTemplate.setConfirmCallback(confirmCallback);
            // Ack Exchange -> queue
            rabbitTemplate.setMandatory(true);
            rabbitTemplate.setReturnCallback(returnCallback);
            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.listener;
    
    import com.soul.home.lws.conf.properties.AutoConfigRabbitMqProperties;
    import com.soul.home.lws.system.mq.error.MQErrorHandler;
    import com.soul.home.lws.system.mq.config.MQMessageQueueNames;
    import org.springframework.amqp.core.AcknowledgeMode;
    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.MessageListenerContainer;
    import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
    import org.springframework.amqp.rabbit.listener.RabbitListenerEndpoint;
    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;
    
    /**
     * 监听队列容器
     */
    @Configuration
    public class MQMessageListenerContainerConfig {
    
        @Autowired
        ConnectionFactory connectionFactory;
        @Autowired
        MQErrorHandler mqErrorHandler;
        @Autowired
        StatelessRetryOperationsInterceptorFactoryBean retryOperationsInterceptorFactoryBean;
        @Autowired
        AutoConfigRabbitMqProperties rabbitMqProperties;
        @Autowired
        MemberChargeNoticeListener memberChargeNoticeListener;
    
    
        /**
         *  消息预取数据计算:
         *  channel.basicQos() = simpleMessageListenerContainer.setPrefetchCount(250)
         *  预取值大小与性能成正比,与数据安全成反比;性能较好值=500,上线2500
         *  根据业务特定需求:设置预取值=1、Ack确认是单条确认,或者预取值=250但每次都进行批量确认或者单条确认即可;
         *  原因:未必能凑够合适大小进行统一批量Ack,导致宕机后消息重回Ready造成幂等性问题。
         *  多个消费客户端在消息确认的时候,使用批量确认,channel.basicAck(deliveryTag, true); 不会造成A客户端确认B的Tags
         *  原因:Ack确认是通过MQ服务端对ConsumerTag和MessageTag共同决定;
         *       同样setConcurrentConsumers=5的状态下,channel也=5,所以其他线程不会Ack掉本线程msg
         * @return
         */
        @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(rabbitMqProperties.getConcurrentConsumers());
            simpleMessageListenerContainer.setMaxConcurrentConsumers(rabbitMqProperties.getConcurrentConsumers());
            simpleMessageListenerContainer.setAdviceChain(retryOperationsInterceptorFactoryBean.getObject());
            simpleMessageListenerContainer.setErrorHandler(mqErrorHandler);
            simpleMessageListenerContainer.setPrefetchCount(rabbitMqProperties.getBasicQos());
            return simpleMessageListenerContainer;
        }
    
    
        /**
         * server for @RabbitListener
         * @EnableRabbit
         * @RabbitListener(queues = MQMessageQueueNames.NULL)
         * @param connectionFactory
         * @return
         */
        @Bean
        public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
            SimpleRabbitListenerContainerFactory simpleMessageListenerContainer =
                    new SimpleRabbitListenerContainerFactory();
            //SimpleRabbitListenerContainerFactory发现消息中有content_type有text就会默认将其转换成string类型的
            simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
            simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
            simpleMessageListenerContainer.setConcurrentConsumers(rabbitMqProperties.getConcurrentConsumers());
            simpleMessageListenerContainer.setMaxConcurrentConsumers(rabbitMqProperties.getConcurrentConsumers());
            simpleMessageListenerContainer.setAdviceChain(retryOperationsInterceptorFactoryBean.getObject());
            simpleMessageListenerContainer.setErrorHandler(mqErrorHandler);
            simpleMessageListenerContainer.setPrefetchCount(rabbitMqProperties.getBasicQos());
            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.io.IOException;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    import com.soul.home.lws.system.mq.config.MQMessageQueueNames;
    import com.soul.home.lws.system.utils.GsonUtils;
    import org.apache.log4j.Logger;
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
    import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
    import org.springframework.amqp.support.converter.MessageConversionException;
    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 IOException {
    	    String msg = new String(message.getBody());
            logger.info("message tag: " + message.getMessageProperties().getDeliveryTag() + ", " + msg);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
            throw new RuntimeException("hanler message " + msg + " failed.");
    //        try {
    //
    //        } catch (MessageConversionException e) {
                logger.error("convert MQ message error.", e);
    //        } finally {
    //            long deliveryTag = message.getMessageProperties().getDeliveryTag();
    //            if (deliveryTag != DELIVERIED_TAG) {
                    // 退回消息则无需进行Ack确认,会自动确认并执行退回交换机队列数据
    //                channel.basicNack(deliveryTag, true, false);  // 批量退回
    //                channel.basicReject(deliveryTag, false);      // 单条退回
    //
    //                channel.basicAck(deliveryTag, true);
    //                message.getMessageProperties().setDeliveryTag(DELIVERIED_TAG);
                    logger.info("revice and ack msg: " + (obj == null ? message : new String((byte[]) obj)));
    //            }
    //        }
    	}
    
    
    }
    
    
  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、付费专栏及课程。

余额充值