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 |
安装脚本步骤:
-
卸载erlang
service rabbitmq-server stop --停止服务 yum list | grep erlang --查询当前环境 yum -y remove erlang-* --卸载 yum remove erlang.x86_64 --移除
-
卸载rabbitMq
yum list | grep rabbitmq --当前是否安装 yum -y remove rabbitmq-server.noarch --移除安装文件
-
安装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.
-
安装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
-
部署插件
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 --弃用插件
-
授权用户访问
rabbitmq-plugins enable rabbitmq_management --启用网页插件 添加用户:rabbitmqctl add_user admin admin 添加权限:rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*" 修改用户角色rabbitmqctl set_user_tags admin administrator 然后就可以远程访问了,然后可直接配置用户权限等信息。
-
启动命令脚本
service rabbitmq-server restart --重启 service rabbitmq-server stop --停止
Spring搭建延时队列-注解方式
此处应用于Spring、SpringBoot等开源框架
-
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
-
定义工厂链接信息
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(); } }
-
定义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); } }
-
进行绑定
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(); } }
-
定义生产者
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); } }
-
定义消费者
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))); // } // } } }
-
辅助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天。